mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-20 12:19:36 +00:00
Merge branch 'fix/nested-split-border-resize' of https://github.com/xiaobai050/kitty
This commit is contained in:
commit
a11839a2c7
3 changed files with 143 additions and 53 deletions
|
|
@ -202,6 +202,8 @@ Detailed list of changes
|
|||
0.49.0 [future]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Splits layout: Fix dragging a border resizing the wrong split in nested layouts.
|
||||
|
||||
- Support for :doc:`/custom-shaders` for adding various graphical effects (:iss:`10344`)
|
||||
|
||||
- Various throughput performance improvements for a 15-35% real world improvement depending on workload
|
||||
|
|
|
|||
|
|
@ -879,62 +879,36 @@ class Splits(Layout):
|
|||
edges: int,
|
||||
all_windows: WindowList,
|
||||
) -> WindowResizeDragData:
|
||||
is_right, is_bottom = bool(edges & RIGHT_EDGE), bool(edges & BOTTOM_EDGE)
|
||||
is_leading_edge = not (is_right or is_bottom)
|
||||
ans = WindowResizeDragData(None, is_right, None, is_bottom)
|
||||
if (wg := all_windows.group_for_window(click_window)) is None or (pair := self.pairs_root.pair_for_window(wg.id)) is None:
|
||||
right, bottom = bool(edges & RIGHT_EDGE), bool(edges & BOTTOM_EDGE)
|
||||
ans = WindowResizeDragData(None, right, None, bottom)
|
||||
group = all_windows.group_for_window(click_window)
|
||||
if group is None:
|
||||
return ans
|
||||
path = self.pairs_root.find_window_in_tree(group.id)
|
||||
if path is None:
|
||||
return ans
|
||||
pair_parent_map = {}
|
||||
for p in self.pairs_root.self_and_descendants():
|
||||
if isinstance(p.one, Pair):
|
||||
pair_parent_map[p.one] = p
|
||||
if isinstance(p.two, Pair):
|
||||
pair_parent_map[p.two] = p
|
||||
p = pair
|
||||
|
||||
def size_increases_forwards(p: Pair) -> bool:
|
||||
in_leading_half = not p.is_group_on_second(wg.id)
|
||||
return is_leading_edge != in_leading_half
|
||||
def target(horizontal: bool, trailing: bool) -> tuple[int | None, bool]:
|
||||
fallback = None
|
||||
for pair, in_one in reversed(path):
|
||||
if pair.is_redundant or pair.horizontal != horizontal:
|
||||
continue
|
||||
if fallback is None:
|
||||
fallback = id(pair)
|
||||
# The right/bottom edge of the first child, or left/top edge
|
||||
# of the second child, belongs to this divider. Otherwise keep
|
||||
# climbing, even across ancestors with the same split axis.
|
||||
if in_one == trailing:
|
||||
return id(pair), True
|
||||
# Preserve native resizing from the outside edge of a layout.
|
||||
return fallback, False
|
||||
|
||||
def ancestor_with_neighboring_border_of_same_orientation(p: Pair) -> Pair | None:
|
||||
horizontal = bool(edges & (LEFT_EDGE | RIGHT_EDGE))
|
||||
while q := pair_parent_map.get(p):
|
||||
if q.horizontal == horizontal:
|
||||
if q.between_borders:
|
||||
return q
|
||||
break
|
||||
p = q
|
||||
return None
|
||||
|
||||
def pair_or_parent(p: Pair) -> tuple[Pair, bool]:
|
||||
in_leading_half = not p.is_group_on_second(wg.id)
|
||||
if is_leading_edge == in_leading_half and p is pair and (parent := ancestor_with_neighboring_border_of_same_orientation(p)):
|
||||
# special case for leading edge of one or trailing edge of two with parent being same orientation
|
||||
return parent, True
|
||||
return p, size_increases_forwards(p)
|
||||
|
||||
while ans.horizontal_id is None or ans.vertical_id is None:
|
||||
if p.is_redundant:
|
||||
continue
|
||||
if ans.horizontal_id is None and p.horizontal:
|
||||
new_p, fwd = pair_or_parent(p)
|
||||
p = new_p
|
||||
if not p.horizontal and ans.vertical_id is None:
|
||||
# pair_or_parent redirected to a vertical pair; use it for vertical resize
|
||||
ans = ans._replace(vertical_id=id(p), height_increases_downwards=fwd)
|
||||
else:
|
||||
ans = ans._replace(horizontal_id=id(p), width_increases_rightwards=fwd)
|
||||
if ans.vertical_id is None and not p.horizontal:
|
||||
new_p, fwd = pair_or_parent(p)
|
||||
p = new_p
|
||||
if p.horizontal and ans.horizontal_id is None:
|
||||
# pair_or_parent redirected to a horizontal pair; use it for horizontal resize
|
||||
ans = ans._replace(horizontal_id=id(p), width_increases_rightwards=fwd)
|
||||
else:
|
||||
ans = ans._replace(vertical_id=id(p), height_increases_downwards=fwd)
|
||||
if (parent := pair_parent_map.get(p)) is None:
|
||||
break
|
||||
p = parent
|
||||
if edges & (LEFT_EDGE | RIGHT_EDGE):
|
||||
horizontal_id, forwards = target(True, right)
|
||||
ans = ans._replace(horizontal_id=horizontal_id, width_increases_rightwards=forwards)
|
||||
if edges & (TOP_EDGE | BOTTOM_EDGE):
|
||||
vertical_id, forwards = target(False, bottom)
|
||||
ans = ans._replace(vertical_id=vertical_id, height_increases_downwards=forwards)
|
||||
return ans
|
||||
|
||||
def layout_state(self) -> dict[str, Any]:
|
||||
|
|
|
|||
|
|
@ -855,3 +855,117 @@ class TestLayout(BaseTest):
|
|||
d = drtw(q, all_windows, wD, TOP_EDGE)
|
||||
self.ae(d.vertical_id, right_pair_id)
|
||||
self.ae(d.height_increases_downwards, True)
|
||||
|
||||
|
||||
class TestSplitBorderResize(BaseTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.set_options({'tab_bar_style': 'hidden'})
|
||||
|
||||
def make_layout(self, shape):
|
||||
layout = create_layout(Splits)
|
||||
windows = create_windows(layout, num=0)
|
||||
for i in range(1, 5):
|
||||
layout.add_window(windows, Window(i))
|
||||
layout.pairs_root.unserialize(shape, lambda x: x)
|
||||
layout(windows)
|
||||
return layout, windows
|
||||
|
||||
def test_left_center_stack_right_after_resize(self):
|
||||
layout, windows = self.make_layout(
|
||||
{
|
||||
'bias': 1 / 3,
|
||||
'one': 1,
|
||||
'two': {'one': {'horizontal': False, 'one': 2, 'two': 3}, 'two': 4},
|
||||
}
|
||||
)
|
||||
root = layout.pairs_root
|
||||
right_pair = root.two
|
||||
center = right_pair.one
|
||||
ws = {w.id: w for w in windows}
|
||||
# Resize right first, then alternate both sides of the left and right
|
||||
# dividers and both halves of the central horizontal divider.
|
||||
for _ in range(3):
|
||||
for wid, edge, expected in (
|
||||
(4, LEFT_EDGE, right_pair),
|
||||
(2, LEFT_EDGE, root),
|
||||
(3, LEFT_EDGE, root),
|
||||
(1, RIGHT_EDGE, root),
|
||||
(2, RIGHT_EDGE, right_pair),
|
||||
(3, RIGHT_EDGE, right_pair),
|
||||
(2, BOTTOM_EDGE, center),
|
||||
(3, TOP_EDGE, center),
|
||||
):
|
||||
for increment in (0.04, -0.03):
|
||||
with self.subTest(wid=wid, edge=edge, increment=increment):
|
||||
data = layout.drag_resize_target_windows(ws[wid], 0, 0, edge, windows)
|
||||
horizontal = bool(edge & (LEFT_EDGE | RIGHT_EDGE))
|
||||
target = data.horizontal_id if horizontal else data.vertical_id
|
||||
forwards = data.width_increases_rightwards if horizontal else data.height_increases_downwards
|
||||
self.assertEqual(target, id(expected))
|
||||
self.assertTrue(forwards)
|
||||
before = {id(p): p.bias for p in root.self_and_descendants()}
|
||||
self.assertTrue(layout.drag_resize_window(windows, target, increment, horizontal))
|
||||
layout(windows)
|
||||
for p in root.self_and_descendants():
|
||||
self.assertAlmostEqual(
|
||||
p.bias,
|
||||
before[id(p)] + (increment if p is expected else 0),
|
||||
)
|
||||
|
||||
def test_all_rendered_internal_borders(self):
|
||||
# All 5 binary tree shapes with 4 leaves, with every combination of
|
||||
# horizontal/vertical ancestors: 40 distinct arrangements.
|
||||
def shapes(ids):
|
||||
if len(ids) == 1:
|
||||
yield ids[0]
|
||||
return
|
||||
for n in range(1, len(ids)):
|
||||
for one in shapes(ids[:n]):
|
||||
for two in shapes(ids[n:]):
|
||||
for horizontal in (True, False):
|
||||
yield {
|
||||
'horizontal': horizontal,
|
||||
'bias': 0.39,
|
||||
'one': one,
|
||||
'two': two,
|
||||
}
|
||||
|
||||
count = 0
|
||||
for shape in shapes((1, 2, 3, 4)):
|
||||
layout, windows = self.make_layout(shape)
|
||||
ws = {w.id: w for w in windows}
|
||||
for pair in layout.pairs_root.self_and_descendants():
|
||||
if pair.is_redundant:
|
||||
continue
|
||||
for half in pair.between_borders:
|
||||
for border in half:
|
||||
trailing = border.window_id > 0
|
||||
edge = (RIGHT_EDGE if trailing else LEFT_EDGE) if pair.horizontal else (BOTTOM_EDGE if trailing else TOP_EDGE)
|
||||
with self.subTest(shape=shape, wid=border.window_id, edge=edge):
|
||||
data = layout.drag_resize_target_windows(ws[abs(border.window_id)], 0, 0, edge, windows)
|
||||
target = data.horizontal_id if pair.horizontal else data.vertical_id
|
||||
forwards = data.width_increases_rightwards if pair.horizontal else data.height_increases_downwards
|
||||
self.assertEqual(target, id(pair))
|
||||
self.assertTrue(forwards)
|
||||
count += 1
|
||||
self.assertEqual(count, 120)
|
||||
|
||||
def test_corner_axes_independent_and_single_pane(self):
|
||||
layout, windows = self.make_layout(
|
||||
{
|
||||
'one': 1,
|
||||
'two': {'one': {'horizontal': False, 'one': 2, 'two': 3}, 'two': 4},
|
||||
}
|
||||
)
|
||||
ws = {w.id: w for w in windows}
|
||||
data = layout.drag_resize_target_windows(ws[2], 0, 0, LEFT_EDGE | BOTTOM_EDGE, windows)
|
||||
self.assertEqual(data.horizontal_id, id(layout.pairs_root))
|
||||
self.assertEqual(data.vertical_id, id(layout.pairs_root.two.one))
|
||||
self.assertTrue(data.width_increases_rightwards)
|
||||
self.assertTrue(data.height_increases_downwards)
|
||||
layout.pairs_root = Pair()
|
||||
layout.pairs_root.one = 1
|
||||
data = layout.drag_resize_target_windows(ws[1], 0, 0, LEFT_EDGE | TOP_EDGE, windows)
|
||||
self.assertIsNone(data.horizontal_id)
|
||||
self.assertIsNone(data.vertical_id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue