Skip to content

Commit

Permalink
Let ILayoutEngines perform custom actions (#736)
Browse files Browse the repository at this point in the history
This PR adds the ability for layout engines to perform non-standard actions, and still have their resulting layouts be saved to `Workspace` instances. This was needed for #634, where the `SliceLayoutPlugin` tries to perform operations on the `SliceLayoutEngine`, but was unable to save the resulting `ILayoutEngine` instance to the `Workspace`.

The `PerformCustomAction` method can be used by `ILayoutEngine` implementations to perform non-standard actions.
  • Loading branch information
dalyIsaac authored Dec 22, 2023
1 parent ca758ae commit 18bbcb1
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 5 deletions.
42 changes: 42 additions & 0 deletions src/Whim.Bar.Tests/BarLayoutEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,46 @@ public void DoLayout_HeightMustBeNonNegative(int height, ILayoutEngine innerLayo
// Then
innerLayoutEngine.Received(1).DoLayout(Arg.Is<Rectangle<int>>(l => l.Height == 0), monitor);
}

[Theory, AutoSubstituteData]
public void PerformCustomAction_NotSame(ILayoutEngine innerLayoutEngine, ILayoutEngine performCustomActionResult)
{
// Given
BarLayoutEngine engine = CreateSut(innerLayoutEngine);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = null
};
innerLayoutEngine.PerformCustomAction(action).Returns(performCustomActionResult);

// When
ILayoutEngine newEngine = engine.PerformCustomAction(action);

// Then
Assert.NotSame(engine, newEngine);
}

[Theory, AutoSubstituteData]
public void PerformCustomAction_Same(ILayoutEngine innerLayoutEngine)
{
// Given
BarLayoutEngine engine = CreateSut(innerLayoutEngine);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = null
};
innerLayoutEngine.PerformCustomAction(action).Returns(innerLayoutEngine);

// When
ILayoutEngine newEngine = engine.PerformCustomAction(action);

// Then
Assert.Same(engine, newEngine);
}
}
4 changes: 4 additions & 0 deletions src/Whim.Bar/BarLayoutEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,8 @@ public override ILayoutEngine MoveWindowToPoint(IWindow window, IPoint<double> p
/// <inheritdoc />
public override ILayoutEngine SwapWindowInDirection(Direction direction, IWindow window) =>
UpdateInner(InnerLayoutEngine.SwapWindowInDirection(direction, window));

/// <inheritdoc />
public override ILayoutEngine PerformCustomAction<T>(LayoutEngineCustomAction<T> action) =>
UpdateInner(InnerLayoutEngine.PerformCustomAction(action));
}
81 changes: 81 additions & 0 deletions src/Whim.FloatingLayout.Tests/FloatingLayoutEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,4 +1090,85 @@ ILayoutEngine newInnerLayoutEngine
newInnerLayoutEngine.Received(1).SwapWindowInDirection(Direction.Left, window);
}
#endregion

#region PerformCustomAction
[Theory, AutoSubstituteData<FloatingLayoutEngineCustomization>]
internal void PerformCustomAction_UseInner(
IContext context,
IInternalFloatingLayoutPlugin plugin,
ILayoutEngine innerLayoutEngine
)
{
// Given
FloatingLayoutEngine engine = new(context, plugin, innerLayoutEngine);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = null
};

// When
ILayoutEngine newEngine = engine.PerformCustomAction(action);

// Then
Assert.NotSame(engine, newEngine);
}

[Theory, AutoSubstituteData<FloatingLayoutEngineCustomization>]
internal void PerformCustomAction_UseInner_WindowIsDefined(
IContext context,
IInternalFloatingLayoutPlugin plugin,
ILayoutEngine innerLayoutEngine
)
{
// Given
FloatingLayoutEngine engine = new(context, plugin, innerLayoutEngine);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = Substitute.For<IWindow>()
};
innerLayoutEngine.PerformCustomAction(action).Returns(innerLayoutEngine);

// When
ILayoutEngine newEngine = engine.PerformCustomAction(action);

// Then
Assert.NotSame(engine, newEngine);
innerLayoutEngine.Received(1).PerformCustomAction(action);
}

[Theory, AutoSubstituteData<FloatingLayoutEngineCustomization>]
internal void PerformCustomAction_FloatingWindow(
IContext context,
IInternalFloatingLayoutPlugin plugin,
ILayoutEngine innerLayoutEngine,
IWindow window
)
{
// Given
FloatingLayoutEngine engine = new(context, plugin, innerLayoutEngine);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = window
};
MarkWindowAsFloating(plugin, window, innerLayoutEngine);
ILayoutEngine newEngine = engine.AddWindow(window);

// When
ILayoutEngine newEngine2 = newEngine.PerformCustomAction(action);

