diff --git a/SightKeeper.Avalonia/App.axaml b/SightKeeper.Avalonia/App.axaml index 5ec3813b..30f79fbe 100644 --- a/SightKeeper.Avalonia/App.axaml +++ b/SightKeeper.Avalonia/App.axaml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Compositions/CompositionViewModel.cs b/SightKeeper.Avalonia/DataSets/Compositions/CompositionViewModel.cs new file mode 100644 index 00000000..b74fce34 --- /dev/null +++ b/SightKeeper.Avalonia/DataSets/Compositions/CompositionViewModel.cs @@ -0,0 +1,34 @@ +using System; +using CommunityToolkit.Mvvm.ComponentModel; +using SightKeeper.Domain.Model.DataSets.Screenshots; + +namespace SightKeeper.Avalonia.DataSets.Compositions; + +internal abstract partial class CompositionViewModel : ViewModel +{ + public abstract string DisplayName { get; } + + public static CompositionViewModel? Create(Composition? composition) => composition switch + { + null => null, + FixedTransparentComposition fixedTransparent => new FixedTransparentCompositionViewModel(fixedTransparent), + FloatingTransparentComposition floatingTransparent => new FloatingTransparentCompositionViewModel(floatingTransparent), + _ => throw new ArgumentOutOfRangeException(nameof(composition)) + }; + + public TimeSpan MaximumScreenshotsDelay => TimeSpan.FromMilliseconds(MaximumScreenshotsDelayInMilliseconds); + + public abstract Composition ToComposition(); + + protected CompositionViewModel() + { + _maximumScreenshotsDelayInMilliseconds = 50; + } + + protected CompositionViewModel(Composition composition) + { + _maximumScreenshotsDelayInMilliseconds = (ushort)composition.MaximumScreenshotsDelay.TotalMilliseconds; + } + + [ObservableProperty] private ushort _maximumScreenshotsDelayInMilliseconds; +} \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Compositions/FixedTransparentCompositionViewModel.cs b/SightKeeper.Avalonia/DataSets/Compositions/FixedTransparentCompositionViewModel.cs new file mode 100644 index 00000000..2b91370d --- /dev/null +++ b/SightKeeper.Avalonia/DataSets/Compositions/FixedTransparentCompositionViewModel.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using SightKeeper.Domain.Model.DataSets.Screenshots; + +namespace SightKeeper.Avalonia.DataSets.Compositions; + +internal sealed partial class FixedTransparentCompositionViewModel : CompositionViewModel +{ + public override string DisplayName => "Fixed transparent"; + + public byte ScreenshotsCount + { + get => (byte)Opacities.Count; + set + { + var delta = value - Opacities.Count; + OnPropertyChanging(); + if (delta > 0) + Opacities = Opacities.AddRange(Enumerable.Repeat(0.1m, delta)); + else if (delta < 0) + Opacities = Opacities.RemoveRange(Opacities.Count + delta, -delta); + OnPropertyChanged(); + } + } + + public FixedTransparentCompositionViewModel() + { + _opacities = [0.1m, 0.2m, 0.7m]; + } + + public FixedTransparentCompositionViewModel(FixedTransparentComposition composition) : base(composition) + { + _opacities = composition.Opacities.Select(opacity => (decimal)opacity).ToImmutableList(); + } + + public override FixedTransparentComposition ToComposition() => + new(MaximumScreenshotsDelay, + Opacities.Select(opacity => (float)opacity).ToImmutableArray()); + + [ObservableProperty] private ImmutableList _opacities; +} \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Compositions/FloatingTransparentCompositionViewModel.cs b/SightKeeper.Avalonia/DataSets/Compositions/FloatingTransparentCompositionViewModel.cs new file mode 100644 index 00000000..645932c0 --- /dev/null +++ b/SightKeeper.Avalonia/DataSets/Compositions/FloatingTransparentCompositionViewModel.cs @@ -0,0 +1,30 @@ +using System; +using CommunityToolkit.Mvvm.ComponentModel; +using SightKeeper.Domain.Model.DataSets.Screenshots; + +namespace SightKeeper.Avalonia.DataSets.Compositions; + +internal sealed partial class FloatingTransparentCompositionViewModel : CompositionViewModel +{ + public override string DisplayName => "Floating transparent"; + + public TimeSpan SeriesDuration => TimeSpan.FromMilliseconds(SeriesDurationInMilliseconds); + + public FloatingTransparentCompositionViewModel() + { + _seriesDurationInMilliseconds = 500; + } + + public FloatingTransparentCompositionViewModel(FloatingTransparentComposition composition) : base(composition) + { + _seriesDurationInMilliseconds = (ushort)composition.SeriesDuration.TotalMilliseconds; + } + + public override FloatingTransparentComposition ToComposition() + { + return new FloatingTransparentComposition(MaximumScreenshotsDelay, SeriesDuration, MinimumOpacity); + } + + [ObservableProperty] private ushort _seriesDurationInMilliseconds; + [ObservableProperty] private float _minimumOpacity; +} \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/DataSetEditorViewModel.cs b/SightKeeper.Avalonia/DataSets/Dialogs/DataSetEditorViewModel.cs index ad4cccb1..98fd9f33 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/DataSetEditorViewModel.cs +++ b/SightKeeper.Avalonia/DataSets/Dialogs/DataSetEditorViewModel.cs @@ -6,6 +6,7 @@ using FluentValidation; using SightKeeper.Application.DataSets; using SightKeeper.Application.Games; +using SightKeeper.Avalonia.DataSets.Compositions; using SightKeeper.Domain.Model; using SightKeeper.Domain.Model.DataSets; using SightKeeper.Domain.Model.DataSets.Screenshots; @@ -21,6 +22,13 @@ public event EventHandler? ErrorsChanged } public IReadOnlyCollection Games { get; } + + public IReadOnlyCollection Compositions { get; } = + [ + new FixedTransparentCompositionViewModel(), + new FloatingTransparentCompositionViewModel() + ]; + public bool HasErrors => _validator.HasErrors; public DataSetEditorViewModel(GamesDataAccess gamesDataAccess, IValidator validator) @@ -35,7 +43,7 @@ public DataSetEditorViewModel(GamesDataAccess gamesDataAccess, IValidator(validator, this, this); } @@ -54,5 +62,7 @@ public void Dispose() [ObservableProperty] private string _name = string.Empty; [ObservableProperty] private string _description = string.Empty; [ObservableProperty] private Game? _game; - [ObservableProperty] private Composition? _composition; + [ObservableProperty] private CompositionViewModel? _composition; + + Composition? DataSetData.Composition => Composition?.ToComposition(); } \ No newline at end of file diff --git a/SightKeeper.Avalonia/DataSets/Dialogs/GeneralDataSetEditor.axaml b/SightKeeper.Avalonia/DataSets/Dialogs/GeneralDataSetEditor.axaml index 02f04fd3..9b28394b 100644 --- a/SightKeeper.Avalonia/DataSets/Dialogs/GeneralDataSetEditor.axaml +++ b/SightKeeper.Avalonia/DataSets/Dialogs/GeneralDataSetEditor.axaml @@ -3,6 +3,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SightKeeper.Avalonia.DataSets.Dialogs" + xmlns:compositions="clr-namespace:SightKeeper.Avalonia.DataSets.Compositions" + xmlns:controls="clr-namespace:Sightful.Avalonia.Controls;assembly=Sightful.Avalonia.Controls" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SightKeeper.Avalonia.DataSets.Dialogs.GeneralDataSetEditor" x:DataType="local:DataSetEditorViewModel"> @@ -17,5 +19,52 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SightKeeper.Avalonia/SightKeeper.Avalonia.csproj b/SightKeeper.Avalonia/SightKeeper.Avalonia.csproj index 9d996028..c49b1767 100644 --- a/SightKeeper.Avalonia/SightKeeper.Avalonia.csproj +++ b/SightKeeper.Avalonia/SightKeeper.Avalonia.csproj @@ -41,6 +41,7 @@ + diff --git a/SightKeeper.Data/Binary/Conversion/DataSets/DataSetConverter.cs b/SightKeeper.Data/Binary/Conversion/DataSets/DataSetConverter.cs index 38e9f9dc..06363a41 100644 --- a/SightKeeper.Data/Binary/Conversion/DataSets/DataSetConverter.cs +++ b/SightKeeper.Data/Binary/Conversion/DataSets/DataSetConverter.cs @@ -53,7 +53,7 @@ protected static PackableTag ConvertPlainTag(byte id, Tag tag) return composition switch { null => null, - TransparentComposition transparentComposition => new PackableTransparentComposition( + FixedTransparentComposition transparentComposition => new PackableTransparentComposition( transparentComposition.MaximumScreenshotsDelay, transparentComposition.Opacities), _ => throw new ArgumentOutOfRangeException() diff --git a/SightKeeper.Data/Binary/Model/DataSets/Compositions/PackableTransparentComposition.cs b/SightKeeper.Data/Binary/Model/DataSets/Compositions/PackableTransparentComposition.cs index a59f0748..9a161bf8 100644 --- a/SightKeeper.Data/Binary/Model/DataSets/Compositions/PackableTransparentComposition.cs +++ b/SightKeeper.Data/Binary/Model/DataSets/Compositions/PackableTransparentComposition.cs @@ -5,7 +5,7 @@ namespace SightKeeper.Data.Binary.Model.DataSets.Compositions; /// -/// MemoryPackable version of +/// MemoryPackable version of /// [MemoryPackable] internal sealed partial class PackableTransparentComposition : PackableComposition diff --git a/SightKeeper.Data/Binary/Replication/DataSets/DataSetReplicator.cs b/SightKeeper.Data/Binary/Replication/DataSets/DataSetReplicator.cs index aa72d9e8..c028f641 100644 --- a/SightKeeper.Data/Binary/Replication/DataSets/DataSetReplicator.cs +++ b/SightKeeper.Data/Binary/Replication/DataSets/DataSetReplicator.cs @@ -75,7 +75,7 @@ protected virtual Tag ReplicateTag( { null => null, PackableTransparentComposition transparentComposition => - new TransparentComposition( + new FixedTransparentComposition( transparentComposition.MaximumScreenshotsDelay, transparentComposition.Opacities), _ => throw new ArgumentOutOfRangeException(nameof(composition)) diff --git a/SightKeeper.Domain.Tests/DataSets/Screenshots/FixedTransparentCompositionTests.cs b/SightKeeper.Domain.Tests/DataSets/Screenshots/FixedTransparentCompositionTests.cs new file mode 100644 index 00000000..09745c74 --- /dev/null +++ b/SightKeeper.Domain.Tests/DataSets/Screenshots/FixedTransparentCompositionTests.cs @@ -0,0 +1,30 @@ +using SightKeeper.Domain.Model.DataSets.Screenshots; + +namespace SightKeeper.Domain.Tests.DataSets.Screenshots; + +public sealed class FixedTransparentCompositionTests +{ + [Fact] + public void ShouldCreate() + { + FixedTransparentComposition _ = new(TimeSpan.FromMilliseconds(50), [0.2f, 0.3f, 0.5f]); + } + + [Fact] + public void ShouldNotCreateWithNegativeDelay() + { + Assert.ThrowsAny(() => new FixedTransparentComposition(TimeSpan.FromMilliseconds(-1), [0.5f, 0.5f])); + } + + [Fact] + public void ShouldNotCreateWithOnlyOneOpacity() + { + Assert.ThrowsAny(() => new FixedTransparentComposition(TimeSpan.FromMilliseconds(50), [1])); + } + + [Fact] + public void ShouldNotCreateWithOpacitiesSumNotEqualToOne() + { + Assert.ThrowsAny(() => new FixedTransparentComposition(TimeSpan.FromMilliseconds(50), [0.1f, 0.3f, 0.5f])); + } +} \ No newline at end of file diff --git a/SightKeeper.Domain.Tests/DataSets/Screenshots/TransparentCompositionTests.cs b/SightKeeper.Domain.Tests/DataSets/Screenshots/TransparentCompositionTests.cs deleted file mode 100644 index 49b3526b..00000000 --- a/SightKeeper.Domain.Tests/DataSets/Screenshots/TransparentCompositionTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using SightKeeper.Domain.Model.DataSets.Screenshots; - -namespace SightKeeper.Domain.Tests.DataSets.Screenshots; - -public sealed class TransparentCompositionTests -{ - [Fact] - public void ShouldCreate() - { - TransparentComposition _ = new(TimeSpan.FromMilliseconds(50), [0.2f, 0.3f, 0.5f]); - } - - [Fact] - public void ShouldNotCreateWithNegativeDelay() - { - Assert.ThrowsAny(() => new TransparentComposition(TimeSpan.FromMilliseconds(-1), [0.5f, 0.5f])); - } - - [Fact] - public void ShouldNotCreateWithOnlyOneOpacity() - { - Assert.ThrowsAny(() => new TransparentComposition(TimeSpan.FromMilliseconds(50), [1])); - } - - [Fact] - public void ShouldNotCreateWithOpacitiesSumNotEqualToOne() - { - Assert.ThrowsAny(() => new TransparentComposition(TimeSpan.FromMilliseconds(50), [0.1f, 0.3f, 0.5f])); - } -} \ No newline at end of file diff --git a/SightKeeper.Domain/Model/DataSets/Screenshots/TransparentComposition.cs b/SightKeeper.Domain/Model/DataSets/Screenshots/FixedTransparentComposition.cs similarity index 83% rename from SightKeeper.Domain/Model/DataSets/Screenshots/TransparentComposition.cs rename to SightKeeper.Domain/Model/DataSets/Screenshots/FixedTransparentComposition.cs index 7511f9a6..437fce79 100644 --- a/SightKeeper.Domain/Model/DataSets/Screenshots/TransparentComposition.cs +++ b/SightKeeper.Domain/Model/DataSets/Screenshots/FixedTransparentComposition.cs @@ -3,7 +3,7 @@ namespace SightKeeper.Domain.Model.DataSets.Screenshots; -public sealed class TransparentComposition : Composition +public sealed class FixedTransparentComposition : Composition { public ImmutableArray Opacities { @@ -15,7 +15,7 @@ public ImmutableArray Opacities } } - public TransparentComposition( + public FixedTransparentComposition( TimeSpan maximumScreenshotsDelay, ImmutableArray opacities) : base(maximumScreenshotsDelay) diff --git a/SightKeeper.Domain/Model/DataSets/Screenshots/FloatingTransparentComposition.cs b/SightKeeper.Domain/Model/DataSets/Screenshots/FloatingTransparentComposition.cs new file mode 100644 index 00000000..c5ac891a --- /dev/null +++ b/SightKeeper.Domain/Model/DataSets/Screenshots/FloatingTransparentComposition.cs @@ -0,0 +1,35 @@ +using CommunityToolkit.Diagnostics; + +namespace SightKeeper.Domain.Model.DataSets.Screenshots; + +public sealed class FloatingTransparentComposition : Composition +{ + public TimeSpan SeriesDuration + { + get => _seriesDuration; + set + { + Guard.IsGreaterThan(value, TimeSpan.Zero); + _seriesDuration = value; + } + } + + public float MinimumOpacity + { + get => _minimumOpacity; + set + { + Guard.IsInRange(value, 0, 1); + _minimumOpacity = value; + } + } + + public FloatingTransparentComposition(TimeSpan maximumScreenshotsDelay, TimeSpan seriesDuration, float minimumOpacity) : base(maximumScreenshotsDelay) + { + SeriesDuration = seriesDuration; + MinimumOpacity = minimumOpacity; + } + + private TimeSpan _seriesDuration; + private float _minimumOpacity; +} \ No newline at end of file diff --git a/SightKeeper.sln b/SightKeeper.sln index d8168256..8820566d 100644 --- a/SightKeeper.sln +++ b/SightKeeper.sln @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sightful.Avalonia.Controls. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sightful", "Sightful", "{61ECB802-CDBC-4D92-869F-C0F305B6705C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sightful.Avalonia.Controls", "..\Sightful.Theme\Sightful.Avalonia.Controls\Sightful.Avalonia.Controls.csproj", "{0DAC9FFF-10BF-4E15-83B0-F22F7132991F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,6 +72,10 @@ Global {F7114FB2-C0FF-4FC1-B047-7F2AB9F21EAD}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7114FB2-C0FF-4FC1-B047-7F2AB9F21EAD}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7114FB2-C0FF-4FC1-B047-7F2AB9F21EAD}.Release|Any CPU.Build.0 = Release|Any CPU + {0DAC9FFF-10BF-4E15-83B0-F22F7132991F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DAC9FFF-10BF-4E15-83B0-F22F7132991F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DAC9FFF-10BF-4E15-83B0-F22F7132991F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DAC9FFF-10BF-4E15-83B0-F22F7132991F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4D385463-EE5E-4257-84C8-75C0A0018879} = {EC16BB21-7A91-4224-BAB7-4C896D7B2920} @@ -77,5 +83,6 @@ Global {542EC90B-A2D0-4055-95C6-3FCD5A8CA08D} = {61ECB802-CDBC-4D92-869F-C0F305B6705C} {F7114FB2-C0FF-4FC1-B047-7F2AB9F21EAD} = {61ECB802-CDBC-4D92-869F-C0F305B6705C} {25B85324-71B2-4011-8C8D-FDFF38668A00} = {61ECB802-CDBC-4D92-869F-C0F305B6705C} + {0DAC9FFF-10BF-4E15-83B0-F22F7132991F} = {61ECB802-CDBC-4D92-869F-C0F305B6705C} EndGlobalSection EndGlobal