Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: flatten nested container when it only has one child remaining #425

Merged
merged 6 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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