Skip to content

Commit

Permalink
fix: flatten nested container when it only has one child remaining (#425
Browse files Browse the repository at this point in the history
)
  • Loading branch information
phisko authored Dec 6, 2023
1 parent 454bc05 commit 4c2b898
Show file tree
Hide file tree
Showing 26 changed files with 175 additions and 281 deletions.
19 changes: 19 additions & 0 deletions GlazeWM.Domain/Common/Enums/TilingDirection.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
using System;

namespace GlazeWM.Domain.Common.Enums
{
public enum TilingDirection
{
Vertical,
Horizontal,
}

public static class TilingDirectionExtensions
{
/// <summary>
/// Get the inverse of a given tiling direction.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static TilingDirection Inverse(this TilingDirection tilingDirection)
{
return tilingDirection switch
{
TilingDirection.Vertical => TilingDirection.Horizontal,
TilingDirection.Horizontal => TilingDirection.Vertical,
_ => throw new ArgumentOutOfRangeException(nameof(tilingDirection)),
};
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System;
using System.Linq;
using GlazeWM.Domain.Containers.Commands;
using GlazeWM.Infrastructure.Bussing;

namespace GlazeWM.Domain.Containers.CommandHandlers
{
internal sealed class AttachContainerHandler : ICommandHandler<AttachContainerCommand>
{
private readonly Bus _bus;
private readonly ContainerService _containerService;

public AttachContainerHandler(ContainerService containerService)
public AttachContainerHandler(Bus bus, ContainerService containerService)
{
_bus = bus;
_containerService = containerService;
}

Expand All @@ -22,13 +25,31 @@ public CommandResponse Handle(AttachContainerCommand command)
if (!childToAdd.IsDetached())
throw new Exception("Cannot attach an already attached container. This is a bug.");

childToAdd.Parent = targetParent;
targetParent.Children.Insert(targetIndex, childToAdd);
targetParent.ChildFocusOrder.Add(childToAdd);
targetParent.InsertChild(targetIndex, childToAdd);

if (childToAdd is IResizable)
ResizeAttachedContainer(childToAdd);

_containerService.ContainersToRedraw.Add(targetParent);

return CommandResponse.Ok;
}

public void ResizeAttachedContainer(Container attachedContainer)
{
var resizableSiblings = attachedContainer.SiblingsOfType<IResizable>();

if (!resizableSiblings.Any())
{
(attachedContainer as IResizable).SizePercentage = 1;
return;
}

var defaultPercent = 1.0 / (resizableSiblings.Count() + 1);

// Set initial size percentage to 0, and then size up the container to `defaultPercent`.
(attachedContainer as IResizable).SizePercentage = 0;
_bus.Invoke(new ResizeContainerCommand(attachedContainer, defaultPercent));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ private void ChangeWindowTilingDirection(
// the split container after the replacement.
_bus.Invoke(new ReplaceContainerCommand(splitContainer, parent, window.Index));

// The child window takes up the full size of its parent split container.
// Add the window as a child of the split container and ensure it takes up the full size
// of its parent split container.
splitContainer.InsertChild(0, window);
(window as IResizable).SizePercentage = 1;
_bus.Invoke(new DetachContainerCommand(window));
_bus.Invoke(new AttachContainerCommand(window, splitContainer));
}

private void ChangeWorkspaceTilingDirection(
Expand Down

This file was deleted.

58 changes: 48 additions & 10 deletions GlazeWM.Domain/Containers/CommandHandlers/DetachContainerHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using GlazeWM.Domain.Containers.Commands;
using GlazeWM.Domain.Workspaces;
using GlazeWM.Infrastructure.Bussing;
Expand All @@ -20,26 +21,63 @@ public CommandResponse Handle(DetachContainerCommand command)
{
var childToRemove = command.ChildToRemove;
var parent = childToRemove.Parent;
var grandparent = parent.Parent;
var siblings = childToRemove.Siblings;

if (parent == null)
throw new Exception("Cannot detach an already detached container. This is a bug.");

childToRemove.Parent = null;
parent.Children.Remove(childToRemove);
parent.ChildFocusOrder.Remove(childToRemove);
parent.RemoveChild(childToRemove);

var isEmptySplitContainer = parent is SplitContainer && !parent.HasChildren()
&& parent is not Workspace;
var parentSiblings = parent.Siblings;
var isEmptySplitContainer =
!parent.HasChildren() && parent is SplitContainer and not Workspace;

// If the parent of the removed child is an empty split container, detach the split container
// as well.
// Get the freed up space after container is detached.
var availableSizePercentage = isEmptySplitContainer
? (parent as IResizable).SizePercentage
: (childToRemove as IResizable)?.SizePercentage ?? 0;

// Resize children of grandparent if `childToRemove`'s parent is also to be detached.
var containersToResize = isEmptySplitContainer
? grandparent.ChildrenOfType<IResizable>()
: parent.ChildrenOfType<IResizable>();

// If the parent of the removed child is now an empty split container, detach the
// split container as well.
// TODO: Move out calls to `ContainersToRedraw.Add(...)`, since detaching might not
// always require a redraw.
if (isEmptySplitContainer)
{
_bus.Invoke(new DetachContainerCommand(parent));
return CommandResponse.Ok;
_containerService.ContainersToRedraw.Add(parent.Parent);
grandparent.RemoveChild(parent);
}
else
_containerService.ContainersToRedraw.Add(parent);

if (availableSizePercentage != 0)
{
var sizePercentageIncrement = availableSizePercentage / containersToResize.Count();

// Adjust `SizePercentage` of the siblings of the removed container.
foreach (var containerToResize in containersToResize)
((IResizable)containerToResize).SizePercentage += sizePercentageIncrement;
}

_containerService.ContainersToRedraw.Add(parent);
var detachedSiblings = isEmptySplitContainer ? parentSiblings : siblings;
var detachedParent = isEmptySplitContainer ? grandparent : parent;

// If there is exactly *one* sibling to the detached container, then flatten that
// sibling if it's a split container. This is to handle layouts like H[1 V[2 H[3]]],
// where container 2 gets detached.
if (detachedSiblings.Count() == 1 && detachedSiblings.ElementAt(0) is SplitContainer && childToRemove is not Workspace)
{
_bus.Invoke(
new FlattenSplitContainerCommand(detachedSiblings.ElementAt(0) as SplitContainer)
);

_bus.Invoke(new FlattenSplitContainerCommand(detachedParent as SplitContainer));
}

return CommandResponse.Ok;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using GlazeWM.Domain.Common.Enums;
using GlazeWM.Domain.Containers.Commands;
using GlazeWM.Infrastructure.Bussing;
using GlazeWM.Infrastructure.Utils;
Expand All @@ -7,45 +8,47 @@ namespace GlazeWM.Domain.Containers.CommandHandlers
{
internal sealed class FlattenSplitContainerHandler : ICommandHandler<FlattenSplitContainerCommand>
{
private readonly Bus _bus;
private readonly ContainerService _containerService;

public FlattenSplitContainerHandler(Bus bus, ContainerService containerService)
public FlattenSplitContainerHandler(ContainerService containerService)
{
_bus = bus;
_containerService = containerService;
}

public CommandResponse Handle(FlattenSplitContainerCommand command)
{
var containerToFlatten = command.ContainerToFlatten;
var splitContainer = command.ContainerToFlatten;

// Keep references to properties of container to flatten prior to detaching.
var originalParent = containerToFlatten.Parent;
var originalChildren = containerToFlatten.Children.ToList();
var originalFocusIndex = containerToFlatten.FocusIndex;
var originalIndex = containerToFlatten.Index;
var originalFocusOrder = containerToFlatten.ChildFocusOrder.ToList();
var parent = splitContainer.Parent;
var index = splitContainer.Index;
var focusIndex = splitContainer.FocusIndex;
var children = splitContainer.Children.ToList();
var focusOrder = splitContainer.ChildFocusOrder.ToList();

foreach (var (child, index) in originalChildren.WithIndex())
foreach (var (child, childIndex) in children.WithIndex())
{
// Insert children of the split container at its original index in the parent. The split
// container will automatically detach once its last child is detached.
_bus.Invoke(new DetachContainerCommand(child));
_bus.Invoke(new AttachContainerCommand(child, originalParent, originalIndex + index));
// Insert child at its original index in the parent.
splitContainer.RemoveChild(child);
parent.Children.Insert(index + childIndex, child);
child.Parent = parent;

(child as IResizable).SizePercentage = (containerToFlatten as IResizable).SizePercentage
* (child as IResizable).SizePercentage;
if (child is IResizable childResizable)
childResizable.SizePercentage = splitContainer.SizePercentage * childResizable.SizePercentage;

// Inverse the tiling direction of any child split containers.
if (child is SplitContainer childSplitContainer)
childSplitContainer.TilingDirection = childSplitContainer.TilingDirection.Inverse();
}

// Remove the split container from the tree.
parent.RemoveChild(splitContainer);

// Correct focus order of the inserted containers.
foreach (var child in originalChildren)
{
var childFocusIndex = originalFocusOrder.IndexOf(child);
originalParent.ChildFocusOrder.ShiftToIndex(originalFocusIndex + childFocusIndex, child);
}
parent.ChildFocusOrder.InsertRange(focusIndex, focusOrder);

_containerService.ContainersToRedraw.Add(originalParent);
// TODO: Remove unnecessary redraws.
_containerService.ContainersToRedraw.Add(parent);

return CommandResponse.Ok;
}
Expand Down
Loading

0 comments on commit 4c2b898

Please sign in to comment.