mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Work on drag resize for splits layout
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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))));
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user