Skip to content

Commit

Permalink
Always hide layout preview when missing Windows events (#963)
Browse files Browse the repository at this point in the history
Previously, the layout preview would sometimes fail to hide in certain scenarios (see below). This has been resolved by:

- Always hiding the preview whenever a window gains focus
- Hiding the preview whenever the preview is clicked

Additionally, `*Manager` references have been replaced by `*Store` references.

<details>
<summary>Bug description</summary>
1. Double click to maximize window
2. Click and drag maximized window to a position
3. Double click to maximize window again
4. Double click to restore window position

Expected behavior: Layout preview hides

Actual behavior: Layout preview is still visible
</details>
  • Loading branch information
dalyIsaac authored Aug 3, 2024
1 parent 54813b0 commit 2b0ec13
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 165 deletions.
195 changes: 119 additions & 76 deletions src/Whim.LayoutPreview.Tests/LayoutPreviewPluginTests.cs
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using AutoFixture;
using NSubstitute;
using Whim.FloatingLayout;
using Whim.TestUtils;
using Windows.Win32.Graphics.Gdi;
using Xunit;

namespace Whim.LayoutPreview.Tests;

public class LayoutPreviewPluginCustomization : ICustomization
public class LayoutPreviewPluginCustomization : StoreCustomization
{
public void Customize(IFixture fixture)
[SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")]
protected override void PostCustomize(IFixture fixture)
{
IContext ctx = fixture.Freeze<IContext>();

IWorkspace workspace = fixture.Freeze<IWorkspace>();

IMonitor monitor = fixture.Freeze<IMonitor>();
monitor.WorkingArea.Returns(
new Rectangle<int>()
{
X = 0,
Y = 0,
Width = 1920,
Height = 1080
}
Workspace workspace = StoreTestUtils.CreateWorkspace(_ctx);
fixture.Inject(workspace);

IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1);
fixture.Inject(monitor);

StoreTestUtils.SetupMonitorAtPoint(
_ctx,
_internalCtx,
_store._root.MutableRootSector,
new Point<int>(0, 0),
monitor
);

ctx.MonitorManager.GetMonitorAtPoint(Arg.Any<IPoint<int>>()).Returns(monitor);
ctx.Butler.Pantry.GetWorkspaceForMonitor(Arg.Any<IMonitor>()).Returns(workspace);
StoreTestUtils.PopulateMonitorWorkspaceMap(_ctx, _store._root.MutableRootSector, monitor, workspace);
}
}

public class LayoutPreviewPluginTests
{
[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void Name(IContext ctx)
internal void Name(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -47,7 +48,7 @@ public void Name(IContext ctx)
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void PluginCommands(IContext ctx)
internal void PluginCommands(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -59,9 +60,9 @@ public void PluginCommands(IContext ctx)
Assert.Empty(commands);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
[Theory, AutoSubstituteData]
[SuppressMessage("Usage", "NS5000:Received check.")]
public void PreInitialize(IContext ctx)
internal void PreInitialize(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -70,15 +71,15 @@ public void PreInitialize(IContext ctx)
plugin.PreInitialize();

// Then
ctx.WindowManager.Received(1).WindowMoveStart += Arg.Any<EventHandler<WindowMoveStartedEventArgs>>();
ctx.WindowManager.Received(1).WindowMoved += Arg.Any<EventHandler<WindowMovedEventArgs>>();
ctx.WindowManager.Received(1).WindowMoveEnd += Arg.Any<EventHandler<WindowMoveEndedEventArgs>>();
ctx.WindowManager.Received(1).WindowRemoved += Arg.Any<EventHandler<WindowRemovedEventArgs>>();
ctx.Store.WindowEvents.Received(1).WindowMoveStarted += Arg.Any<EventHandler<WindowMoveStartedEventArgs>>();
ctx.Store.WindowEvents.Received(1).WindowMoved += Arg.Any<EventHandler<WindowMovedEventArgs>>();
ctx.Store.WindowEvents.Received(1).WindowMoveEnded += Arg.Any<EventHandler<WindowMoveEndedEventArgs>>();
ctx.Store.WindowEvents.Received(1).WindowRemoved += Arg.Any<EventHandler<WindowRemovedEventArgs>>();
ctx.FilterManager.Received(1).AddTitleMatchFilter(LayoutPreviewWindow.WindowTitle);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void PostInitialize(IContext ctx)
internal void PostInitialize(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -91,7 +92,7 @@ public void PostInitialize(IContext ctx)
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void LoadState(IContext ctx)
internal void LoadState(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -104,7 +105,7 @@ public void LoadState(IContext ctx)
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void SaveState(IContext ctx)
internal void SaveState(IContext ctx)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -117,7 +118,7 @@ public void SaveState(IContext ctx)
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoveStart_NotDragged(IContext ctx)
internal void WindowMoveStarted_NotDragged(IContext ctx, MutableRootSector rootSector)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -131,18 +132,16 @@ public void WindowMoveStart_NotDragged(IContext ctx)

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoveStart += Raise.Event<EventHandler<WindowMoveStartedEventArgs>>(
ctx.WindowManager,
e
);
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
Assert.Null(plugin.DraggedWindow);
}

#region WindowMoved
[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoved_NotDragged(IContext ctx)
internal void WindowMoved_NotDragged(IContext ctx, MutableRootSector rootSector)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -156,15 +155,16 @@ public void WindowMoved_NotDragged(IContext ctx)

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, e);
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
ctx.MonitorManager.DidNotReceive().GetMonitorAtPoint(Arg.Any<IPoint<int>>());
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoved_MovingEdges(IContext ctx)
internal void WindowMoved_MovingEdges(IContext ctx, MutableRootSector rootSector)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -178,15 +178,20 @@ public void WindowMoved_MovingEdges(IContext ctx)

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, e);
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
ctx.MonitorManager.DidNotReceive().GetMonitorAtPoint(Arg.Any<IPoint<int>>());
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoved_Dragged_CannotFindWorkspace(IContext ctx, IWorkspace workspace)
internal void WindowMoved_Dragged_CannotFindWorkspace(
IContext ctx,
MutableRootSector rootSector,
IWorkspace workspace
)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -197,23 +202,65 @@ public void WindowMoved_Dragged_CannotFindWorkspace(IContext ctx, IWorkspace wor
CursorDraggedPoint = new Rectangle<int>(),
MovedEdges = null
};
ctx.Butler.Pantry.GetWorkspaceForMonitor(Arg.Any<IMonitor>()).Returns((IWorkspace?)null);
rootSector.MapSector.MonitorWorkspaceMap = rootSector.MapSector.MonitorWorkspaceMap.Clear();

workspace.ActiveLayoutEngine.ClearReceivedCalls();

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, e);
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
ctx.MonitorManager.Received(1).GetMonitorAtPoint(Arg.Any<IPoint<int>>());
ctx.Butler.Pantry.Received(1).GetWorkspaceForMonitor(Arg.Any<IMonitor>());
Assert.Empty(workspace.ActiveLayoutEngine.ReceivedCalls());
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoved_Dragged_Success(IContext ctx, IWindow window, IWorkspace workspace)
internal void WindowMoved_Dragged_IgnoreFloatingLayoutEngine(
IContext ctx,
MutableRootSector rootSector,
IWindow window,
Workspace workspace,
IMonitor monitor
)
{
// Given
workspace = workspace with
{
LayoutEngines = ImmutableList<ILayoutEngine>.Empty.Add(
new FreeLayoutEngine(ctx, new LayoutEngineIdentity())
),
ActiveLayoutEngineIndex = 0
};
rootSector.WorkspaceSector.Workspaces = rootSector.WorkspaceSector.Workspaces.SetItem(workspace.Id, workspace);
ctx.MonitorManager.GetMonitorAtPoint(Arg.Any<IPoint<int>>()).Returns(monitor);

using LayoutPreviewPlugin plugin = new(ctx);
WindowMovedEventArgs e =
new()
{
Window = window,
CursorDraggedPoint = new Rectangle<int>(),
MovedEdges = null
};

// When
plugin.PreInitialize();
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
internal void WindowMoved_Dragged_Success(
IContext ctx,
MutableRootSector rootSector,
IWindow window,
Workspace workspace
)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -227,20 +274,20 @@ public void WindowMoved_Dragged_Success(IContext ctx, IWindow window, IWorkspace

workspace.ActiveLayoutEngine.ClearReceivedCalls();

rootSector.WindowSector.QueueEvent(e);

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, e);
rootSector.DispatchEvents();

// Then
ctx.MonitorManager.Received(1).GetMonitorAtPoint(Arg.Any<IPoint<int>>());
ctx.Butler.Pantry.Received(1).GetWorkspaceForMonitor(Arg.Any<IMonitor>());
Assert.Single(workspace.ActiveLayoutEngine.ReceivedCalls());
Assert.Equal(window, plugin.DraggedWindow);
}
#endregion

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowMoveEnd(IContext ctx)
internal void WindowMoveEnded(IContext ctx, MutableRootSector rootSector)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -254,42 +301,43 @@ public void WindowMoveEnd(IContext ctx)

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoveEnd += Raise.Event<EventHandler<WindowMoveEndedEventArgs>>(ctx.WindowManager, e);
rootSector.WindowSector.QueueEvent(e);
rootSector.DispatchEvents();

// Then
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowManager_WindowRemoved_NotHidden(IContext ctx, IWindow movedWindow, IWindow removedWindow)
internal void WindowEvents_WindowRemoved_NotHidden(
IContext ctx,
MutableRootSector rootSector,
IWindow movedWindow,
IWindow removedWindow
)
{
// Given
// Given WindowMoved and WindowRemoved events are queued
using LayoutPreviewPlugin plugin = new(ctx);

WindowMovedEventArgs moveArgs =
new()
rootSector.WindowSector.QueueEvent(
new WindowMovedEventArgs()
{
Window = movedWindow,
CursorDraggedPoint = new Rectangle<int>(),
MovedEdges = null
};

WindowEventArgs removeArgs = new WindowRemovedEventArgs() { Window = removedWindow };
}
);
rootSector.WindowSector.QueueEvent(new WindowRemovedEventArgs() { Window = removedWindow });

// When
// When the events are dispatched
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, moveArgs);
ctx.WindowManager.WindowRemoved += Raise.Event<EventHandler<WindowRemovedEventArgs>>(
ctx.WindowManager,
removeArgs
);
rootSector.DispatchEvents();

// Then
Assert.Equal(movedWindow, plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowManager_WindowRemoved_Shown(IContext ctx, IWindow movedWindow)
internal void WindowEvents_WindowRemoved_Hidden(IContext ctx, MutableRootSector rootSector, IWindow movedWindow)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -301,23 +349,20 @@ public void WindowManager_WindowRemoved_Shown(IContext ctx, IWindow movedWindow)
CursorDraggedPoint = new Rectangle<int>(),
MovedEdges = null
};

WindowEventArgs removeArgs = new WindowRemovedEventArgs() { Window = movedWindow };

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, moveArgs);
ctx.WindowManager.WindowRemoved += Raise.Event<EventHandler<WindowRemovedEventArgs>>(
ctx.WindowManager,
removeArgs
);
rootSector.WindowSector.QueueEvent(moveArgs);
rootSector.WindowSector.QueueEvent(removeArgs);
rootSector.DispatchEvents();

// Then
Assert.Null(plugin.DraggedWindow);
}

[Theory, AutoSubstituteData<LayoutPreviewPluginCustomization>]
public void WindowManager_WindowFocused(IContext ctx, IWindow movedWindow)
internal void WindowEvents_WindowFocused(IContext ctx, MutableRootSector rootSector, IWindow movedWindow)
{
// Given
using LayoutPreviewPlugin plugin = new(ctx);
Expand All @@ -334,11 +379,9 @@ public void WindowManager_WindowFocused(IContext ctx, IWindow movedWindow)

// When
plugin.PreInitialize();
ctx.WindowManager.WindowMoved += Raise.Event<EventHandler<WindowMovedEventArgs>>(ctx.WindowManager, moveArgs);
ctx.WindowManager.WindowFocused += Raise.Event<EventHandler<WindowFocusedEventArgs>>(
ctx.WindowManager,
focusArgs
);
rootSector.WindowSector.QueueEvent(moveArgs);
rootSector.WindowSector.QueueEvent(focusArgs);
rootSector.DispatchEvents();

// Then
Assert.Null(plugin.DraggedWindow);
Expand Down
Loading

0 comments on commit 2b0ec13

Please sign in to comment.