From ad560715a63c22685a08622152dc165c8d14d144 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Mar 2026 21:34:48 +0530 Subject: [PATCH] Track border rect orientation explicitly --- kitty/borders.py | 7 +++-- kitty/layout/base.py | 5 +-- kitty/layout/grid.py | 14 ++++----- kitty/layout/splits.py | 18 +++++------ kitty/layout/tall.py | 68 +++++++++++++++------------------------- kitty/layout/vertical.py | 4 +-- kitty/mouse.c | 17 +++++----- kitty/state.c | 5 +-- kitty/state.h | 1 + 9 files changed, 64 insertions(+), 75 deletions(-) diff --git a/kitty/borders.py b/kitty/borders.py index a917ddfc9..bdd85af55 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -25,16 +25,17 @@ class Border(NamedTuple): bottom: int color: BorderColor border_type: int = 0 + horizontal: bool = False def vertical_edge(rects: list[Border], color: BorderColor, width: int, top: int, bottom: int, left: int, border_type: int) -> None: if width > 0: - rects.append(Border(left, top, left + width, bottom, color, border_type)) + rects.append(Border(left, top, left + width, bottom, color, border_type, False)) def horizontal_edge(rects: list[Border], color: BorderColor, height: int, left: int, right: int, top: int, border_type: int) -> None: if height > 0: - rects.append(Border(left, top, right, top + height, color, border_type)) + rects.append(Border(left, top, right, top + height, color, border_type, True)) def add_borders(rects: list[Border], color: BorderColor, wg: WindowGroup) -> None: @@ -126,5 +127,5 @@ def __call__( if draw_minimal_borders: for border_line in current_layout.get_minimal_borders(all_windows): - rects.append(Border(*border_line.edges, border_line.color, border_line.window_id)) + rects.append(Border(*border_line.edges, border_line.color, border_line.window_id, border_line.horizontal)) set_borders_rects(self.os_window_id, self.tab_id, rects) diff --git a/kitty/layout/base.py b/kitty/layout/base.py index 62d0098dd..b58e7cd0c 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -18,6 +18,7 @@ class BorderLine(NamedTuple): edges: Edges = Edges() color: BorderColor = BorderColor.inactive window_id: int = 0 + horizontal: bool = False class LayoutOpts: @@ -437,11 +438,11 @@ def neighbors_for_window(self, window: WindowType, windows: WindowList) -> Neigh def compute_needs_borders_map(self, all_windows: WindowList) -> dict[int, bool]: return all_windows.compute_needs_borders_map(lgd.draw_active_borders) - def get_minimal_borders(self, windows: WindowList) -> Generator[BorderLine, None, None]: + def get_minimal_borders(self, windows: WindowList) -> Iterator[BorderLine]: self._set_dimensions(windows) yield from self.minimal_borders(windows) - def minimal_borders(self, windows: WindowList) -> Generator[BorderLine, None, None]: + def minimal_borders(self, windows: WindowList) -> Iterator[BorderLine]: yield from () def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> bool | None: diff --git a/kitty/layout/grid.py b/kitty/layout/grid.py index 057030ae1..34caa15f0 100644 --- a/kitty/layout/grid.py +++ b/kitty/layout/grid.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Kovid Goyal -from collections.abc import Callable, Generator, Sequence +from collections.abc import Callable, Generator, Iterator, Sequence from functools import lru_cache from itertools import repeat from math import ceil, floor @@ -192,7 +192,7 @@ def position_window_in_grid_cell(window_idx: int, xl: LayoutData, yl: LayoutData n, nrows, ncols, special_rows, special_col, on_col_done): position_window_in_grid_cell(window_idx, xl, yl) - def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: + def minimal_borders(self, all_windows: WindowList) -> Iterator[BorderLine]: n = all_windows.num_groups if not lgd.draw_minimal_borders or n < 2: return @@ -231,7 +231,7 @@ def on_col_done(col_windows: list[int]) -> None: def ends(yl: LayoutData) -> tuple[int, int]: return yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after - def borders_for_window(gid: int, color: BorderColor, wid: int) -> Generator[BorderLine, None, None]: + def borders_for_window(gid: int, color: BorderColor, wid: int) -> Iterator[BorderLine]: xl, yl = layout_data_map[gid] left, right = ends(xl) top, bottom = ends(yl) @@ -240,15 +240,15 @@ def borders_for_window(gid: int, color: BorderColor, wid: int) -> Generator[Bord # Horizontal if not first_row: - yield BorderLine(Edges(left, top, right, top + bw), color, -wid) + yield BorderLine(Edges(left, top, right, top + bw), color, -wid, True) if not last_row: - yield BorderLine(Edges(left, bottom - bw, right, bottom), color, wid) + yield BorderLine(Edges(left, bottom - bw, right, bottom), color, wid, True) # Vertical if not first_column: - yield BorderLine(Edges(left, top, left + bw, bottom), color, -wid) + yield BorderLine(Edges(left, top, left + bw, bottom), color, -wid, False) if not last_column: - yield BorderLine(Edges(right - bw, top, right, bottom), color, wid) + yield BorderLine(Edges(right - bw, top, right, bottom), color, wid, False) for wg in all_groups_in_order: color = BorderColor.inactive diff --git a/kitty/layout/splits.py b/kitty/layout/splits.py index b636d7a0d..e331c0a73 100644 --- a/kitty/layout/splits.py +++ b/kitty/layout/splits.py @@ -273,7 +273,7 @@ def layout_pair( self.one.layout_pair(left, top, w1, height, id_window_map, layout_object) if bw: for etop, ebottom, window_id in self.one.edge_border(RIGHT_EDGE, id_window_map): - one.append(BorderLine(Edges(bleft, etop, bleft + bw, ebottom), window_id=window_id)) + one.append(BorderLine(Edges(bleft, etop, bleft + bw, ebottom), window_id=window_id, horizontal=False)) else: wg = id_window_map[self.one] yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height, border_mult=border_mult)) @@ -281,18 +281,18 @@ def layout_pair( geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.one, geom, id_window_map, layout_object) if bw: - one.append(BorderLine(Edges(bleft, top, bleft + bw, top + height), window_id=wg.active_window_id)) + one.append(BorderLine(Edges(bleft, top, bleft + bw, top + height), window_id=wg.active_window_id, horizontal=False)) left += w1 + bw2 self.second_extent = Edges(left, top, left + w2, top + height) if isinstance(self.two, Pair): self.two.layout_pair(left, top, w2, height, id_window_map, layout_object) if bw: for etop, ebottom, window_id in self.two.edge_border(LEFT_EDGE, id_window_map): - two.append(BorderLine(Edges(left - bw, etop, left, ebottom), window_id=window_id)) + two.append(BorderLine(Edges(left - bw, etop, left, ebottom), window_id=window_id, horizontal=False)) else: wg = id_window_map[self.two] if bw: - two.append(BorderLine(Edges(left - bw, top, left, top + height), window_id=-wg.active_window_id)) + two.append(BorderLine(Edges(left - bw, top, left, top + height), window_id=-wg.active_window_id, horizontal=False)) xl = next(layout_object.xlayout(iter((wg,)), start=left, size=w2, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top, size=height, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) @@ -311,7 +311,7 @@ def layout_pair( self.one.layout_pair(left, top, width, h1, id_window_map, layout_object) if bw: for eleft, eright, window_id in self.one.edge_border(BOTTOM_EDGE, id_window_map): - one.append(BorderLine(Edges(eleft, btop, eright, btop + bw), window_id=window_id)) + one.append(BorderLine(Edges(eleft, btop, eright, btop + bw), window_id=window_id, horizontal=True)) else: wg = id_window_map[self.one] xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width, border_mult=border_mult)) @@ -319,18 +319,18 @@ def layout_pair( geom = window_geometry_from_layouts(xl, yl) self.apply_window_geometry(self.one, geom, id_window_map, layout_object) if bw: - one.append(BorderLine(Edges(left, btop, left + width, btop + bw), window_id=wg.active_window_id)) + one.append(BorderLine(Edges(left, btop, left + width, btop + bw), window_id=wg.active_window_id, horizontal=True)) top += bw2 + h1 self.second_extent = Edges(left, top, left + width, top + h2) if isinstance(self.two, Pair): self.two.layout_pair(left, top, width, h2, id_window_map, layout_object) if bw: for eleft, eright, window_id in self.two.edge_border(TOP_EDGE, id_window_map): - two.append(BorderLine(Edges(eleft, top - bw, eright, top), window_id=window_id)) + two.append(BorderLine(Edges(eleft, top - bw, eright, top), window_id=window_id, horizontal=True)) else: wg = id_window_map[self.two] if bw: - two.append(BorderLine(Edges(left, top - bw, left + width, top), window_id=-wg.active_window_id)) + two.append(BorderLine(Edges(left, top - bw, left + width, top), window_id=-wg.active_window_id, horizontal=True)) xl = next(layout_object.xlayout(iter((wg,)), start=left, size=width, border_mult=border_mult)) yl = next(layout_object.ylayout(iter((wg,)), start=top, size=h2, border_mult=border_mult)) geom = window_geometry_from_layouts(xl, yl) @@ -630,7 +630,7 @@ def remove_all_biases(self) -> bool: pair.bias = 0.5 return True - def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: + def minimal_borders(self, all_windows: WindowList) -> Iterator[BorderLine]: groups = tuple(all_windows.iter_all_layoutable_groups()) window_count = len(groups) if not lgd.draw_minimal_borders or window_count < 2: diff --git a/kitty/layout/tall.py b/kitty/layout/tall.py index 95e8e1045..d144279f5 100644 --- a/kitty/layout/tall.py +++ b/kitty/layout/tall.py @@ -2,7 +2,7 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal import sys -from collections.abc import Generator, Sequence +from collections.abc import Generator, Iterator, Sequence from itertools import islice, repeat from typing import Any @@ -301,7 +301,7 @@ def layout_action(self, action_name: str, args: Sequence[str], all_windows: Wind return False return None - def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: + def minimal_borders(self, all_windows: WindowList) -> Iterator[BorderLine]: num = all_windows.num_groups if num < 2 or not lgd.draw_minimal_borders: return @@ -321,6 +321,7 @@ def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) active_group = all_windows.active_group mirrored = self.layout_opts.mirrored + print(11111111, self.main_is_horizontal) for wg, xl, yl, is_full_size in layouts: if is_full_size: main_layouts.append((wg, xl, yl)) @@ -328,49 +329,30 @@ def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None color = BorderColor.inactive if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell - if self.main_is_horizontal: - e1 = Edges( - xl.content_pos - xl.space_before, - yl.content_pos - yl.space_before, - xl.content_pos + xl.content_size + xl.space_after, - yl.content_pos - yl.space_before + bw - ) - e3 = Edges( - xl.content_pos - xl.space_before, - yl.content_pos + yl.content_size + yl.space_after - bw, - xl.content_pos + xl.content_size + xl.space_after, - yl.content_pos + yl.content_size + yl.space_after, - ) - e2 = Edges( - xl.content_pos + ((xl.content_size + xl.space_after - bw) if mirrored else -xl.space_before), - yl.content_pos - yl.space_before, - xl.content_pos + ((xl.content_size + xl.space_after) if mirrored else (bw - xl.space_before)), - yl.content_pos + yl.content_size + yl.space_after, - ) - else: - e1 = Edges( - xl.content_pos - xl.space_before, - yl.content_pos - yl.space_before, - xl.content_pos - xl.space_before + bw, - yl.content_pos + yl.content_size + yl.space_after, - ) - e3 = Edges( - xl.content_pos + xl.content_size + xl.space_after - bw, - yl.content_pos - yl.space_before, - xl.content_pos + xl.content_size + xl.space_after, - yl.content_pos + yl.content_size + yl.space_after, - ) - e2 = Edges( - xl.content_pos - xl.space_before, - yl.content_pos + ((yl.content_size + yl.space_after - bw) if mirrored else -yl.space_before), - xl.content_pos + xl.content_size + xl.space_after, - yl.content_pos + ((yl.content_size + yl.space_after) if mirrored else (bw - yl.space_before)), - ) wid = wg.active_window_id mult = 1 if mirrored else -1 - perp_borders.append(BorderLine(e1, color, -wid)) - perp_borders.append(BorderLine(e2, color, mult*wid)) - perp_borders.append(BorderLine(e3, color, wid)) + def h(left: int, right: int, top: int, mult: int) -> None: + e = Edges(left, top, right, top + bw) + perp_borders.append(BorderLine(e, color, mult*wid, True)) + def v(top: int, bottom: int, left: int, mult: int) -> None: + print(f'{left=} {top=} {bottom=} {mult=}') + e = Edges(left, top, left + bw, bottom) + perp_borders.append(BorderLine(e, color, mult*wid, False)) + + if self.main_is_horizontal: + h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, + yl.content_pos - yl.space_before, -1) + v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, + xl.content_pos + ((xl.content_size + xl.space_after - bw) if mirrored else -xl.space_before), mult) + h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, + yl.content_pos + yl.content_size + yl.space_after - bw, 1) + else: + v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, + xl.content_pos - xl.space_before, -1) + h(xl.content_pos - xl.space_before, xl.content_pos + xl.content_size + xl.space_after, + yl.content_pos + ((yl.content_size + yl.space_after - bw) if mirrored else -yl.space_before), mult) + v(yl.content_pos - yl.space_before, yl.content_pos + yl.content_size + yl.space_after, + xl.content_pos + xl.content_size + xl.space_after - bw, 1) mirrored = self.layout_opts.mirrored yield from borders( diff --git a/kitty/layout/vertical.py b/kitty/layout/vertical.py index bcd395788..fdf231db0 100644 --- a/kitty/layout/vertical.py +++ b/kitty/layout/vertical.py @@ -50,8 +50,8 @@ def borders( color = BorderColor.inactive if needs_borders_map.get(wg.id): color = BorderColor.active if wg is active_group else BorderColor.bell - borders.append(BorderLine(e1, color, -wg.active_window_id)) - borders.append(BorderLine(e2, color, wg.active_window_id)) + borders.append(BorderLine(e1, color, -wg.active_window_id, not is_horizontal)) + borders.append(BorderLine(e2, color, wg.active_window_id, not is_horizontal)) last_idx = len(borders) - 1 - end_offset for i, x in enumerate(borders): diff --git a/kitty/mouse.c b/kitty/mouse.c index 3a6a16707..3a7e3921d 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -259,8 +259,7 @@ border_contains_mouse(BorderRect *br, double tolerance, Edge *edges) { double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; if ((int)br->px.left - tolerance <= x && x < (int)br->px.right + tolerance && (int)br->px.top - tolerance <= y && y < (int)br->px.bottom + tolerance) { ans = true; - if (br->px.right - br->px.left < br->px.bottom - br->px.top) - *edges |= br->border_type < 0 ? LEFT_EDGE : RIGHT_EDGE; + if (!br->horizontal) *edges |= br->border_type < 0 ? LEFT_EDGE : RIGHT_EDGE; else *edges |= br->border_type < 0 ? TOP_EDGE : BOTTOM_EDGE; } return ans; @@ -927,18 +926,20 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border double tolerance = ((long)round((OPT(window_drag_tolerance) * (dpi / 72.0)))); BorderRect *closest_vert = NULL, *closest_horiz = NULL; double closest_vert_dist = (double)UINT_MAX, closest_horiz_dist = (double)UINT_MAX; + bool is_within_border_without_tolerance = false; for (unsigned i = 0; i < t->border_rects.num_border_rects; i++) { BorderRect *br = t->border_rects.rect_buf + i; if (!br->border_type) continue; Edge edges = 0; if (border_contains_mouse(br, 0, &edges)) { *window_border |= edges; + is_within_border_without_tolerance = true; if (edges & (LEFT_EDGE | RIGHT_EDGE)) { closest_vert_dist = -1; closest_vert = NULL; } else { closest_horiz_dist = -1; closest_horiz = NULL; } window_id = br->border_type < 0 ? -br->border_type : br->border_type; } else if (border_contains_mouse(br, tolerance, &edges)) { unsigned width = br->px.right - br->px.left, height = br->px.bottom - br->px.top; - if (width < height) { + if (!br->horizontal) { double d = br->px.left + width/2. - w->mouse_x; d = d*d; if (d < closest_vert_dist) { closest_vert_dist = d; closest_vert = br; } } else { @@ -947,10 +948,12 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar, Edge *window_border } } } - if (closest_vert && border_contains_mouse(closest_vert, tolerance, window_border) && !window_id) - window_id = closest_vert->border_type < 0 ? -closest_vert->border_type : closest_vert->border_type; - if (closest_horiz && border_contains_mouse(closest_horiz, tolerance, window_border) && !window_id) - window_id = closest_horiz->border_type < 0 ? -closest_horiz->border_type : closest_horiz->border_type; + if (!is_within_border_without_tolerance) { + if (closest_vert && border_contains_mouse(closest_vert, tolerance, window_border) && !window_id) + window_id = closest_vert->border_type < 0 ? -closest_vert->border_type : closest_vert->border_type; + if (closest_horiz && border_contains_mouse(closest_horiz, tolerance, window_border) && !window_id) + window_id = closest_horiz->border_type < 0 ? -closest_horiz->border_type : closest_horiz->border_type; + } if (*window_border) { if (!window_id) return NULL; for (unsigned int i = 0; i < t->num_windows; i++) diff --git a/kitty/state.c b/kitty/state.c index e0e5fc0cb..56ef919d5 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -614,14 +614,15 @@ pyset_borders_rects(PyObject *self UNUSED, PyObject *args) { PyObject *pr = PyList_GET_ITEM(rects, i); unsigned long color; long long border_type; BorderRect *r = br->rect_buf + i; + int horizontal; if (!PyArg_ParseTuple( - pr, "IIIIkL", &r->px.left, &r->px.top, &r->px.right, &r->px.bottom, &color, &border_type + pr, "IIIIkLp", &r->px.left, &r->px.top, &r->px.right, &r->px.bottom, &color, &border_type, &horizontal )) return NULL; r->left = gl_pos_x(r->px.left, osw->viewport_width); r->top = gl_pos_y(r->px.top, osw->viewport_height); r->right = r->left + gl_size(r->px.right - r->px.left, osw->viewport_width); r->bottom = r->top - gl_size(r->px.bottom - r->px.top, osw->viewport_height); - r->color = color; r->border_type = border_type; + r->color = color; r->border_type = border_type; r->horizontal = horizontal; } END_WITH_TAB Py_RETURN_NONE; diff --git a/kitty/state.h b/kitty/state.h index 38c8a3cc9..b962994ff 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -241,6 +241,7 @@ typedef struct BorderRect { struct { unsigned left, top, right, bottom; } px; uint32_t color; long long border_type; + bool horizontal; } BorderRect; typedef struct BorderRects {