From 36d9e73cacc7e3f05d8072f96f729fde8bb7307b Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 14:19:40 +1100 Subject: [PATCH 1/6] Replaced deprecated API usage --- .../Workspace/WorkspaceWidgetViewModel.cs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs index 1088ed66d..dac747ae4 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs @@ -31,33 +31,37 @@ public WorkspaceWidgetViewModel(IContext context, IMonitor monitor) _context = context; Monitor = monitor; - _context.WorkspaceManager.WorkspaceAdded += WorkspaceManager_WorkspaceAdded; - _context.WorkspaceManager.WorkspaceRemoved += WorkspaceManager_WorkspaceRemoved; - _context.Butler.MonitorWorkspaceChanged += Butler_MonitorWorkspaceChanged; - _context.WorkspaceManager.WorkspaceRenamed += WorkspaceManager_WorkspaceRenamed; + _context.Store.WorkspaceEvents.WorkspaceAdded += WorkspaceEvents_WorkspaceAdded; + _context.Store.WorkspaceEvents.WorkspaceRemoved += WorkspaceEvents_WorkspaceRemoved; + _context.Store.MapEvents.MonitorWorkspaceChanged += MapEvents_MonitorWorkspaceChanged; + _context.Store.WorkspaceEvents.WorkspaceRenamed += WorkspaceEvents_WorkspaceRenamed; // Populate the list of workspaces - foreach (IWorkspace workspace in _context.WorkspaceManager) + foreach (IWorkspace workspace in _context.Store.Pick(Pickers.PickWorkspaces())) { - IMonitor? monitorForWorkspace = _context.Butler.Pantry.GetMonitorForWorkspace(workspace); + IMonitor? monitorForWorkspace = _context + .Store.Pick(Pickers.PickMonitorByWorkspace(workspace.Id)) + .ValueOrDefault; Workspaces.Add(new WorkspaceModel(context, this, workspace, Monitor.Handle == monitorForWorkspace?.Handle)); } } - private void WorkspaceManager_WorkspaceAdded(object? sender, WorkspaceEventArgs args) + private void WorkspaceEvents_WorkspaceAdded(object? sender, WorkspaceEventArgs args) { if (Workspaces.Any(model => model.Workspace.Id == args.Workspace.Id)) { return; } - IMonitor? monitorForWorkspace = _context.Butler.Pantry.GetMonitorForWorkspace(args.Workspace); + IMonitor? monitorForWorkspace = _context + .Store.Pick(Pickers.PickMonitorByWorkspace(args.Workspace.Id)) + .ValueOrDefault; Workspaces.Add( new WorkspaceModel(_context, this, args.Workspace, Monitor.Handle == monitorForWorkspace?.Handle) ); } - private void WorkspaceManager_WorkspaceRemoved(object? sender, WorkspaceEventArgs args) + private void WorkspaceEvents_WorkspaceRemoved(object? sender, WorkspaceEventArgs args) { WorkspaceModel? workspaceModel = Workspaces.FirstOrDefault(model => model.Workspace.Id == args.Workspace.Id); if (workspaceModel == null) @@ -68,7 +72,7 @@ private void WorkspaceManager_WorkspaceRemoved(object? sender, WorkspaceEventArg Workspaces.Remove(workspaceModel); } - private void Butler_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChangedEventArgs args) + private void MapEvents_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChangedEventArgs args) { if (args.Monitor.Handle != Monitor.Handle) { @@ -81,7 +85,7 @@ private void Butler_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChan } } - private void WorkspaceManager_WorkspaceRenamed(object? sender, WorkspaceRenamedEventArgs e) + private void WorkspaceEvents_WorkspaceRenamed(object? sender, WorkspaceRenamedEventArgs e) { WorkspaceModel? workspace = Workspaces.FirstOrDefault(m => m.Workspace.Id == e.Workspace.Id); if (workspace == null) @@ -100,10 +104,10 @@ protected virtual void Dispose(bool disposing) if (disposing) { // dispose managed state (managed objects) - _context.WorkspaceManager.WorkspaceAdded -= WorkspaceManager_WorkspaceAdded; - _context.WorkspaceManager.WorkspaceRemoved -= WorkspaceManager_WorkspaceRemoved; - _context.Butler.MonitorWorkspaceChanged -= Butler_MonitorWorkspaceChanged; - _context.WorkspaceManager.WorkspaceRenamed -= WorkspaceManager_WorkspaceRenamed; + _context.Store.WorkspaceEvents.WorkspaceAdded -= WorkspaceEvents_WorkspaceAdded; + _context.Store.WorkspaceEvents.WorkspaceRemoved -= WorkspaceEvents_WorkspaceRemoved; + _context.Store.MapEvents.MonitorWorkspaceChanged -= MapEvents_MonitorWorkspaceChanged; + _context.Store.WorkspaceEvents.WorkspaceRenamed -= WorkspaceEvents_WorkspaceRenamed; } // free unmanaged resources (unmanaged objects) and override finalizer From f8e4a368aac348bd828fe108c97b3bf38c77e303 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 15:13:52 +1100 Subject: [PATCH 2/6] Only show the workspaces for each monitor --- .../Workspace/WorkspaceWidgetViewModel.cs | 46 ++++------ .../Store/MapSector/MapPickersTests.cs | 14 +-- src/Whim/Store/MapSector/MapPickers.cs | 91 ++++++++++++++++++- .../Transforms/ActivateWorkspaceTransform.cs | 2 +- 4 files changed, 115 insertions(+), 38 deletions(-) diff --git a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs index dac747ae4..2f459f327 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Threading; namespace Whim.Bar; @@ -36,41 +38,33 @@ public WorkspaceWidgetViewModel(IContext context, IMonitor monitor) _context.Store.MapEvents.MonitorWorkspaceChanged += MapEvents_MonitorWorkspaceChanged; _context.Store.WorkspaceEvents.WorkspaceRenamed += WorkspaceEvents_WorkspaceRenamed; - // Populate the list of workspaces - foreach (IWorkspace workspace in _context.Store.Pick(Pickers.PickWorkspaces())) + UpdateWorkspacesCollection(); + } + + private void UpdateWorkspacesCollection() + { + Workspaces.Clear(); + + IReadOnlyList workspaces = + _context.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(Monitor.Handle)).ValueOrDefault ?? []; + + foreach (IWorkspace workspace in workspaces) { IMonitor? monitorForWorkspace = _context .Store.Pick(Pickers.PickMonitorByWorkspace(workspace.Id)) .ValueOrDefault; - Workspaces.Add(new WorkspaceModel(context, this, workspace, Monitor.Handle == monitorForWorkspace?.Handle)); - } - } - private void WorkspaceEvents_WorkspaceAdded(object? sender, WorkspaceEventArgs args) - { - if (Workspaces.Any(model => model.Workspace.Id == args.Workspace.Id)) - { - return; + Workspaces.Add( + new WorkspaceModel(_context, this, workspace, Monitor.Handle == monitorForWorkspace?.Handle) + ); } - - IMonitor? monitorForWorkspace = _context - .Store.Pick(Pickers.PickMonitorByWorkspace(args.Workspace.Id)) - .ValueOrDefault; - Workspaces.Add( - new WorkspaceModel(_context, this, args.Workspace, Monitor.Handle == monitorForWorkspace?.Handle) - ); } - private void WorkspaceEvents_WorkspaceRemoved(object? sender, WorkspaceEventArgs args) - { - WorkspaceModel? workspaceModel = Workspaces.FirstOrDefault(model => model.Workspace.Id == args.Workspace.Id); - if (workspaceModel == null) - { - return; - } + private void WorkspaceEvents_WorkspaceAdded(object? sender, WorkspaceEventArgs args) => + UpdateWorkspacesCollection(); - Workspaces.Remove(workspaceModel); - } + private void WorkspaceEvents_WorkspaceRemoved(object? sender, WorkspaceEventArgs args) => + UpdateWorkspacesCollection(); private void MapEvents_MonitorWorkspaceChanged(object? sender, MonitorWorkspaceChangedEventArgs args) { diff --git a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs index fadbd6c76..adf40cadb 100644 --- a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs +++ b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs @@ -422,7 +422,7 @@ internal void PickExplicitStickyMonitorIndicesByWorkspace_Success(IContext ctx, } } -public class PickValidMonitorForWorkspaceTests +public class PickValidMonitorByWorkspaceTests { [Theory, AutoSubstituteData] internal void TargetMonitorIsValid(IContext ctx, MutableRootSector root) @@ -438,7 +438,7 @@ internal void TargetMonitorIsValid(IContext ctx, MutableRootSector root) ); // When we get the monitor - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)1)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)1)); // Then we get the target monitor Assert.True(result.IsSuccessful); @@ -464,7 +464,7 @@ internal void FallbackToLastMonitor(IContext ctx, MutableRootSector root) ); // When we try to use monitor 1 - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)1)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)1)); // Then we get monitor 2 since it's the last valid monitor used Assert.True(result.IsSuccessful); @@ -490,7 +490,7 @@ internal void FallbackToFirstAvailableMonitor(IContext ctx, MutableRootSector ro ); // When we try to use monitor 2 - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id, (HMONITOR)2)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id, (HMONITOR)2)); // Then we get monitor 1 as it's the first valid monitor Assert.True(result.IsSuccessful); @@ -513,7 +513,7 @@ internal void NoValidMonitors(IContext ctx, MutableRootSector root) ); // When we try to get a valid monitor - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id)); // Then we get an error since there are no valid monitors and no fallback monitors Assert.False(result.IsSuccessful); @@ -531,7 +531,7 @@ internal void UseActiveMonitorWhenNoTargetSpecified(IContext ctx, MutableRootSec root.MonitorSector.ActiveMonitorHandle = (HMONITOR)2; // When we don't specify a target monitor - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(workspace.Id)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(workspace.Id)); // Then we get the active monitor Assert.True(result.IsSuccessful); @@ -545,7 +545,7 @@ internal void WorkspaceDoesNotExist(IContext ctx) Guid nonExistentWorkspaceId = Guid.NewGuid(); // When we try to get a valid monitor - var result = ctx.Store.Pick(Pickers.PickValidMonitorForWorkspace(nonExistentWorkspaceId)); + var result = ctx.Store.Pick(Pickers.PickValidMonitorByWorkspace(nonExistentWorkspaceId)); // Then we get an error Assert.False(result.IsSuccessful); diff --git a/src/Whim/Store/MapSector/MapPickers.cs b/src/Whim/Store/MapSector/MapPickers.cs index f82fe7c61..bf9392cf4 100644 --- a/src/Whim/Store/MapSector/MapPickers.cs +++ b/src/Whim/Store/MapSector/MapPickers.cs @@ -300,10 +300,17 @@ out ImmutableArray monitorIndices /// /// /// - /// - /// - /// - public static PurePicker> PickValidMonitorForWorkspace( + /// + /// The ID of the workspace to get the monitor for. + /// + /// + /// The preferred monitor to use. If not provided, the last monitor the workspace was activated on will next be tried. + /// + /// + /// The first valid monitor for the workspace, when passed to . + /// If the workspace can't be found, then an error is returned. + /// + public static PurePicker> PickValidMonitorByWorkspace( WorkspaceId workspaceId, HMONITOR monitorHandle = default ) => @@ -359,4 +366,80 @@ public static PurePicker> PickValidMonitorForWorkspace( return Result.FromException(StoreExceptions.NoValidMonitorForWorkspace(workspaceId)); }; + + /// + /// Retrieves the workspaces which can be shown on the given monitor. + /// + /// + /// The handle of the monitor to get the workspaces for. + /// + /// + /// The workspaces which can be shown on the monitor, when passed to . + /// If the monitor can't be found, then an error is returned. + /// + public static PurePicker>> PickStickyWorkspacesByMonitor(HMONITOR monitorHandle) => + rootSector => + { + IMapSector mapSector = rootSector.MapSector; + IWorkspaceSector workspaceSector = rootSector.WorkspaceSector; + IMonitorSector monitorSector = rootSector.MonitorSector; + + // Verify the monitor exists. + Result monitorResult = PickMonitorByHandle(monitorHandle)(rootSector); + if (!monitorResult.IsSuccessful) + { + return Result.FromException>(monitorResult.Error!); + } + + // Get the index of the monitor. + IMonitor monitor = monitorResult.Value; + ImmutableArray monitors = monitorSector.Monitors; + int monitorIndex = monitors.IndexOf(monitor); + + // Get the workspaces which can be shown on the monitor. + List processedWorkspaces = []; + List unsortedWorkspaces = []; + + foreach ( + ( + WorkspaceId workspaceId, + ImmutableArray monitorIndices + ) in mapSector.StickyWorkspaceMonitorIndexMap + ) + { + // If the workspace is sticky to the monitor, or it's orphaned. + if ( + monitorIndices.Contains(monitorIndex) + || monitorIndices.All(index => index < 0 || index >= monitors.Length) + ) + { + unsortedWorkspaces.Add(workspaceId); + } + + processedWorkspaces.Add(workspaceId); + } + + // Get the workspaces which can be shown on any monitor. + foreach (WorkspaceId workspaceId in workspaceSector.Workspaces.Keys) + { + if (processedWorkspaces.Contains(workspaceId)) + { + continue; + } + + unsortedWorkspaces.Add(workspaceId); + } + + // Crude sorting. + List sortedWorkspaces = []; + foreach (WorkspaceId workspaceId in workspaceSector.WorkspaceOrder) + { + if (unsortedWorkspaces.Contains(workspaceId)) + { + sortedWorkspaces.Add(workspaceSector.Workspaces[workspaceId]); + } + } + + return sortedWorkspaces; + }; } diff --git a/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs b/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs index 00b98978a..747f16445 100644 --- a/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs +++ b/src/Whim/Store/MapSector/Transforms/ActivateWorkspaceTransform.cs @@ -31,7 +31,7 @@ internal override Result Execute(IContext ctx, IInternalContext internalCt } Result targetMonitorHandleResult = ctx.Store.Pick( - PickValidMonitorForWorkspace(workspace.Id, MonitorHandle) + PickValidMonitorByWorkspace(workspace.Id, MonitorHandle) ); if (!targetMonitorHandleResult.TryGet(out HMONITOR targetMonitorHandle)) { From dc3b17451c20bc88dd826ab3951013f72c862687 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 16:38:57 +1100 Subject: [PATCH 3/6] WorkspaceWidgetViewModelTests --- .../WorkspaceWidgetViewModelTests.cs | 337 ++++++++---------- .../Workspace/WorkspaceWidgetViewModel.cs | 1 - 2 files changed, 139 insertions(+), 199 deletions(-) diff --git a/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs b/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs index 17d024808..fdd20d771 100644 --- a/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs +++ b/src/Whim.Bar.Tests/Workspace/WorkspaceWidgetViewModelTests.cs @@ -1,4 +1,3 @@ -using AutoFixture; using NSubstitute; using Whim.TestUtils; using Windows.Win32.Graphics.Gdi; @@ -6,282 +5,224 @@ namespace Whim.Bar.Tests; -public class WorkspaceWidgetViewModelCustomization : ICustomization -{ - public void Customize(IFixture fixture) - { - IContext context = fixture.Freeze(); - - // The workspace manager should have a single workspace - using IWorkspace workspace = fixture.Create(); - workspace.Id.Returns(Guid.NewGuid()); - context.WorkspaceManager.GetEnumerator().Returns((_) => new List() { workspace }.GetEnumerator()); - - fixture.Inject(context); - } -} - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] public class WorkspaceWidgetViewModelTests { - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceAdded_AlreadyExists(IContext context, IMonitor monitor) + [Theory, AutoSubstituteData] + internal void WorkspaceWidgetViewModel_Ctor(IContext ctx, MutableRootSector root) { // Given - IWorkspace workspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); + + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2); // When - context.WorkspaceManager.WorkspaceAdded += Raise.Event>( - context.WorkspaceManager, - new WorkspaceAddedEventArgs() { Workspace = workspace } - ); + WorkspaceWidgetViewModel sut = new(ctx, monitor); // Then - Assert.Single(viewModel.Workspaces); - Assert.Equal(workspace, viewModel.Workspaces[0].Workspace); - context.Butler.Pantry.Received(1).GetMonitorForWorkspace(workspace); + Assert.Equal(2, sut.Workspaces.Count); + Assert.Same(workspace1, sut.Workspaces[0].Workspace); + Assert.Same(workspace2, sut.Workspaces[1].Workspace); + Assert.False(sut.Workspaces[0].ActiveOnMonitor); + Assert.True(sut.Workspaces[1].ActiveOnMonitor); } - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceAdded(IContext context, IMonitor monitor, IWorkspace addedWorkspace) + [Theory, AutoSubstituteData] + internal void WorkspaceAdded(IContext ctx, MutableRootSector root) { // Given - IWorkspace workspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); - // When - context.WorkspaceManager.WorkspaceAdded += Raise.Event>( - context.WorkspaceManager, - new WorkspaceAddedEventArgs() { Workspace = addedWorkspace } - ); + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2); - // Then - Assert.Equal(2, viewModel.Workspaces.Count); - context.Butler.Pantry.Received(1).GetMonitorForWorkspace(workspace); - context.Butler.Pantry.Received(1).GetMonitorForWorkspace(addedWorkspace); - } + WorkspaceWidgetViewModel sut = new(ctx, monitor); - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceRemoved(IContext context, IMonitor monitor) - { - // Given - IWorkspace workspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + Workspace workspace3 = StoreTestUtils.CreateWorkspace(ctx); + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace3); // When - context.WorkspaceManager.WorkspaceRemoved += Raise.Event>( - context.WorkspaceManager, - new WorkspaceRemovedEventArgs() { Workspace = workspace } - ); + root.WorkspaceSector.QueueEvent(new WorkspaceAddedEventArgs() { Workspace = workspace3 }); + root.DispatchEvents(); // Then - Assert.Empty(viewModel.Workspaces); + Assert.Equal(3, sut.Workspaces.Count); + Assert.Same(workspace1, sut.Workspaces[0].Workspace); + Assert.Same(workspace2, sut.Workspaces[1].Workspace); + Assert.Same(workspace3, sut.Workspaces[2].Workspace); + Assert.False(sut.Workspaces[0].ActiveOnMonitor); + Assert.True(sut.Workspaces[1].ActiveOnMonitor); + Assert.False(sut.Workspaces[2].ActiveOnMonitor); } - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceRemoved_DoesNotExist( - IContext context, - IMonitor monitor, - IWorkspace removedWorkspace - ) + [Theory, AutoSubstituteData] + internal void WorkspaceRemoved(IContext ctx, MutableRootSector root) { // Given - IWorkspace workspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace3 = StoreTestUtils.CreateWorkspace(ctx); - // When - context.WorkspaceManager.WorkspaceRemoved += Raise.Event>( - context.WorkspaceManager, - new WorkspaceRemovedEventArgs() { Workspace = removedWorkspace } - ); + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace2); - // Then - Assert.Single(viewModel.Workspaces); - Assert.Equal(workspace, viewModel.Workspaces[0].Workspace); - } - - #region WorkspaceManager_MonitorWorkspaceChanged - [Theory, AutoSubstituteData] - public void WorkspaceManager_MonitorWorkspaceChanged_Deactivate( - IContext context, - IMonitor monitor, - IWorkspace currentWorkspace - ) - { - // Given - IWorkspace previousWorkspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + WorkspaceWidgetViewModel sut = new(ctx, monitor); // When - context.Butler.MonitorWorkspaceChanged += Raise.Event>( - context.WorkspaceManager, - new MonitorWorkspaceChangedEventArgs() - { - Monitor = monitor, - PreviousWorkspace = previousWorkspace, - CurrentWorkspace = currentWorkspace, - } - ); + root.WorkspaceSector.Workspaces = root.WorkspaceSector.Workspaces.Remove(workspace1.Id); + root.WorkspaceSector.QueueEvent(new WorkspaceRemovedEventArgs() { Workspace = workspace1 }); + root.DispatchEvents(); // Then - WorkspaceModel model = viewModel.Workspaces[0]; - Assert.False(model.ActiveOnMonitor); + Assert.Equal(2, sut.Workspaces.Count); + Assert.Same(workspace2, sut.Workspaces[0].Workspace); + Assert.Same(workspace3, sut.Workspaces[1].Workspace); + Assert.True(sut.Workspaces[0].ActiveOnMonitor); + Assert.False(sut.Workspaces[1].ActiveOnMonitor); } - [Theory, AutoSubstituteData] - public void WorkspaceManager_MonitorWorkspaceChanged_Activate( - IContext context, - IMonitor monitor, - IWorkspace addedWorkspace - ) + [Theory, AutoSubstituteData] + internal void MonitorWorkspaceChanged_WrongMonitor(IContext ctx, MutableRootSector root) { // Given - monitor.Handle.Returns((HMONITOR)100); + IMonitor monitor1 = StoreTestUtils.CreateMonitor((HMONITOR)100); + IMonitor monitor2 = StoreTestUtils.CreateMonitor((HMONITOR)200); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); - IWorkspace workspace = context.WorkspaceManager.First(); - context.Butler.Pantry.GetMonitorForWorkspace(workspace).Returns(monitor); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor1, workspace1); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor2, workspace2); - // Add workspace - context.WorkspaceManager.WorkspaceAdded += Raise.Event>( - context.WorkspaceManager, - new WorkspaceAddedEventArgs() { Workspace = addedWorkspace } - ); - - // Verify that the correct workspace is active on the monitor - WorkspaceModel existingModel = viewModel.Workspaces[0]; - WorkspaceModel addedWorkspaceModel = viewModel.Workspaces[1]; - Assert.True(existingModel.ActiveOnMonitor); - Assert.False(addedWorkspaceModel.ActiveOnMonitor); + WorkspaceWidgetViewModel sut = new(ctx, monitor1); // When - context.Butler.MonitorWorkspaceChanged += Raise.Event>( - context.WorkspaceManager, - new MonitorWorkspaceChangedEventArgs() - { - Monitor = monitor, - PreviousWorkspace = existingModel.Workspace, - CurrentWorkspace = addedWorkspaceModel.Workspace, - } + root.MapSector.QueueEvent( + new MonitorWorkspaceChangedEventArgs() { Monitor = monitor2, CurrentWorkspace = workspace2 } ); + root.DispatchEvents(); // Then - Assert.False(existingModel.ActiveOnMonitor); - Assert.True(addedWorkspaceModel.ActiveOnMonitor); + Assert.Equal(2, sut.Workspaces.Count); + Assert.Same(workspace1, sut.Workspaces[0].Workspace); + Assert.Same(workspace2, sut.Workspaces[1].Workspace); + Assert.True(sut.Workspaces[0].ActiveOnMonitor); + Assert.False(sut.Workspaces[1].ActiveOnMonitor); } - [Theory, AutoSubstituteData] - public void WorkspaceManager_MonitorWorkspaceChanged_DifferentMonitor( - IContext context, - IMonitor monitor, - IWorkspace addedWorkspace, - IMonitor otherMonitor - ) + [Theory, AutoSubstituteData] + internal void MonitorWorkspaceChanged_CorrectMonitor(IContext ctx, MutableRootSector root) { // Given - monitor.Handle.Returns((HMONITOR)100); - - IWorkspace workspace = context.WorkspaceManager.First(); - context.Butler.Pantry.GetMonitorForWorkspace(workspace).Returns(monitor); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); - // Add workspace - context.WorkspaceManager.WorkspaceAdded += Raise.Event>( - context.WorkspaceManager, - new WorkspaceAddedEventArgs() { Workspace = addedWorkspace } - ); + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace1); - // Verify that the correct workspace is active on the monitor - WorkspaceModel existingModel = viewModel.Workspaces[0]; - WorkspaceModel addedWorkspaceModel = viewModel.Workspaces[1]; - Assert.True(existingModel.ActiveOnMonitor); - Assert.False(addedWorkspaceModel.ActiveOnMonitor); + WorkspaceWidgetViewModel sut = new(ctx, monitor); // When - context.Butler.MonitorWorkspaceChanged += Raise.Event>( - context.WorkspaceManager, - new MonitorWorkspaceChangedEventArgs() - { - Monitor = otherMonitor, - PreviousWorkspace = existingModel.Workspace, - CurrentWorkspace = addedWorkspaceModel.Workspace, - } + root.MapSector.QueueEvent( + new MonitorWorkspaceChangedEventArgs() { Monitor = monitor, CurrentWorkspace = workspace2 } ); + root.DispatchEvents(); // Then - Assert.True(existingModel.ActiveOnMonitor); - Assert.False(addedWorkspaceModel.ActiveOnMonitor); + Assert.Equal(2, sut.Workspaces.Count); + Assert.Same(workspace1, sut.Workspaces[0].Workspace); + Assert.Same(workspace2, sut.Workspaces[1].Workspace); + Assert.False(sut.Workspaces[0].ActiveOnMonitor); + Assert.True(sut.Workspaces[1].ActiveOnMonitor); } - #endregion - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceRenamed_ExistingWorkspace(IContext context, IMonitor monitor) + [Theory, AutoSubstituteData] + internal void WorkspaceRenamed_WrongMonitor(IContext ctx, MutableRootSector root) { // Given - IWorkspace workspace = context.WorkspaceManager.First(); - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor1 = StoreTestUtils.CreateMonitor((HMONITOR)100); + IMonitor monitor2 = StoreTestUtils.CreateMonitor((HMONITOR)200); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); + + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor1, workspace1); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor2, workspace2); + + WorkspaceWidgetViewModel sut = new(ctx, monitor1); + WorkspaceModel workspaceModel = sut.Workspaces[0]; // When - // Then - Assert.PropertyChanged( - viewModel.Workspaces[0], - nameof(WorkspaceModel.Name), + CustomAssert.DoesNotPropertyChange( + h => workspaceModel.PropertyChanged += h, + h => workspaceModel.PropertyChanged -= h, () => { - context.WorkspaceManager.WorkspaceRenamed += Raise.Event>( - context.WorkspaceManager, - new WorkspaceRenamedEventArgs() { Workspace = workspace, PreviousName = "Old Name" } + root.WorkspaceSector.QueueEvent( + new WorkspaceRenamedEventArgs() { Workspace = workspace2, PreviousName = "Old Name" } ); + root.DispatchEvents(); } ); } - [Theory, AutoSubstituteData] - public void WorkspaceManager_WorkspaceRenamed_NonExistingWorkspace( - IContext context, - IMonitor monitor, - IWorkspace renamedWorkspace - ) + [Theory, AutoSubstituteData] + internal void WorkspaceRenamed_CorrectMonitor(IContext ctx, MutableRootSector root) { // Given - WorkspaceWidgetViewModel viewModel = new(context, monitor); + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)100); + Workspace workspace1 = StoreTestUtils.CreateWorkspace(ctx); + Workspace workspace2 = StoreTestUtils.CreateWorkspace(ctx); - // Verify that property changed is not raised + StoreTestUtils.AddWorkspacesToManager(ctx, root, workspace1, workspace2); + StoreTestUtils.PopulateMonitorWorkspaceMap(ctx, root, monitor, workspace1); - bool propertyChangedRaised = false; - WorkspaceModel model = viewModel.Workspaces[0]; + WorkspaceWidgetViewModel sut = new(ctx, monitor); + WorkspaceModel workspaceModel = sut.Workspaces[0]; // When - model.PropertyChanged += (sender, args) => propertyChangedRaised = true; - - context.WorkspaceManager.WorkspaceRenamed += Raise.Event>( - context.WorkspaceManager, - new WorkspaceRenamedEventArgs() { Workspace = renamedWorkspace, PreviousName = "Old Name" } + Assert.PropertyChanged( + sut.Workspaces[0], + nameof(workspaceModel.Name), + () => + { + root.WorkspaceSector.QueueEvent( + new WorkspaceRenamedEventArgs() { Workspace = workspace1, PreviousName = "Old Name" } + ); + root.DispatchEvents(); + } ); - - // Then - Assert.False(propertyChangedRaised); } - [Theory, AutoSubstituteData] - [System.Diagnostics.CodeAnalysis.SuppressMessage( - "Usage", - "NS5000:Received check.", - Justification = "The analyzer is wrong" - )] - public void Dispose(IContext context, IMonitor monitor) + [Theory, AutoSubstituteData] + public void Dispose(IContext ctx, IMonitor monitor) { // Given - WorkspaceWidgetViewModel viewModel = new(context, monitor); + WorkspaceWidgetViewModel sut = new(ctx, monitor); // When - viewModel.Dispose(); + sut.Dispose(); // Then - context.WorkspaceManager.Received(1).WorkspaceAdded -= Arg.Any>(); - context.WorkspaceManager.Received(1).WorkspaceRemoved -= Arg.Any>(); - context.Butler.Received(1).MonitorWorkspaceChanged -= Arg.Any>(); - context.WorkspaceManager.Received(1).WorkspaceRenamed -= Arg.Any>(); + ctx.Store.WorkspaceEvents.Received(1).WorkspaceAdded += Arg.Any>(); + ctx.Store.WorkspaceEvents.Received(1).WorkspaceRemoved += Arg.Any>(); + ctx.Store.MapEvents.Received(1).MonitorWorkspaceChanged += Arg.Any< + EventHandler + >(); + ctx.Store.WorkspaceEvents.Received(1).WorkspaceRenamed += Arg.Any>(); + + ctx.Store.WorkspaceEvents.Received(1).WorkspaceAdded -= Arg.Any>(); + ctx.Store.WorkspaceEvents.Received(1).WorkspaceRemoved -= Arg.Any>(); + ctx.Store.MapEvents.Received(1).MonitorWorkspaceChanged -= Arg.Any< + EventHandler + >(); + ctx.Store.WorkspaceEvents.Received(1).WorkspaceRenamed -= Arg.Any>(); } } diff --git a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs index 2f459f327..512874f63 100644 --- a/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs +++ b/src/Whim.Bar/Workspace/WorkspaceWidgetViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace Whim.Bar; From 9dae2948cd255889aca1560933de1195e41169b7 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 16:49:56 +1100 Subject: [PATCH 4/6] Docs --- docs/configure/plugins/bar.md | 5 ++++- docs/script/plugins/bar.md | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/configure/plugins/bar.md b/docs/configure/plugins/bar.md index 9e4b3c84c..245d5809d 100644 --- a/docs/configure/plugins/bar.md +++ b/docs/configure/plugins/bar.md @@ -79,7 +79,10 @@ The `FocusedWindowWidget` displays the title of the focused window. ### Workspace Widget -The `WorkspaceWidget` displays the name of the current workspace. +The `WorkspaceWidget` displays: + +- the active workspace on the bar's monitor +- the workspaces which can be opened on the monitor - i.e., [sticky workspaces for the monitor](../core/workspaces.md#sticky-workspaces) and non-sticky workspaces ### Tree Layout Widget diff --git a/docs/script/plugins/bar.md b/docs/script/plugins/bar.md index da21ec30b..96b90282b 100644 --- a/docs/script/plugins/bar.md +++ b/docs/script/plugins/bar.md @@ -34,6 +34,8 @@ List rightComponents = new() - [FocusedWindowWidget]() - [WorkspaceWidget](xref:Whim.Bar.WorkspaceWidget.CreateComponent) +More information about each widget can be found [here](../../configure/plugins/bar.md#widgets). + ## Example Config ```csharp From 18627424c6865b46ca438de38bc6f213116c300e Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 17:09:16 +1100 Subject: [PATCH 5/6] PickStickyWorkspacesByMonitor tests --- .../Store/MapSector/MapPickersTests.cs | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs index adf40cadb..f8ee95055 100644 --- a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs +++ b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs @@ -552,3 +552,127 @@ internal void WorkspaceDoesNotExist(IContext ctx) Assert.Contains("not found", result.Error!.Message); } } + +public class PickStickyWorkspacesByMonitorTests +{ + [Theory, AutoSubstituteData] + internal void MonitorNotFound(IContext ctx) + { + // Given we have a monitor that doesn't exist + HMONITOR nonExistentHandle = (HMONITOR)999; + + // When we try to get workspaces for the monitor + var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(nonExistentHandle)); + + // Then we get an error + Assert.False(result.IsSuccessful); + Assert.Contains("not found", result.Error!.Message); + } + + [Theory, AutoSubstituteData] + internal void GetStickyWorkspaces(IContext ctx, MutableRootSector root) + { + // Given we have three workspaces, two sticky to monitor 1 + var workspace1 = CreateWorkspace(ctx); + var workspace2 = CreateWorkspace(ctx); + var workspace3 = CreateWorkspace(ctx); + AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3); + + IMonitor monitor = CreateMonitor((HMONITOR)1); + AddMonitorsToManager(ctx, root, monitor); + + root.MapSector.StickyWorkspaceMonitorIndexMap = root + .MapSector.StickyWorkspaceMonitorIndexMap.SetItem(workspace1.Id, [0]) + .SetItem(workspace2.Id, [0]); + + // When we get the workspaces for the monitor + var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle)); + + // Then we get the sticky workspaces in workspace order + Assert.True(result.IsSuccessful); + Assert.Equal(3, result.Value.Count); // All workspaces (2 sticky + 1 non-sticky) + Assert.Equal(workspace1, result.Value[0]); // First in workspace order + Assert.Equal(workspace2, result.Value[1]); // Second in workspace order + Assert.Equal(workspace3, result.Value[2]); // Non-sticky workspace + } + + [Theory, AutoSubstituteData] + internal void GetAllWorkspacesWhenNoneSticky(IContext ctx, MutableRootSector root) + { + // Given we have three workspaces but none are sticky + var workspace1 = CreateWorkspace(ctx); + var workspace2 = CreateWorkspace(ctx); + var workspace3 = CreateWorkspace(ctx); + AddWorkspacesToManager(ctx, root, workspace1, workspace2, workspace3); + + IMonitor monitor = CreateMonitor((HMONITOR)1); + AddMonitorsToManager(ctx, root, monitor); + + // When we get the workspaces for the monitor + var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle)); + + // Then we get all workspaces in workspace order + Assert.True(result.IsSuccessful); + Assert.Equal(3, result.Value.Count); + Assert.Equal(workspace1, result.Value[0]); + Assert.Equal(workspace2, result.Value[1]); + Assert.Equal(workspace3, result.Value[2]); + } + + [Theory, AutoSubstituteData] + internal void OrphanedWorkspacesCanGoOnAnyMonitor(IContext ctx, MutableRootSector root) + { + // Given we have a workspace sticky to a non-existent monitor index + var workspace = CreateWorkspace(ctx); + AddWorkspacesToManager(ctx, root, workspace); + + IMonitor monitor = CreateMonitor((HMONITOR)1); + AddMonitorsToManager(ctx, root, monitor); + + root.MapSector.StickyWorkspaceMonitorIndexMap = root.MapSector.StickyWorkspaceMonitorIndexMap.SetItem( + workspace.Id, + [99] + ); // Non-existent monitor index + + // When we get the workspaces for the monitor + var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle)); + + // Then the orphaned workspace is included + Assert.True(result.IsSuccessful); + Assert.Single(result.Value); + Assert.Equal(workspace, result.Value[0]); + } + + [Theory, AutoSubstituteData] + internal void InvalidMonitorIndicesAreIgnored(IContext ctx, MutableRootSector root) + { + // Given we have a workspace with both valid and invalid monitor indices + var workspace = CreateWorkspace(ctx); + AddWorkspacesToManager(ctx, root, workspace); + + IMonitor monitor1 = CreateMonitor((HMONITOR)1); + IMonitor monitor2 = CreateMonitor((HMONITOR)2); + AddMonitorsToManager(ctx, root, monitor1, monitor2); // Only indices 0 and 1 are valid + + root.MapSector.StickyWorkspaceMonitorIndexMap = root.MapSector.StickyWorkspaceMonitorIndexMap.SetItem( + workspace.Id, + [-1, 0, 1, 2] // Invalid indices: -1 and 2 + ); + + // When we get the workspaces for monitor1 (index 0) + var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor1.Handle)); + + // Then the workspace is included because index 0 is valid, ignoring invalid indices + Assert.True(result.IsSuccessful); + Assert.Single(result.Value); + Assert.Equal(workspace, result.Value[0]); + + // And when we get workspaces for monitor2 (index 1) + result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor2.Handle)); + + // Then the workspace is included because index 1 is valid, ignoring invalid indices + Assert.True(result.IsSuccessful); + Assert.Single(result.Value); + Assert.Equal(workspace, result.Value[0]); + } +} From 40cb4f57dfb4dcb9c65fba1d6724a3d87073c903 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 10 Nov 2024 17:12:59 +1100 Subject: [PATCH 6/6] Remove unnecessary test --- .../Store/MapSector/MapPickersTests.cs | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs index f8ee95055..ccef7cb0d 100644 --- a/src/Whim.Tests/Store/MapSector/MapPickersTests.cs +++ b/src/Whim.Tests/Store/MapSector/MapPickersTests.cs @@ -582,8 +582,8 @@ internal void GetStickyWorkspaces(IContext ctx, MutableRootSector root) AddMonitorsToManager(ctx, root, monitor); root.MapSector.StickyWorkspaceMonitorIndexMap = root - .MapSector.StickyWorkspaceMonitorIndexMap.SetItem(workspace1.Id, [0]) - .SetItem(workspace2.Id, [0]); + .MapSector.StickyWorkspaceMonitorIndexMap.SetItem(workspace1.Id, [0, 1]) + .SetItem(workspace2.Id, [0, 4]); // When we get the workspaces for the monitor var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle)); @@ -619,30 +619,6 @@ internal void GetAllWorkspacesWhenNoneSticky(IContext ctx, MutableRootSector roo Assert.Equal(workspace3, result.Value[2]); } - [Theory, AutoSubstituteData] - internal void OrphanedWorkspacesCanGoOnAnyMonitor(IContext ctx, MutableRootSector root) - { - // Given we have a workspace sticky to a non-existent monitor index - var workspace = CreateWorkspace(ctx); - AddWorkspacesToManager(ctx, root, workspace); - - IMonitor monitor = CreateMonitor((HMONITOR)1); - AddMonitorsToManager(ctx, root, monitor); - - root.MapSector.StickyWorkspaceMonitorIndexMap = root.MapSector.StickyWorkspaceMonitorIndexMap.SetItem( - workspace.Id, - [99] - ); // Non-existent monitor index - - // When we get the workspaces for the monitor - var result = ctx.Store.Pick(Pickers.PickStickyWorkspacesByMonitor(monitor.Handle)); - - // Then the orphaned workspace is included - Assert.True(result.IsSuccessful); - Assert.Single(result.Value); - Assert.Equal(workspace, result.Value[0]); - } - [Theory, AutoSubstituteData] internal void InvalidMonitorIndicesAreIgnored(IContext ctx, MutableRootSector root) {