// Then
Assert.NotSame(engine, newEngine);
Assert.Same(newEngine, newEngine2);
innerLayoutEngine.DidNotReceive().PerformCustomAction(action);
}
#endregion
}
19 changes: 17 additions & 2 deletions src/Whim.FloatingLayout/FloatingLayoutEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ ImmutableDictionary<IWindow, IRectangle<double>> floatingWindowRects
/// couldn't get the window's rectangle).
/// </param>
/// <returns></returns>
private FloatingLayoutEngine UpdateInner(ILayoutEngine newInnerLayoutEngine, IWindow gcWindow)
private FloatingLayoutEngine UpdateInner(ILayoutEngine newInnerLayoutEngine, IWindow? gcWindow)
{
ImmutableDictionary<IWindow, IRectangle<double>> newFloatingWindowRects = _floatingWindowRects.Remove(gcWindow);
ImmutableDictionary<IWindow, IRectangle<double>> newFloatingWindowRects =
gcWindow != null ? _floatingWindowRects.Remove(gcWindow) : _floatingWindowRects;

return InnerLayoutEngine == newInnerLayoutEngine && _floatingWindowRects == newFloatingWindowRects
? this
Expand Down Expand Up @@ -245,4 +246,18 @@ public override ILayoutEngine SwapWindowInDirection(Direction direction, IWindow
/// <inheritdoc />
public override bool ContainsWindow(IWindow window) =>
_floatingWindowRects.ContainsKey(window) || InnerLayoutEngine.ContainsWindow(window);

/// <inheritdoc />
public override ILayoutEngine PerformCustomAction<T>(LayoutEngineCustomAction<T> action)
{
if (action.Window != null && IsWindowFloating(action.Window))
{
// At this stage, we don't have a way to get the window in a child layout engine at
// a given point.
// For now, we do nothing.
return this;
}

return InnerLayoutEngine.PerformCustomAction(action);
}
}
50 changes: 50 additions & 0 deletions src/Whim.Gaps.Tests/GapsLayoutEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -674,4 +674,54 @@ public void GetFirstWindow(IWindow window, ILayoutEngine innerLayoutEngine)
Assert.Same(window, firstWindow);
innerLayoutEngine.Received(1).GetFirstWindow();
}

[Theory, AutoSubstituteData]
public void PerformCustomAction_NotSame(ILayoutEngine innerLayoutEngine, ILayoutEngine performCustomActionResult)
{
// Given
GapsConfig gapsConfig = new() { OuterGap = 10, InnerGap = 5 };

LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = Substitute.For<IWindow>()
};
innerLayoutEngine.PerformCustomAction(action).Returns(performCustomActionResult);

GapsLayoutEngine gapsLayoutEngine = new(gapsConfig, innerLayoutEngine);

// When
ILayoutEngine newLayoutEngine = gapsLayoutEngine.PerformCustomAction(action);

// Then
Assert.NotSame(gapsLayoutEngine, newLayoutEngine);
innerLayoutEngine.Received(1).PerformCustomAction(action);
}

[Theory, AutoSubstituteData]
public void PerformCustomAction_Same(ILayoutEngine innerLayoutEngine)
{
// Given
GapsConfig gapsConfig = new() { OuterGap = 10, InnerGap = 5 };

LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = Substitute.For<IWindow>()
};
innerLayoutEngine.PerformCustomAction(action).Returns(innerLayoutEngine);

GapsLayoutEngine gapsLayoutEngine = new(gapsConfig, innerLayoutEngine);

// When
ILayoutEngine newLayoutEngine = gapsLayoutEngine.PerformCustomAction(action);

// Then
Assert.Same(gapsLayoutEngine, newLayoutEngine);
innerLayoutEngine.Received(1).PerformCustomAction(action);
}
}
4 changes: 4 additions & 0 deletions src/Whim.Gaps/GapsLayoutEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,8 @@ public override ILayoutEngine MoveWindowToPoint(IWindow window, IPoint<double> p
/// <inheritdoc />
public override ILayoutEngine SwapWindowInDirection(Direction direction, IWindow window) =>
UpdateInner(InnerLayoutEngine.SwapWindowInDirection(direction, window));

/// <inheritdoc />
public override ILayoutEngine PerformCustomAction<T>(LayoutEngineCustomAction<T> action) =>
UpdateInner(InnerLayoutEngine.PerformCustomAction(action));
}
21 changes: 19 additions & 2 deletions src/Whim.TestUtils/ProxyLayoutEngineBaseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace Whim.TestUtils;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
/// <summary>
/// Tests for <see cref="ILayoutEngine"/> implementations.
/// </summary>
Expand Down Expand Up @@ -181,5 +180,23 @@ public void MoveWindowEdgesInDirection_WindowIsNotPresent(ILayoutEngine inner, I

// Then it shouldn't throw.
}

[Theory, AutoSubstituteData]
public void PerformCustomAction_WindowIsPresent(ILayoutEngine inner, IWindow window1)
{
// Given
ILayoutEngine layoutEngine = CreateLayoutEngine(inner).AddWindow(window1);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = null
};

// When
layoutEngine.PerformCustomAction(action);

// Then it shouldn't throw.
}
}
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
4 changes: 4 additions & 0 deletions src/Whim.TestUtils/TestLayoutEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ public ILayoutEngine MoveWindowEdgesInDirection(Direction edges, IPoint<double>
/// <inheritdoc/>
public ILayoutEngine SwapWindowInDirection(Direction direction, IWindow window) =>
throw new NotImplementedException();

/// <inheritdoc/>
public ILayoutEngine PerformCustomAction<T>(LayoutEngineCustomAction<T> action) =>
throw new NotImplementedException();
}
20 changes: 20 additions & 0 deletions src/Whim.Tests/Layout/ColumnLayoutEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -873,4 +873,24 @@ public void AddWindowAtPoint_RightToLeft_Middle(IWindow window)
Assert.Equal(1, windows.FindIndex(w => w.Window == window));
}
#endregion

[Theory, AutoSubstituteData]
public void PerformCustomAction(IWindow window)
{
// Given
ILayoutEngine engine = new ColumnLayoutEngine(identity).AddWindow(window);
LayoutEngineCustomAction<string> action =
new()
{
Name = "Action",
Payload = "payload",
Window = Substitute.For<IWindow>()
};

// When
ILayoutEngine newEngine = engine.PerformCustomAction(action);

// Then
Assert.Same(engine, newEngine);
}
}
Loading

0 comments on commit 18bbcb1

Please sign in to comment.