diff --git a/.editorconfig b/.editorconfig index f0d77090f8..841ef755bf 100644 --- a/.editorconfig +++ b/.editorconfig @@ -75,3 +75,13 @@ indent_size = 2 [*.csproj] indent_size = 4 + +# Verify settings +[*.{received,verified}.{txt,xml,json}] +charset = "utf-8-bom" +end_of_line = lf +indent_size = unset +indent_style = unset +insert_final_newline = false +tab_width = unset +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes index 10a3cece82..56f19b4302 100644 --- a/.gitattributes +++ b/.gitattributes @@ -59,3 +59,8 @@ .gitattributes export-ignore .gitignore export-ignore .gitkeep export-ignore + +# Verify +*.verified.txt text eol=lf working-tree-encoding=UTF-8 +*.verified.xml text eol=lf working-tree-encoding=UTF-8 +*.verified.json text eol=lf working-tree-encoding=UTF-8 diff --git a/.gitignore b/.gitignore index 3b85a8161a..7446eaed8e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,7 @@ Thumbs.db Desktop.ini .DS_Store -coverage.json \ No newline at end of file +coverage.json + +# Verify +*.received.* diff --git a/Directory.Packages.props b/Directory.Packages.props index 23f055cbd1..18dc6b9be1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -58,8 +58,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -80,12 +80,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/NexusMods.App.sln b/NexusMods.App.sln index cfcf80f583..9702dc425d 100644 --- a/NexusMods.App.sln +++ b/NexusMods.App.sln @@ -64,6 +64,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solutionItems", ".solution NuGet.Build.props = NuGet.Build.props README.md = README.md Directory.Packages.props = Directory.Packages.props + .gitignore = .gitignore EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NexusMods.Games.FOMOD", "src\Games\NexusMods.Games.FOMOD\NexusMods.Games.FOMOD.csproj", "{B43A31B2-1F08-4D8E-9C45-48015FBA983E}" @@ -129,6 +130,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.BladeAndSorcery", "src\Games\NexusMods.Games.BladeAndSorcery\NexusMods.Games.BladeAndSorcery.csproj", "{81F23A27-F517-41AD-B86E-6DCE7B4CCE93}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.BladeAndSorcery.Tests", "tests\Games\NexusMods.Games.BladeAndSorcery.Tests\NexusMods.Games.BladeAndSorcery.Tests.csproj", "{C2F6C9E5-CC53-44B7-994C-5B9287408263}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.AdvancedInstaller.UI", "src\Games\NexusMods.Games.AdvancedInstaller.UI\NexusMods.Games.AdvancedInstaller.UI.csproj", "{07B8ACA6-CE4B-496D-B183-63A57C5F08E1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Games.AdvancedInstaller.UI.Tests", "tests\Games\NexusMods.Games.AdvancedInstaller.UI.Tests\NexusMods.Games.AdvancedInstaller.UI.Tests.csproj", "{2BFAAE53-AFFF-4F0B-AD76-67918665F298}" diff --git a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonDesignViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonDesignViewModel.cs index 7b0b96d1f0..25e7422bcf 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonDesignViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonDesignViewModel.cs @@ -6,11 +6,11 @@ public class AddPanelButtonDesignViewModel : AddPanelButtonViewModel { public AddPanelButtonDesignViewModel() : base(DummyState, IconUtils.StateToBitmap(DummyState)) { } - private static readonly IReadOnlyDictionary DummyState = new Dictionary - { - { PanelId.NewId(), new Rect(0, 0, 0.5, 0.5) }, - { PanelId.DefaultValue, new Rect(0.5, 0, 0.5, 0.5) }, - { PanelId.NewId(), new Rect(0, 0.5, 0.5, 0.5) }, - { PanelId.NewId(), new Rect(0.5, 0.5, 0.5, 0.5) }, - }; + private static readonly WorkspaceGridState DummyState = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(), new Rect(0, 0, 0.5, 0.5)), + new PanelGridState(PanelId.NewId(), new Rect(0, 0.5, 0.5, 0.5)), + new PanelGridState(PanelId.NewId(), new Rect(0.5, 0, 0.5, 0.5)), + new PanelGridState(PanelId.DefaultValue, new Rect(0.5, 0.5, 0.5, 0.5)) + ); } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonViewModel.cs index 249e64d88d..2d7a565522 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/AddPanelButtonViewModel.cs @@ -7,12 +7,12 @@ namespace NexusMods.App.UI.WorkspaceSystem; public class AddPanelButtonViewModel : AViewModel, IAddPanelButtonViewModel { - public IReadOnlyDictionary NewLayoutState { get; } + public WorkspaceGridState NewLayoutState { get; } public IImage ButtonImage { get; } - public ReactiveCommand> AddPanelCommand { get; } + public ReactiveCommand AddPanelCommand { get; } public AddPanelButtonViewModel( - IReadOnlyDictionary newLayoutState, + WorkspaceGridState newLayoutState, IImage buttonImage) { NewLayoutState = newLayoutState; diff --git a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/IAddPanelButtonViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/IAddPanelButtonViewModel.cs index 078244f87b..f078f62eea 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/IAddPanelButtonViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/AddPanelButton/IAddPanelButtonViewModel.cs @@ -1,5 +1,4 @@ using System.Reactive; -using Avalonia; using Avalonia.Media; using ReactiveUI; @@ -7,9 +6,9 @@ namespace NexusMods.App.UI.WorkspaceSystem; public interface IAddPanelButtonViewModel : IViewModelInterface { - public IReadOnlyDictionary NewLayoutState { get; } + public WorkspaceGridState NewLayoutState { get; } public IImage ButtonImage { get; } - public ReactiveCommand> AddPanelCommand { get; } + public ReactiveCommand AddPanelCommand { get; } } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/IconUtils.cs b/src/NexusMods.App.UI/WorkspaceSystem/IconUtils.cs index 877962502e..0ba74d07aa 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/IconUtils.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/IconUtils.cs @@ -23,7 +23,7 @@ internal static class IconUtils /// /// Generates a for the given state. /// - internal static Bitmap StateToBitmap(IReadOnlyDictionary state) + internal static Bitmap StateToBitmap(WorkspaceGridState state) { using var skPicture = GeneratePicture(state); using var skBitmap = skPicture.ToBitmap( @@ -39,7 +39,7 @@ internal static Bitmap StateToBitmap(IReadOnlyDictionary state) return skBitmap.ToAvaloniaImage(); } - private static SKPicture GeneratePicture(IReadOnlyDictionary state) + private static SKPicture GeneratePicture(WorkspaceGridState state) { using var skPictureRecorder = new SKPictureRecorder(); using var skCanvas = skPictureRecorder.BeginRecording(new SKRect(0f, 0f, IconSize, IconSize)); @@ -52,9 +52,9 @@ private static SKPicture GeneratePicture(IReadOnlyDictionary stat using var skPathFilled = new SKPath(); using var skPathHollow = new SKPath(); - foreach (var kv in state) + foreach (var panel in state) { - var (panelId, rect) = kv; + var (panelId, rect) = panel; DrawRect(skPathFilled, skPathHollow, rect, isHollow: panelId != PanelId.DefaultValue); } diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs index 49dd636afb..cb53e76664 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/IWorkspaceViewModel.cs @@ -11,6 +11,8 @@ public interface IWorkspaceViewModel : IViewModelInterface public ReadOnlyObservableCollection AddPanelButtonViewModels { get; } + public bool IsHorizontal { get; } + /// /// Called by the View to notify the VM about the new size of the control. /// @@ -20,7 +22,7 @@ public interface IWorkspaceViewModel : IViewModelInterface /// Add a new panel to the workspace. /// /// The newly created . - public IPanelViewModel AddPanel(IReadOnlyDictionary state); + public IPanelViewModel AddPanel(WorkspaceGridState state); /// /// Transforms the current state of the workspace into a serializable data format. diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspacePlaygroundViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspacePlaygroundViewModel.cs index 19a1714895..19ec57fbc0 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspacePlaygroundViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspacePlaygroundViewModel.cs @@ -2,11 +2,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Text.Json; -using Avalonia; using Microsoft.Extensions.DependencyInjection; -using NexusMods.App.UI.RightContent.LoadoutGrid; -using NexusMods.Common; -using NexusMods.DataModel.Loadouts; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -63,10 +59,10 @@ public WorkspacePlaygroundViewModel() this.WhenActivated(disposables => { - WorkspaceViewModel.AddPanel(new Dictionary + WorkspaceViewModel.AddPanel(WorkspaceGridState.From(new[] { - { PanelId.DefaultValue, MathUtils.One } - }); + new PanelGridState(PanelId.DefaultValue, MathUtils.One) + }, isHorizontal: WorkspaceViewModel.IsHorizontal)); Disposable.Create(() => { }).DisposeWith(disposables); }); diff --git a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs index cb71465e35..511b04d325 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/Workspace/WorkspaceViewModel.cs @@ -206,7 +206,7 @@ public WorkspaceViewModel(PageFactoryController factoryController) private Size _lastWorkspaceSize; - [Reactive] private bool IsHorizontal { get; set; } + [Reactive] public bool IsHorizontal { get; private set; } /// public void Arrange(Size workspaceSize) @@ -244,10 +244,8 @@ private void UpdateStates() foreach (var state in newStates) { - var dict = state.ToDictionary(); - - var image = IconUtils.StateToBitmap(dict); - updater.Add(new AddPanelButtonViewModel(dict, image)); + var image = IconUtils.StateToBitmap(state); + updater.Add(new AddPanelButtonViewModel(state, image)); } }); } @@ -271,14 +269,14 @@ private void UpdateResizers() } /// - public IPanelViewModel AddPanel(IReadOnlyDictionary state) + public IPanelViewModel AddPanel(WorkspaceGridState state) { IPanelViewModel panelViewModel = null!; _panelSource.Edit(updater => { - foreach (var kv in state) + foreach (var panel in state) { - var (panelId, logicalBounds) = kv; + var (panelId, logicalBounds) = panel; if (panelId == PanelId.DefaultValue) { panelViewModel = new PanelViewModel(_factoryController) diff --git a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceGridState.cs b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceGridState.cs index f345890127..467bb04917 100644 --- a/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceGridState.cs +++ b/src/NexusMods.App.UI/WorkspaceSystem/WorkspaceGridState.cs @@ -44,6 +44,16 @@ public static WorkspaceGridState From(IEnumerable panels, bool i ); } + public static WorkspaceGridState From(bool isHorizontal, params PanelGridState[] panels) => From(panels, isHorizontal); + + public static WorkspaceGridState From(IReadOnlyDictionary panels, bool isHorizontal) + { + return new WorkspaceGridState( + inner: panels.Select(kv => new PanelGridState(kv.Key, kv.Value)).ToImmutableSortedSet(PanelGridStateComparer.Instance), + isHorizontal + ); + } + public static WorkspaceGridState Empty(bool isHorizontal) => new(ImmutableSortedSet.Empty, isHorizontal); private WorkspaceGridState WithInner(ImmutableSortedSet inner) diff --git a/tests/NexusMods.UI.Tests/ModuleInitializer.cs b/tests/NexusMods.UI.Tests/ModuleInitializer.cs new file mode 100644 index 0000000000..45105a1484 --- /dev/null +++ b/tests/NexusMods.UI.Tests/ModuleInitializer.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; +using ImageMagick; + +namespace NexusMods.UI.Tests; + +public static class ModuleInitializer +{ + [ModuleInitializer] + public static void Init() + { + VerifyImageMagick.Initialize(); + VerifyImageMagick.RegisterComparers(threshold: 0.005D, metric: ErrorMetric.Fuzz); + } + + [ModuleInitializer] + public static void InitOther() + { + VerifierSettings.InitializePlugins(); + } +} diff --git a/tests/NexusMods.UI.Tests/NexusMods.UI.Tests.csproj b/tests/NexusMods.UI.Tests/NexusMods.UI.Tests.csproj index 18830ab43c..968b07dbab 100644 --- a/tests/NexusMods.UI.Tests/NexusMods.UI.Tests.csproj +++ b/tests/NexusMods.UI.Tests/NexusMods.UI.Tests.csproj @@ -8,6 +8,8 @@ + + @@ -23,5 +25,9 @@ PreserveNewest + + IconUtilsTests + IconUtilsTests.cs + diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_FourPanels.verified.png b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_FourPanels.verified.png new file mode 100644 index 0000000000..4c9ec76909 Binary files /dev/null and b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_FourPanels.verified.png differ diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeColumn.verified.png b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeColumn.verified.png new file mode 100644 index 0000000000..6ea9cd8794 Binary files /dev/null and b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeColumn.verified.png differ diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeRow.verified.png b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeRow.verified.png new file mode 100644 index 0000000000..f3080969ca Binary files /dev/null and b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_ThreePanels_OneLargeRow.verified.png differ diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoColumns.verified.png b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoColumns.verified.png new file mode 100644 index 0000000000..7cca4168d2 Binary files /dev/null and b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoColumns.verified.png differ diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoRows.verified.png b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoRows.verified.png new file mode 100644 index 0000000000..802e6f18d1 Binary files /dev/null and b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.Test_StateToBitmap_TwoRows.verified.png differ diff --git a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.cs b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.cs index d3dfaaeb75..8d5619cf40 100644 --- a/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.cs +++ b/tests/NexusMods.UI.Tests/WorkspaceSystem/IconUtilsTests.cs @@ -1,23 +1,86 @@ +using System.Runtime.CompilerServices; using Avalonia; -using FluentAssertions; using NexusMods.App.UI.WorkspaceSystem; namespace NexusMods.UI.Tests.WorkspaceSystem; -public class IconUtilsTests : AUiTest +[UsesVerify] +public class IconUtilsTests(IServiceProvider provider) : AUiTest(provider) { - public IconUtilsTests(IServiceProvider provider) : base(provider) { } + [Fact] + public Task Test_StateToBitmap_TwoColumns() + { + var state = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(),new Rect(0, 0, 0.5, 1)), + new PanelGridState(PanelId.DefaultValue, new Rect(0.5, 0, 0.5, 1)) + ); + + return RunVerify(state); + } + + [Fact] + public Task Test_StateToBitmap_TwoRows() + { + var state = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(), new Rect(0, 0, 1, 0.5)), + new PanelGridState(PanelId.DefaultValue, new Rect(0, 0.5, 1, 0.5)) + ); + + return RunVerify(state); + } [Fact] - public void Test_StateToBitmap() + public Task Test_StateToBitmap_ThreePanels_OneLargeColumn() + { + var state = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(),new Rect(0, 0, 0.5, 0.5)), + new PanelGridState(PanelId.DefaultValue, new Rect(0.5, 0, 0.5, 1)), + new PanelGridState(PanelId.NewId(), new Rect(0, 0.5, 0.5, 0.5)) + ); + + return RunVerify(state); + } + + [Fact] + public Task Test_StateToBitmap_ThreePanels_OneLargeRow() + { + var state = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(), new Rect(0, 0, 0.5, 0.5)), + new PanelGridState(PanelId.NewId(), new Rect(0.5, 0, 0.5, 0.5)), + new PanelGridState(PanelId.DefaultValue,new Rect(0, 0.5, 1, 0.5)) + ); + + return RunVerify(state); + } + + [Fact] + public Task Test_StateToBitmap_FourPanels() + { + var state = WorkspaceGridState.From( + isHorizontal: true, + new PanelGridState(PanelId.NewId(), new Rect(0, 0, 0.5, 0.5)), + new PanelGridState(PanelId.NewId(), new Rect(0, 0.5, 0.5, 0.5)), + new PanelGridState(PanelId.NewId(), new Rect(0.5, 0, 0.5, 0.5)), + new PanelGridState(PanelId.DefaultValue, new Rect(0.5, 0.5, 0.5, 0.5)) + ); + + return RunVerify(state); + } + + private static Task RunVerify(WorkspaceGridState state, [CallerFilePath] string sourceFile = "") { - var state = new Dictionary + using var stream = new MemoryStream(); + using (var bitmap = IconUtils.StateToBitmap(state)) { - { PanelId.NewId(), new Rect(0, 0, 0.5, 1) }, - { PanelId.DefaultValue, new Rect(0.5, 0, 0.5, 1) } - }; + bitmap.Save(stream); + stream.Position = 0; + } - var bitmap = IconUtils.StateToBitmap(state); - bitmap.Size.Should().Be(new Size(150, 150)); + // ReSharper disable once ExplicitCallerInfoArgument + return Verify(stream, extension: "png", sourceFile: sourceFile).DisableDiff(); } }