mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +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,
|
self, edges: int, x: float, y: float, window_id: int, cell_width: int, cell_height: int,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if (w := self.window_id_map.get(window_id)) and (tab := w.tabref()):
|
if (w := self.window_id_map.get(window_id)) and (tab := w.tabref()):
|
||||||
horizontal, width_increases_rightwards, vertical, height_increases_downwards = \
|
data = tab.current_layout.drag_resize_target_windows(w, x, y, edges, tab.windows)
|
||||||
tab.current_layout.drag_resize_target_windows(w, x, y, edges, tab.windows)
|
if not edges & (LEFT_EDGE | RIGHT_EDGE):
|
||||||
horizontal_allowed = bool(edges & (LEFT_EDGE | RIGHT_EDGE))
|
data = data._replace(horizontal_id=None)
|
||||||
vertical_allowed = bool(edges & (TOP_EDGE | BOTTOM_EDGE))
|
if not edges & (TOP_EDGE | BOTTOM_EDGE):
|
||||||
|
data = data._replace(vertical_id=None)
|
||||||
self.drag_resize_of_window = WindowResizeDrag(
|
self.drag_resize_of_window = WindowResizeDrag(
|
||||||
is_active=True, horizontal_target_window_id=horizontal.id if horizontal_allowed else 0,
|
is_active=True, tab_id=tab.id, data=data,
|
||||||
vertical_target_window_id=vertical.id if vertical_allowed else 0, tab_id=tab.id,
|
|
||||||
cell_width=cell_width, cell_height=cell_height, initial_x=x, initial_y=y,
|
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:
|
for cw in tab:
|
||||||
cw.pause_resize_notifications_to_child()
|
cw.pause_resize_notifications_to_child()
|
||||||
@@ -2444,22 +2442,21 @@ class Boss:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def drag_resize_update(self, x: float, y: float) -> None:
|
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
|
return
|
||||||
if h := self.window_id_map.get(r.horizontal_target_window_id):
|
if (h := r.data.horizontal_id) is not None:
|
||||||
mult = 1 if r.width_increases_rightwards else -1
|
mult = 1 if r.data.width_increases_rightwards else -1
|
||||||
step_x = floor((x - r.initial_x) / r.cell_width) * mult
|
step_x = floor((x - r.initial_x) / r.cell_width) * mult
|
||||||
dx = step_x - r.last_step_x
|
dx = step_x - r.last_step_x
|
||||||
if dx != 0:
|
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)
|
self.drag_resize_of_window = r._replace(last_step_x=step_x)
|
||||||
|
if (v := r.data.vertical_id) is not None:
|
||||||
if v := self.window_id_map.get(r.vertical_target_window_id):
|
mult = 1 if r.data.height_increases_downwards else -1
|
||||||
mult = 1 if r.height_increases_downwards else -1
|
|
||||||
step_y = floor((y - r.initial_y) / r.cell_height) * mult
|
step_y = floor((y - r.initial_y) / r.cell_height) * mult
|
||||||
dy = step_y - r.last_step_y
|
dy = step_y - r.last_step_y
|
||||||
if dy != 0:
|
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)
|
self.drag_resize_of_window = r._replace(last_step_y=step_y)
|
||||||
|
|
||||||
def drag_resize_end(self) -> None:
|
def drag_resize_end(self) -> None:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Any, Callable, NamedTuple
|
|||||||
from kitty.borders import BorderColor
|
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.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.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.typing_compat import WindowType
|
||||||
from kitty.window_list import WindowGroup, WindowList
|
from kitty.window_list import WindowGroup, WindowList
|
||||||
|
|
||||||
@@ -267,6 +267,7 @@ class Layout:
|
|||||||
if idx is None or not increment:
|
if idx is None or not increment:
|
||||||
return False
|
return False
|
||||||
return self.apply_bias(idx, increment, all_windows, is_horizontal)
|
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:
|
def parse_layout_opts(self, layout_opts: str | None = None) -> LayoutOpts:
|
||||||
data: dict[str, str] = {}
|
data: dict[str, str] = {}
|
||||||
@@ -454,8 +455,8 @@ class Layout:
|
|||||||
|
|
||||||
def drag_resize_target_windows(
|
def drag_resize_target_windows(
|
||||||
self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList,
|
self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList,
|
||||||
) -> tuple[WindowType, bool, WindowType, bool]:
|
) -> WindowResizeDragData:
|
||||||
return click_window, bool(edges & RIGHT_EDGE), click_window, bool(edges & BOTTOM_EDGE)
|
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]:
|
def serialize(self, all_windows: WindowList) -> dict[str, Any]:
|
||||||
ans = self.layout_state()
|
ans = self.layout_state()
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from collections.abc import Collection, Generator, Sequence
|
|||||||
from typing import Any, NamedTuple, Optional, TypedDict, Union
|
from typing import Any, NamedTuple, Optional, TypedDict, Union
|
||||||
|
|
||||||
from kitty.borders import BorderColor
|
from kitty.borders import BorderColor
|
||||||
from kitty.fast_data_types import BOTTOM_EDGE, LEFT_EDGE, RIGHT_EDGE, TOP_EDGE
|
from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE
|
||||||
from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper
|
from kitty.types import Edges, NeighborsMap, WindowGeometry, WindowMapper, WindowResizeDragData
|
||||||
from kitty.typing_compat import EdgeLiteral, WindowType
|
from kitty.typing_compat import EdgeLiteral, WindowType
|
||||||
from kitty.window_list import WindowGroup, WindowList
|
from kitty.window_list import WindowGroup, WindowList
|
||||||
|
|
||||||
@@ -460,6 +460,15 @@ class Pair:
|
|||||||
else:
|
else:
|
||||||
yield q
|
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):
|
class SplitsLayoutOpts(LayoutOpts):
|
||||||
|
|
||||||
@@ -706,70 +715,39 @@ class Splits(Layout):
|
|||||||
|
|
||||||
return None
|
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(
|
def drag_resize_target_windows(
|
||||||
self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList,
|
self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList,
|
||||||
) -> tuple[WindowType, bool, WindowType, bool]:
|
) -> WindowResizeDragData:
|
||||||
horizontal_target = click_window
|
is_right, is_bottom = bool(edges & RIGHT_EDGE), bool(edges & BOTTOM_EDGE)
|
||||||
vertical_target = click_window
|
ans = WindowResizeDragData(None, is_right, None, is_bottom)
|
||||||
width_increases_rightwards = bool(edges & RIGHT_EDGE)
|
if (wg := all_windows.group_for_window(click_window)) is None or (pair := self.pairs_root.pair_for_window(wg.id)) is None:
|
||||||
height_increases_downwards = bool(edges & BOTTOM_EDGE)
|
return ans
|
||||||
|
pair_parent_map = {}
|
||||||
id_group_map = {g.id: g for g in all_windows.iter_all_layoutable_groups()}
|
for p in self.pairs_root.self_and_descendants():
|
||||||
|
if isinstance(p.one, Pair):
|
||||||
def window_for_id(wid: int) -> WindowType | None:
|
pair_parent_map[p.one] = p
|
||||||
g = id_group_map.get(wid)
|
if isinstance(p.two, Pair):
|
||||||
# Use the last (topmost) window in the group, matching the convention
|
pair_parent_map[p.two] = p
|
||||||
# used elsewhere in the layout code (e.g. tall.py drag_resize_target_windows).
|
p = pair
|
||||||
return g.windows[-1] if g and g.windows else None
|
while ans.horizontal_id is None or ans.vertical_id is None:
|
||||||
|
if not p.is_redundant:
|
||||||
def select_resize_target(pair: Pair) -> tuple[WindowType | None, bool]:
|
if ans.horizontal_id is None and p.horizontal and p.window_on_second(wg.id) != is_right:
|
||||||
# Prefer a window stored directly in pair (not inside a sub-Pair) so that
|
ans = ans._replace(horizontal_id=id(p), width_increases_rightwards=not is_right)
|
||||||
# pair_for_window() returns this pair itself and modify_size_of_child()
|
if ans.vertical_id is None and not p.horizontal and p.window_on_second(wg.id) != is_bottom:
|
||||||
# modifies the intended pair's bias without prematurely stopping at an inner
|
ans = ans._replace(horizontal_id=id(p))
|
||||||
# pair of the same orientation.
|
if (parent := pair_parent_map.get(p)) is None:
|
||||||
# pair.one (left/top) with direction=True: moving right/down grows pair.one.
|
break
|
||||||
# pair.two (right/bottom) with direction=False: achieves the same net effect
|
p = parent
|
||||||
# because modify_size_of_child negates the increment for which==2.
|
return ans
|
||||||
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
|
|
||||||
|
|
||||||
def layout_state(self) -> dict[str, Any]:
|
def layout_state(self) -> dict[str, Any]:
|
||||||
return {'pairs': self.pairs_root.serialize()}
|
return {'pairs': self.pairs_root.serialize()}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Any
|
|||||||
from kitty.borders import BorderColor
|
from kitty.borders import BorderColor
|
||||||
from kitty.conf.utils import to_bool
|
from kitty.conf.utils import to_bool
|
||||||
from kitty.fast_data_types import BOTTOM_EDGE, RIGHT_EDGE
|
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.typing_compat import EdgeLiteral, WindowType
|
||||||
from kitty.window_list import WindowGroup, WindowList
|
from kitty.window_list import WindowGroup, WindowList
|
||||||
|
|
||||||
@@ -33,12 +33,12 @@ def drag_resize_target_windows(
|
|||||||
num_full_size_windows: int,
|
num_full_size_windows: int,
|
||||||
all_windows: WindowList,
|
all_windows: WindowList,
|
||||||
main_is_horizontal: bool = True
|
main_is_horizontal: bool = True
|
||||||
) -> tuple[WindowType, bool, WindowType, bool]:
|
) -> WindowResizeDragData:
|
||||||
groups = tuple(all_windows.iter_all_layoutable_groups())
|
groups = tuple(all_windows.iter_all_layoutable_groups())
|
||||||
horizontal = vertical = click_window
|
horizontal = vertical = click_window
|
||||||
min_dist = float(sys.maxsize)
|
min_dist = float(sys.maxsize)
|
||||||
height_increases_downwards = bool(edges * BOTTOM_EDGE)
|
height_increases_downwards = bool(edges & BOTTOM_EDGE)
|
||||||
width_increases_rightwards = bool(edges * RIGHT_EDGE)
|
width_increases_rightwards = bool(edges & RIGHT_EDGE)
|
||||||
for gr in groups[num_full_size_windows:]:
|
for gr in groups[num_full_size_windows:]:
|
||||||
if gr.windows:
|
if gr.windows:
|
||||||
w = gr.windows[-1]
|
w = gr.windows[-1]
|
||||||
@@ -53,7 +53,7 @@ def drag_resize_target_windows(
|
|||||||
min_dist = dist
|
min_dist = dist
|
||||||
horizontal = w
|
horizontal = w
|
||||||
width_increases_rightwards = x > g.left + (g.right - g.left) / 2
|
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(
|
def neighbors_for_tall_window(
|
||||||
@@ -391,7 +391,7 @@ class Tall(Layout):
|
|||||||
|
|
||||||
def drag_resize_target_windows(
|
def drag_resize_target_windows(
|
||||||
self, click_window: WindowType, x: float, y: float, edges: int, all_windows: WindowList,
|
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)
|
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;
|
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*
|
static Window*
|
||||||
window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border) {
|
window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border) {
|
||||||
Region central, tab_bar;
|
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) {
|
if (in_central && w->num_tabs > 0) {
|
||||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
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;
|
*window_border = 0;
|
||||||
double dpi = (w->fonts_data->logical_dpi_x + w->fonts_data->logical_dpi_y) / 2.;
|
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))));
|
double tolerance = ((long)round((OPT(window_drag_tolerance) * (dpi / 72.0))));
|
||||||
|
|||||||
@@ -562,6 +562,12 @@ class Tab: # {{{
|
|||||||
return None
|
return None
|
||||||
return 'Could not resize'
|
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', '''
|
@ac('win', '''
|
||||||
Resize the active window by the specified amount
|
Resize the active window by the specified amount
|
||||||
|
|
||||||
|
|||||||
@@ -243,19 +243,23 @@ class NeighborsMap(TypedDict, total=False):
|
|||||||
bottom: list[int]
|
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):
|
class WindowResizeDrag(NamedTuple):
|
||||||
is_active: bool = False
|
is_active: bool = False
|
||||||
cell_width: int = 0
|
cell_width: int = 0
|
||||||
cell_height: int = 0
|
cell_height: int = 0
|
||||||
horizontal_target_window_id: int = 0
|
|
||||||
vertical_target_window_id: int = 0
|
|
||||||
initial_x: float = 0
|
initial_x: float = 0
|
||||||
initial_y: float = 0
|
initial_y: float = 0
|
||||||
last_step_x: float = 0
|
last_step_x: float = 0
|
||||||
last_step_y: float = 0
|
last_step_y: float = 0
|
||||||
height_increases_downwards: bool = True
|
|
||||||
width_increases_rightwards: bool = True
|
|
||||||
tab_id: int = 0
|
tab_id: int = 0
|
||||||
|
data: WindowResizeDragData = WindowResizeDragData()
|
||||||
|
|
||||||
def __bool__(self) -> bool:
|
def __bool__(self) -> bool:
|
||||||
return self.is_active
|
return self.is_active
|
||||||
|
|||||||
Reference in New Issue
Block a user