diff --git a/kitty/boss.py b/kitty/boss.py index 19d3e1677..25617f181 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -2427,16 +2427,14 @@ class Boss: self, edges: int, x: float, y: float, window_id: int, cell_width: int, cell_height: int, ) -> bool: if (w := self.window_id_map.get(window_id)) and (tab := w.tabref()): - horizontal, width_increases_rightwards, vertical, height_increases_downwards = \ - tab.current_layout.drag_resize_target_windows(w, x, y, edges, tab.windows) - horizontal_allowed = bool(edges & (LEFT_EDGE | RIGHT_EDGE)) - vertical_allowed = bool(edges & (TOP_EDGE | BOTTOM_EDGE)) + data = tab.current_layout.drag_resize_target_windows(w, x, y, edges, tab.windows) + if not edges & (LEFT_EDGE | RIGHT_EDGE): + data = data._replace(horizontal_id=None) + if not edges & (TOP_EDGE | BOTTOM_EDGE): + data = data._replace(vertical_id=None) self.drag_resize_of_window = WindowResizeDrag( - is_active=True, horizontal_target_window_id=horizontal.id if horizontal_allowed else 0, - vertical_target_window_id=vertical.id if vertical_allowed else 0, tab_id=tab.id, + is_active=True, tab_id=tab.id, data=data, cell_width=cell_width, cell_height=cell_height, initial_x=x, initial_y=y, - width_increases_rightwards=width_increases_rightwards, - height_increases_downwards=height_increases_downwards, ) for cw in tab: cw.pause_resize_notifications_to_child() @@ -2444,22 +2442,21 @@ class Boss: return False def drag_resize_update(self, x: float, y: float) -> None: - if not (r := self.drag_resize_of_window): + if not (r := self.drag_resize_of_window) or not (tab := self.tab_for_id(r.tab_id)): return - if h := self.window_id_map.get(r.horizontal_target_window_id): - mult = 1 if r.width_increases_rightwards else -1 + if (h := r.data.horizontal_id) is not None: + mult = 1 if r.data.width_increases_rightwards else -1 step_x = floor((x - r.initial_x) / r.cell_width) * mult dx = step_x - r.last_step_x if dx != 0: - if self.resize_layout_window(h, float(dx), is_horizontal=True) is None: + if tab.drag_resize_window(h, float(dx), True): self.drag_resize_of_window = r._replace(last_step_x=step_x) - - if v := self.window_id_map.get(r.vertical_target_window_id): - mult = 1 if r.height_increases_downwards else -1 + if (v := r.data.vertical_id) is not None: + mult = 1 if r.data.height_increases_downwards else -1 step_y = floor((y - r.initial_y) / r.cell_height) * mult dy = step_y - r.last_step_y if dy != 0: - if self.resize_layout_window(v, float(dy), is_horizontal=False) is None: + if tab.drag_resize_window(v, float(dy), False): self.drag_resize_of_window = r._replace(last_step_y=step_y) def drag_resize_end(self) -> None: diff --git a/kitty/layout/base.py b/kitty/layout/base.py index d8b02b30e..1aef90806 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -9,7 +9,7 @@ from typing import Any, Callable, NamedTuple from kitty.borders import BorderColor from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE, Region, get_options, set_active_window, viewport_for_window from kitty.options.types import Options -from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper +from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper, WindowResizeDragData from kitty.typing_compat import WindowType from kitty.window_list import WindowGroup, WindowList @@ -267,6 +267,7 @@ class Layout: if idx is None or not increment: return False return self.apply_bias(idx, increment, all_windows, is_horizontal) + drag_resize_window = modify_size_of_window def parse_layout_opts(self, layout_opts: str | None = None) -> LayoutOpts: data: dict[str, str] = {} @@ -454,8 +455,8 @@ class Layout: def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, - ) -> tuple[WindowType, bool, WindowType, bool]: - return click_window, bool(edges & RIGHT_EDGE), click_window, bool(edges & BOTTOM_EDGE) + ) -> WindowResizeDragData: + return WindowResizeDragData(click_window.id, bool(edges & RIGHT_EDGE), click_window.id, bool(edges & BOTTOM_EDGE)) def serialize(self, all_windows: WindowList) -> dict[str, Any]: ans = self.layout_state() diff --git a/kitty/layout/splits.py b/kitty/layout/splits.py index e3652a306..758b90b4c 100644 --- a/kitty/layout/splits.py +++ b/kitty/layout/splits.py @@ -5,8 +5,8 @@ from collections.abc import Collection, Generator, Sequence from typing import Any, NamedTuple, Optional, TypedDict, Union from kitty.borders import BorderColor -from kitty.fast_data_types import BOTTOM_EDGE, LEFT_EDGE, RIGHT_EDGE, TOP_EDGE -from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper +from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE +from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper, WindowResizeDragData from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList @@ -460,6 +460,15 @@ class Pair: else: yield q + def window_on_second(self, wid: int) -> bool: + if self.one == wid: + return False + if self.two == wid: + return True + if not isinstance(self.two, Pair): + return False + return self.two.window_on_second(wid) + class SplitsLayoutOpts(LayoutOpts): @@ -706,70 +715,39 @@ class Splits(Layout): return None + def drag_resize_window(self, all_windows: WindowList, pair_id: int, increment: float, is_horizontal: bool = True) -> bool: + for pair in self.pairs_root.self_and_descendants(): + if id(pair) == pair_id: + new_bias = max(0, min(pair.bias + increment, 1)) + if new_bias != pair.bias: + pair.bias = new_bias + return True + return False + def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, - ) -> tuple[WindowType, bool, WindowType, bool]: - horizontal_target = click_window - vertical_target = click_window - width_increases_rightwards = bool(edges & RIGHT_EDGE) - height_increases_downwards = bool(edges & BOTTOM_EDGE) - - id_group_map = {g.id: g for g in all_windows.iter_all_layoutable_groups()} - - def window_for_id(wid: int) -> WindowType | None: - g = id_group_map.get(wid) - # Use the last (topmost) window in the group, matching the convention - # used elsewhere in the layout code (e.g. tall.py drag_resize_target_windows). - return g.windows[-1] if g and g.windows else None - - def select_resize_target(pair: Pair) -> tuple[WindowType | None, bool]: - # Prefer a window stored directly in pair (not inside a sub-Pair) so that - # pair_for_window() returns this pair itself and modify_size_of_child() - # modifies the intended pair's bias without prematurely stopping at an inner - # pair of the same orientation. - # pair.one (left/top) with direction=True: moving right/down grows pair.one. - # pair.two (right/bottom) with direction=False: achieves the same net effect - # because modify_size_of_child negates the increment for which==2. - if isinstance(pair.one, int): - w = window_for_id(pair.one) - if w is not None: - return w, True - if isinstance(pair.two, int): - w = window_for_id(pair.two) - if w is not None: - return w, False - # Both sides are sub-Pairs: best-effort fallback using any window from pair.one - if isinstance(pair.one, Pair): - for wid in pair.one.all_window_ids(): - w = window_for_id(wid) - if w is not None: - return w, True - return None, True - - for pair in self.pairs_root.self_and_descendants(): - if not pair.between_borders: - continue - b0, b1 = pair.between_borders[0], pair.between_borders[1] - if pair.horizontal: - # Horizontal pairs have a vertical border (left/right edges). - # The border covers x in [b0.left, b1.right] and y in [b0.top, b0.bottom]. - if edges & (LEFT_EDGE | RIGHT_EDGE): - if b0.left <= x <= b1.right and b0.top <= y <= b0.bottom: - target, direction = select_resize_target(pair) - if target is not None: - horizontal_target = target - width_increases_rightwards = direction - else: - # Vertical pairs have a horizontal border (top/bottom edges). - # The border covers x in [b0.left, b0.right] and y in [b0.top, b1.bottom]. - if edges & (TOP_EDGE | BOTTOM_EDGE): - if b0.left <= x <= b0.right and b0.top <= y <= b1.bottom: - target, direction = select_resize_target(pair) - if target is not None: - vertical_target = target - height_increases_downwards = direction - - return horizontal_target, width_increases_rightwards, vertical_target, height_increases_downwards + ) -> WindowResizeDragData: + is_right, is_bottom = bool(edges & RIGHT_EDGE), bool(edges & BOTTOM_EDGE) + 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: + 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 + while ans.horizontal_id is None or ans.vertical_id is None: + if not p.is_redundant: + if ans.horizontal_id is None and p.horizontal and p.window_on_second(wg.id) != is_right: + ans = ans._replace(horizontal_id=id(p), width_increases_rightwards=not is_right) + if ans.vertical_id is None and not p.horizontal and p.window_on_second(wg.id) != is_bottom: + ans = ans._replace(horizontal_id=id(p)) + if (parent := pair_parent_map.get(p)) is None: + break + p = parent + return ans def layout_state(self) -> dict[str, Any]: return {'pairs': self.pairs_root.serialize()} diff --git a/kitty/layout/tall.py b/kitty/layout/tall.py index 9cd5197e1..f60b5445c 100644 --- a/kitty/layout/tall.py +++ b/kitty/layout/tall.py @@ -9,7 +9,7 @@ from typing import Any from kitty.borders import BorderColor from kitty.conf.utils import to_bool from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE -from kitty.types import Edges, NeighborsMap, WindowMapper +from kitty.types import Edges, NeighborsMap, WindowMapper, WindowResizeDragData from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList @@ -33,12 +33,12 @@ def drag_resize_target_windows( num_full_size_windows: int, all_windows: WindowList, main_is_horizontal: bool = True -) -> tuple[WindowType, bool, WindowType, bool]: +) -> WindowResizeDragData: groups = tuple(all_windows.iter_all_layoutable_groups()) horizontal = vertical = click_window min_dist = float(sys.maxsize) - height_increases_downwards = bool(edges * BOTTOM_EDGE) - width_increases_rightwards = bool(edges * RIGHT_EDGE) + height_increases_downwards = bool(edges & BOTTOM_EDGE) + width_increases_rightwards = bool(edges & RIGHT_EDGE) for gr in groups[num_full_size_windows:]: if gr.windows: w = gr.windows[-1] @@ -53,7 +53,7 @@ def drag_resize_target_windows( min_dist = dist horizontal = w width_increases_rightwards = x > g.left + (g.right - g.left) / 2 - return horizontal, width_increases_rightwards, vertical, height_increases_downwards + return WindowResizeDragData(horizontal.id, width_increases_rightwards, vertical.id, height_increases_downwards) def neighbors_for_tall_window( @@ -391,7 +391,7 @@ class Tall(Layout): def drag_resize_target_windows( self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList, - ) -> tuple[WindowType, bool, WindowType, bool]: + ) -> WindowResizeDragData: return drag_resize_target_windows(click_window, edges, x, y, self.num_full_size_windows, all_windows, self.main_is_horizontal) diff --git a/kitty/mouse.c b/kitty/mouse.c index 83989c405..70d0dcf7b 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -898,6 +898,13 @@ mouse_in_region(Region *r) { return true; } +static unsigned +num_visible_windows(Tab *t) { + unsigned ans = t->num_windows; + for (unsigned i = 0; i < t->num_windows; i++) if (!t->windows[i].visible) ans--; + return ans; +} + static Window* window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border) { Region central, tab_bar; @@ -913,7 +920,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border } if (in_central && w->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; - if (window_border) { + if (window_border && num_visible_windows(t) > 1) { *window_border = 0; double dpi = (w->fonts_data->logical_dpi_x + w->fonts_data->logical_dpi_y) / 2.; double tolerance = ((long)round((OPT(window_drag_tolerance) * (dpi / 72.0)))); diff --git a/kitty/tabs.py b/kitty/tabs.py index f507841ed..08bad0a15 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -562,6 +562,12 @@ class Tab: # {{{ return None return 'Could not resize' + def drag_resize_window(self, object_id: int, increment: float, is_horizontal: bool) -> bool: + increment_as_percent = self.current_layout.bias_increment_for_cell(self.windows, is_horizontal) * increment + if resized := self.current_layout.drag_resize_window(self.windows, object_id, increment_as_percent, is_horizontal): + self.relayout() + return resized + @ac('win', ''' Resize the active window by the specified amount diff --git a/kitty/types.py b/kitty/types.py index 61ae862f9..1dc323fad 100644 --- a/kitty/types.py +++ b/kitty/types.py @@ -243,19 +243,23 @@ class NeighborsMap(TypedDict, total=False): bottom: list[int] +class WindowResizeDragData(NamedTuple): + horizontal_id: int | None = None + height_increases_downwards: bool = True + vertical_id: int | None = None + width_increases_rightwards: bool = True + + class WindowResizeDrag(NamedTuple): is_active: bool = False cell_width: int = 0 cell_height: int = 0 - horizontal_target_window_id: int = 0 - vertical_target_window_id: int = 0 initial_x: float = 0 initial_y: float = 0 last_step_x: float = 0 last_step_y: float = 0 - height_increases_downwards: bool = True - width_increases_rightwards: bool = True tab_id: int = 0 + data: WindowResizeDragData = WindowResizeDragData() def __bool__(self) -> bool: return self.is_active