From fe8e309a50b67a3f091026103e5b511cf80f6ea7 Mon Sep 17 00:00:00 2001 From: Isaac Daly Date: Sun, 3 Nov 2024 16:46:12 +1100 Subject: [PATCH] Prevent gaps-triggered shifting for `FloatingLayoutEngine` (#1069) There was special handling for the `ProxyFloatingLayoutEngine`, but `FloatingLayoutEngine` was not included. As a result, windows could shift while using the `FloatingLayoutEngine`. This PR fixes this. --- src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs | 80 +++++++++++++++++++- src/Whim.Gaps.Tests/Whim.Gaps.Tests.csproj | 1 + src/Whim.Gaps/GapsLayoutEngine.cs | 15 +++- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs b/src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs index 5d18433ea..b02fcfa6d 100644 --- a/src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs +++ b/src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs @@ -1,6 +1,7 @@ using FluentAssertions; using NSubstitute; using Whim.FloatingWindow; +using Whim.SliceLayout; using Whim.TestUtils; using Windows.Win32.Foundation; using Windows.Win32.Graphics.Gdi; @@ -99,11 +100,18 @@ public static TheoryData DoLayout_Da } [Theory] - [MemberData(nameof(DoLayout_Data))] - public void DoLayout(GapsConfig gapsConfig, IWindow[] windows, int scale, IWindowState[] expectedWindowStates) + [MemberAutoSubstituteData(nameof(DoLayout_Data))] + public void DoLayout( + GapsConfig gapsConfig, + IWindow[] windows, + int scale, + IWindowState[] expectedWindowStates, + ISliceLayoutPlugin sliceLayoutPlugin, + IContext ctx + ) { // Given - ILayoutEngine innerLayoutEngine = new ColumnLayoutEngine(_identity); + ILayoutEngine innerLayoutEngine = SliceLayouts.CreateRowLayout(ctx, sliceLayoutPlugin, _identity); foreach (IWindow w in windows) { @@ -405,7 +413,7 @@ IWindowState[] expectedWindowStates [Theory, AutoSubstituteData] [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] - internal void DoLayout_WithFloatingLayoutEngine(IContext context, MutableRootSector root) + internal void DoLayout_WithProxyFloatingLayoutEngine(IContext context, MutableRootSector root) { // Input GapsConfig gapsConfig = new(); @@ -506,6 +514,70 @@ internal void DoLayout_WithFloatingLayoutEngine(IContext context, MutableRootSec ); } + [Theory, AutoSubstituteData] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope")] + internal void DoLayout_WithFloatingLayoutEngine(IContext context, MutableRootSector root) + { + // Input + GapsConfig gapsConfig = new(); + + Rectangle rect1 = new(10, 10, 20, 20); + Rectangle rect2 = new(30, 30, 40, 40); + + IMonitor monitor = StoreTestUtils.CreateMonitor((HMONITOR)1); + monitor.WorkingArea.Returns(new Rectangle(0, 0, 100, 100)); + + IWindow window1 = StoreTestUtils.CreateWindow((HWND)1); + IWindow window2 = StoreTestUtils.CreateWindow((HWND)2); + + Workspace workspace = StoreTestUtils.CreateWorkspace(context); + StoreTestUtils.PopulateThreeWayMap(context, root, monitor, workspace, window1); + StoreTestUtils.PopulateWindowWorkspaceMap(context, root, window2, workspace); + + context.NativeManager.DwmGetWindowRectangle(window1.Handle).Returns(rect1); + context.NativeManager.DwmGetWindowRectangle(window2.Handle).Returns(rect2); + + FloatingLayoutEngine floatingLayoutEngine = new(context, _identity); + + // Given + GapsLayoutEngine gapsLayoutEngine = new(gapsConfig, floatingLayoutEngine); + + // When + GapsLayoutEngine gaps1 = (GapsLayoutEngine)gapsLayoutEngine.AddWindow(window1); + GapsLayoutEngine gaps2 = (GapsLayoutEngine)gaps1.AddWindow(window2); + + IWindowState[] outputWindowStates = gaps2.DoLayout(monitor.WorkingArea, monitor).ToArray(); + + // Then + Assert.Equal(2, outputWindowStates.Length); + + Assert.Contains( + outputWindowStates, + ws => + ws.Equals( + new WindowState() + { + Window = window1, + Rectangle = rect1, + WindowSize = WindowSize.Normal, + } + ) + ); + + Assert.Contains( + outputWindowStates, + ws => + ws.Equals( + new WindowState() + { + Window = window2, + Rectangle = rect2, + WindowSize = WindowSize.Normal, + } + ) + ); + } + [Theory, AutoSubstituteData] public void Count(ILayoutEngine innerLayoutEngine) { diff --git a/src/Whim.Gaps.Tests/Whim.Gaps.Tests.csproj b/src/Whim.Gaps.Tests/Whim.Gaps.Tests.csproj index 96c55dc7d..1926fca54 100644 --- a/src/Whim.Gaps.Tests/Whim.Gaps.Tests.csproj +++ b/src/Whim.Gaps.Tests/Whim.Gaps.Tests.csproj @@ -44,6 +44,7 @@ + \ No newline at end of file diff --git a/src/Whim.Gaps/GapsLayoutEngine.cs b/src/Whim.Gaps/GapsLayoutEngine.cs index 8ae723a75..837655601 100644 --- a/src/Whim.Gaps/GapsLayoutEngine.cs +++ b/src/Whim.Gaps/GapsLayoutEngine.cs @@ -43,8 +43,19 @@ private GapsLayoutEngine UpdateInner(ILayoutEngine newInnerLayoutEngine) => /// public override IEnumerable DoLayout(IRectangle rectangle, IMonitor monitor) { + // If the inner layout engine is a floating layout engine, then we don't apply gaps. + if (InnerLayoutEngine.GetLayoutEngine() is not null) + { + foreach (IWindowState windowState in InnerLayoutEngine.DoLayout(rectangle, monitor)) + { + yield return windowState; + } + yield break; + } + + // If the inner layout engine is a proxy floating layout engine, then we apply gaps for the non-floating windows. int nonProxiedCount = 0; - if (InnerLayoutEngine is ProxyFloatingLayoutEngine proxy) + if (InnerLayoutEngine.GetLayoutEngine() is ProxyFloatingLayoutEngine proxy) { nonProxiedCount = proxy.FloatingWindowRects.Count + proxy.MinimizedWindowRects.Count; @@ -52,7 +63,7 @@ public override IEnumerable DoLayout(IRectangle rectangle, IM // The InnerLayoutEngine will use the proxied rectangle for the remaining windows. // This is brittle and relies on the order of the windows in the ProxyFloatingLayoutEngine. - IEnumerable windows = InnerLayoutEngine.DoLayout(rectangle, monitor); + IEnumerable windows = proxy.DoLayout(rectangle, monitor); using IEnumerator enumerator = windows.GetEnumerator(); for (int i = 0; i < nonProxiedCount; i++)