Skip to content

Commit

Permalink
feat(core, layout): allow specifying position=next/previous during sp…
Browse files Browse the repository at this point in the history
…lit()
  • Loading branch information
aravinda0 committed Apr 29, 2024
1 parent 07d5de5 commit c9ba593
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 17 deletions.
41 changes: 27 additions & 14 deletions src/qtile_bonsai/core/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
import abc
from typing import TypeVar

from qtile_bonsai.core.geometry import Axis, AxisParam, Box, PerimieterParams, Rect
from qtile_bonsai.core.geometry import (
Axis,
AxisParam,
Box,
Direction1D,
PerimieterParams,
Rect,
)


class Node(metaclass=abc.ABCMeta):
Expand Down Expand Up @@ -42,7 +49,7 @@ def transform(self, axis: AxisParam, start: int, size: int):

@abc.abstractmethod
def get_participants_for_split_op(
self, axis: Axis
self, axis: Axis, position: Direction1D
) -> tuple[SplitContainer | None, Node, int]:
"""Return participants that would be invovled in a split operation on this node.
Expand Down Expand Up @@ -254,14 +261,14 @@ def transform(self, axis: AxisParam, start: int, size: int):
setattr(rect, axis.dim, size)

def get_participants_for_split_op(
self, axis: Axis
self, axis: Axis, position: Direction1D
) -> tuple[SplitContainer | None, Node, int]:
parent = self.parent
if parent.axis != axis:
return (None, self, 1)
return (None, self, 1 if position == Direction1D.next else 0)

index = parent.children.index(self)
return (parent, self, index + 1)
return (parent, self, index + 1 if position == Direction1D.next else index)

def as_dict(self) -> dict:
return {
Expand Down Expand Up @@ -338,17 +345,23 @@ def transform(self, axis: AxisParam, start: int, size: int):
child.transform(axis, start, size)

def get_participants_for_split_op(
self, axis: Axis
self, axis: Axis, position: Direction1D
) -> tuple[SplitContainer | None, Node, int]:
parent = self.parent

if self.axis == axis:
return (self, self, len(self.children))
return (
self,
self,
len(self.children) if position == Direction1D.next else 0,
)
if self.is_nearest_under_tc and self.is_sole_child:
return (None, self, 1)
return (None, self, 1 if position == Direction1D.next else 0)

assert isinstance(parent, SplitContainer)
return (parent, self, parent.children.index(self) + 1)

index = parent.children.index(self)
return (parent, self, index + 1 if position == Direction1D.next else index)

def as_dict(self) -> dict:
return {
Expand Down Expand Up @@ -391,9 +404,9 @@ def transform(self, axis: AxisParam, start: int, size: int):
self.children[0].transform(axis, start, size)

def get_participants_for_split_op(
self, axis: Axis
self, axis: Axis, position: Direction1D
) -> tuple[SplitContainer | None, Node, int]:
return self.parent.get_participants_for_split_op(axis)
return self.parent.get_participants_for_split_op(axis, position)

def as_dict(self) -> dict:
return {
Expand Down Expand Up @@ -457,17 +470,17 @@ def transform(self, axis: AxisParam, start: int, size: int):
child.transform(axis, start, size)

def get_participants_for_split_op(
self, axis: Axis
self, axis: Axis, position: Direction1D
) -> tuple[SplitContainer | None, Node, int]:
parent = self.parent
if parent is None:
raise ValueError("Invalid node for split operation")

if parent.axis != axis:
return (None, self, 1)
return (None, self, 1 if position == Direction1D.next else 0)

index = parent.children.index(self)
return (parent, self, index + 1)
return (parent, self, index + 1 if position == Direction1D.next else index)

def as_dict(self) -> dict:
return {
Expand Down
14 changes: 12 additions & 2 deletions src/qtile_bonsai/core/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
AxisParam,
Box,
Direction,
Direction1D,
Direction1DParam,
DirectionParam,
PerimieterParams,
Rect,
Expand Down Expand Up @@ -281,16 +283,18 @@ def split(
*,
ratio: float = 0.5,
normalize: bool = False,
position: Direction1DParam = Direction1D.next,
) -> Pane:
"""Create a new pane by splitting the provided `node` on the provided `axis`.
If `normalize` is provided, it takes precedence over `ratio`. In this case, the
new pane and all the sibling nodes will be adjusted to be of equal size.
"""
axis = Axis(axis)
position = Direction1D(position)

new_p, added_nodes, removed_nodes = self._split(
node, axis, ratio=ratio, normalize=normalize
node, axis, ratio=ratio, normalize=normalize, position=position
)
self._notify_subscribers(TreeEvent.node_added, added_nodes)
self._notify_subscribers(TreeEvent.node_removed, removed_nodes)
Expand Down Expand Up @@ -975,6 +979,7 @@ def _split(
ratio: float = 0.5,
normalize: bool = False,
insert_node: Node | None = None,
position: Direction1D = Direction1D.next,
) -> tuple[Node, list[Node], list[Node]]:
validate_unit_range(ratio, "ratio")
try:
Expand All @@ -991,7 +996,9 @@ def _split(

added_nodes = []
removed_nodes = []
container, node_to_split, new_index = node.get_participants_for_split_op(axis)
container, node_to_split, new_index = node.get_participants_for_split_op(
axis, position
)
if container is None:
# We need a new intermediate SC
sc = self.create_split_container()
Expand All @@ -1013,6 +1020,9 @@ def _split(
container = sc

n1_rect, n2_rect = node_to_split.principal_rect.split(axis, ratio)
if position == Direction1D.previous:
n1_rect, n2_rect = n2_rect, n1_rect

node_to_split.transform(axis, n1_rect.coord(axis), n1_rect.size(axis))

assert isinstance(container, SplitContainer)
Expand Down
10 changes: 9 additions & 1 deletion src/qtile_bonsai/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ def spawn_split(
*,
ratio: float = 0.5,
normalize: bool = True,
position: Direction1DParam = Direction1D.next,
):
"""
Launch the provided `program` into a new window that splits the currently
Expand All @@ -427,18 +428,25 @@ def spawn_split(
If `True`, overrides `ratio` and leads to the new window and all sibling
windows becoming of equal size along the corresponding split axis.
Defaults to `True`.
`position`:
Whether the new split content appears after or before the currently
focused window.
Can be `"next"` or `"previous"`. Defaults to `"next"`.
Examples:
- `layout.spawn_split(my_terminal, "x")`
- `layout.spawn_split(my_terminal, "y", ratio=0.2, normalize=False)`
- `layout.spawn_split(my_terminal, "x", position="previous")`
"""
if self._tree.is_empty:
logger.warn("There are no windows yet to split")
return

def _handle_next_window():
target = self.focused_pane or self._tree.find_mru_pane()
return self._tree.split(target, axis, ratio=ratio, normalize=normalize)
return self._tree.split(
target, axis, ratio=ratio, normalize=normalize, position=position
)

self._on_next_window = _handle_next_window

Expand Down
60 changes: 60 additions & 0 deletions tests/unit/core/test_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ def test_when_there_are_existing_splits_along_an_axis_and_new_split_is_created_w
""",
)

def test_when_position_is_previous(self, tree: Tree):
p1 = tree.tab()

tree.split(p1, "x", position="previous")

assert tree_matches_repr(
tree,
"""
- tc:1
- t:2
- sc.x:3
- p:5 | {x: 0, y: 20, w: 200, h: 280}
- p:4 | {x: 200, y: 20, w: 200, h: 280}
""",
)

def test_subscribers_are_notified_of_added_nodes(self, tree: Tree):
callback = mock.Mock()
tree.subscribe(TreeEvent.node_added, callback)
Expand Down Expand Up @@ -385,6 +401,50 @@ def test_when_split_on_sc_against_axis(self, tree: Tree):
""",
)

def test_when_split_on_sc_along_axis_with_position_previous(self, tree: Tree):
p1 = tree.tab()
p2 = tree.split(p1, "x")
p3 = tree.split(p2, "y")

sc = p3.parent
tree.split(sc, "y", position="previous")

assert tree_matches_repr(
tree,
"""
- tc:1
- t:2
- sc.x:3
- p:4 | {x: 0, y: 20, w: 200, h: 280}
- sc.y:6
- p:8 | {x: 200, y: 20, w: 200, h: 140}
- p:5 | {x: 200, y: 160, w: 200, h: 70}
- p:7 | {x: 200, y: 230, w: 200, h: 70}
""",
)

def test_when_split_on_sc_against_axis_with_position_previous(self, tree: Tree):
p1 = tree.tab()
p2 = tree.split(p1, "x")
p3 = tree.split(p2, "y")

sc = p3.parent
tree.split(sc, "x", position="previous")

assert tree_matches_repr(
tree,
"""
- tc:1
- t:2
- sc.x:3
- p:4 | {x: 0, y: 20, w: 200, h: 280}
- p:8 | {x: 200, y: 20, w: 100, h: 280}
- sc.y:6
- p:5 | {x: 300, y: 20, w: 100, h: 140}
- p:7 | {x: 300, y: 160, w: 100, h: 140}
""",
)

def test_when_split_on_t(self, tree: Tree):
p1 = tree.tab()
p2 = tree.split(p1, "y")
Expand Down

0 comments on commit c9ba593

Please sign in to comment.