From 3d369f8632ea286523e93b974ff7710c3056f491 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Mar 2026 13:26:56 +0530 Subject: [PATCH] Cleanup handling of drag_overlay_mode --- kitty/layout/base.py | 27 ++++++++++++++++----------- kitty/layout/grid.py | 4 ++-- kitty/layout/splits.py | 6 +++--- kitty/layout/tall.py | 7 ++++--- kitty/layout/vertical.py | 8 ++++---- kitty/tabs.py | 40 ++++++++++++++++++++-------------------- kitty/window_list.py | 2 +- 7 files changed, 50 insertions(+), 44 deletions(-) diff --git a/kitty/layout/base.py b/kitty/layout/base.py index 08bade8f6..ffd911fcf 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -2,9 +2,10 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Generator, Iterable, Iterator, Sequence +from enum import Enum from functools import partial from itertools import repeat -from typing import Any, Callable, ClassVar, Literal, NamedTuple +from typing import Any, Callable, ClassVar, 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 @@ -225,6 +226,14 @@ def create_window_id_map_for_unserialize(all_windows: WindowList) -> dict[int, i return window_id_map +class DragOverlayMode(Enum): + ' Controls drag-and-drop overlay display and valid direction axis for body drops ' + full = 'full' # full-window overlay, positional swap (Stack and any unrecognised layout) + axis_x = 'axis_x' # top/bottom halves only (Vertical, Tall, Grid) + axis_y = 'axis_y' # left/right halves only (Horizontal, Fat) + free = 'free' # 4-way free direction (Splits; handled by its own insert_window_next_to override) + + class Layout: name: str = '' @@ -232,12 +241,7 @@ class Layout: must_draw_borders = False # can be overridden to customize behavior from kittens layout_opts = LayoutOpts({}) only_active_window_visible = False - # Controls drag-and-drop overlay display and valid direction axis for body drops. - # 'full' – full-window overlay, positional swap (Stack and any unrecognised layout) - # 'axis_y' – top/bottom halves only (Vertical, Tall, Grid) - # 'axis_x' – left/right halves only (Horizontal, Fat) - # 'free' – 4-way free direction (Splits; handled by its own insert_window_next_to override) - drag_overlay_mode: ClassVar[Literal['full', 'axis_y', 'axis_x', 'free']] = 'full' + drag_overlay_mode: ClassVar[DragOverlayMode] = DragOverlayMode.full def __init__(self, os_window_id: int, tab_id: int, layout_opts: str = '') -> None: self.set_owner(os_window_id, tab_id) @@ -317,18 +321,19 @@ def insert_window_next_to( horizontal: bool, after: bool, ) -> None: - """Reposition window as a linear neighbour of next_to. + ''' + Reposition window as a linear neighbour of next_to. For axis_x/axis_y layouts this performs a positional insert that preserves the order of all other groups. For 'full' layouts it falls back to a swap. The Splits layout overrides this with tree-based logic. - """ + ''' src_wg = all_windows.group_for_window(window) dest_wg = all_windows.group_for_window(next_to) if src_wg is None or dest_wg is None or src_wg.id == dest_wg.id: return all_windows.set_active_window_group_for(window) - if self.drag_overlay_mode in ('axis_x', 'axis_y'): + if self.drag_overlay_mode in (DragOverlayMode.axis_x, DragOverlayMode.axis_y): all_windows.insert_window_group_next_to(dest_wg.id, after) else: # 'full' fallback: swap (preserves existing behaviour for Stack etc.) @@ -402,7 +407,7 @@ def __call__(self, all_windows: WindowList) -> None: # Set show_title_bar flag on each visible window before layout min_windows = get_options().window_title_bar_min_windows visible_groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True)) - force_show = getattr(all_windows, '_force_show_title_bars', False) + force_show = all_windows.force_show_title_bars show_title_bar = force_show or (min_windows > 0 and len(visible_groups) >= min_windows) for wg in visible_groups: for w in wg.windows: diff --git a/kitty/layout/grid.py b/kitty/layout/grid.py index 5a7fcda9c..3060f0b43 100644 --- a/kitty/layout/grid.py +++ b/kitty/layout/grid.py @@ -12,7 +12,7 @@ from kitty.typing_compat import WindowType from kitty.window_list import WindowGroup, WindowList -from .base import BorderLine, Layout, LayoutData, LayoutDimension, ListOfWindows, layout_dimension, lgd +from .base import BorderLine, DragOverlayMode, Layout, LayoutData, LayoutDimension, ListOfWindows, layout_dimension, lgd from .tall import neighbors_for_tall_window @@ -34,7 +34,7 @@ class Grid(Layout): name: str = 'grid' no_minimal_window_borders = True - drag_overlay_mode = 'axis_y' + drag_overlay_mode = DragOverlayMode.axis_y def remove_all_biases(self) -> bool: self.biased_rows: dict[int, float] = {} diff --git a/kitty/layout/splits.py b/kitty/layout/splits.py index 67fa6ff25..3e8283f1b 100644 --- a/kitty/layout/splits.py +++ b/kitty/layout/splits.py @@ -10,7 +10,7 @@ from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList -from .base import BorderLine, Layout, LayoutOpts, blank_rects_for_window, lgd, window_geometry_from_layouts +from .base import BorderLine, DragOverlayMode, Layout, LayoutOpts, blank_rects_for_window, lgd, window_geometry_from_layouts class SerializedPair(TypedDict, total=False): @@ -561,7 +561,7 @@ class Splits(Layout): needs_all_windows = True layout_opts = SplitsLayoutOpts({}) no_minimal_window_borders = True - drag_overlay_mode = 'free' + drag_overlay_mode = DragOverlayMode.free @property def default_axis_is_horizontal(self) -> bool | None: @@ -731,7 +731,7 @@ def insert_window_next_to( horizontal: bool, after: bool ) -> None: - """Reposition an existing window as a split adjacent to next_to.""" + ''' Reposition an existing window as a split adjacent to next_to ''' src_wg = all_windows.group_for_window(window) dest_wg = all_windows.group_for_window(next_to) if src_wg is None or dest_wg is None or src_wg.id == dest_wg.id: diff --git a/kitty/layout/tall.py b/kitty/layout/tall.py index d2f6515e2..3c8b95dd7 100644 --- a/kitty/layout/tall.py +++ b/kitty/layout/tall.py @@ -4,7 +4,7 @@ import sys from collections.abc import Generator, Iterator, Sequence from itertools import islice, repeat -from typing import Any, ClassVar, Literal +from typing import Any from kitty.borders import BorderColor from kitty.conf.utils import to_bool @@ -15,6 +15,7 @@ from .base import ( BorderLine, + DragOverlayMode, Layout, LayoutData, LayoutDimension, @@ -136,7 +137,7 @@ class Tall(Layout): name = 'tall' main_is_horizontal = True no_minimal_window_borders = True - drag_overlay_mode: ClassVar[Literal['full', 'axis_y', 'axis_x', 'free']] = 'axis_y' + drag_overlay_mode = DragOverlayMode.axis_y layout_opts = TallLayoutOpts({}) main_axis_layout = Layout.xlayout perp_axis_layout = Layout.ylayout @@ -382,6 +383,6 @@ class Fat(Tall): name = 'fat' main_is_horizontal = False - drag_overlay_mode = 'axis_x' + drag_overlay_mode = DragOverlayMode.axis_x main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout diff --git a/kitty/layout/vertical.py b/kitty/layout/vertical.py index 82513f30a..7a3159565 100644 --- a/kitty/layout/vertical.py +++ b/kitty/layout/vertical.py @@ -2,14 +2,14 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal from collections.abc import Generator, Iterable -from typing import Any, ClassVar, Literal +from typing import Any from kitty.borders import BorderColor from kitty.types import Edges, NeighborsMap, WindowMapper from kitty.typing_compat import EdgeLiteral, WindowType from kitty.window_list import WindowGroup, WindowList -from .base import BorderLine, Layout, LayoutData, LayoutDimension, lgd +from .base import BorderLine, DragOverlayMode, Layout, LayoutData, LayoutDimension, lgd def borders( @@ -64,7 +64,7 @@ class Vertical(Layout): name = 'vertical' main_is_horizontal = False no_minimal_window_borders = True - drag_overlay_mode: ClassVar[Literal['full', 'axis_y', 'axis_x', 'free']] = 'axis_y' + drag_overlay_mode = DragOverlayMode.axis_y main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout @@ -156,6 +156,6 @@ class Horizontal(Vertical): name = 'horizontal' main_is_horizontal = True - drag_overlay_mode = 'axis_x' + drag_overlay_mode = DragOverlayMode.axis_x main_axis_layout = Layout.xlayout perp_axis_layout = Layout.ylayout diff --git a/kitty/tabs.py b/kitty/tabs.py index 3c141c443..d4a0dd1c0 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -54,7 +54,7 @@ swap_tabs, sync_os_window_title, ) -from .layout.base import Layout +from .layout.base import DragOverlayMode, Layout from .layout.interface import create_layout_object_for, evict_cached_layouts from .progress import ProgressState from .tab_bar import TabBar, TabBarData, apply_title_template @@ -466,9 +466,9 @@ def on_bell(self, window: Window) -> None: def relayout(self) -> None: if self.allow_relayouts: if self.windows: - self.windows._force_show_title_bars = self.force_show_title_bars + self.windows.force_show_title_bars = self.force_show_title_bars self.current_layout(self.windows) - self.windows._force_show_title_bars = False + self.windows.force_show_title_bars = False self.relayout_borders() def relayout_borders(self) -> None: @@ -1959,21 +1959,21 @@ def on_window_drop_move(self, window_id: int = 0, is_dest: bool = False, x: int return active_tab = self.active_tab if active_tab is not None: - mode = active_tab.current_layout.drag_overlay_mode rel_x = x - central.left g = dest_window.geometry dx = rel_x - (g.left + g.right) / 2 dy = rel_y - (g.top + g.bottom) / 2 quad_map = {'left': 1, 'right': 2, 'top': 3, 'bottom': 4} - if mode == 'axis_y': - direction = 'bottom' if dy > 0 else 'top' - elif mode == 'axis_x': - direction = 'right' if dx > 0 else 'left' - elif mode == 'free': - direction = ('right' if dx > 0 else 'left') if abs(dx) >= abs(dy) else ('bottom' if dy > 0 else 'top') - else: # 'full' (Stack, etc.): full-window overlay, no directional highlight - self._set_drag_target_window(dest_window.id, 6) - return + match active_tab.current_layout.drag_overlay_mode: + case DragOverlayMode.axis_y: + direction = 'bottom' if dy > 0 else 'top' + case DragOverlayMode.axis_x: + direction = 'right' if dx > 0 else 'left' + case DragOverlayMode.free: + direction = ('right' if dx > 0 else 'left') if abs(dx) >= abs(dy) else ('bottom' if dy > 0 else 'top') + case DragOverlayMode.full: + self._set_drag_target_window(dest_window.id, 6) + return self._set_drag_target_window(dest_window.id, quad_map[direction]) else: self._set_drag_target_window(0) @@ -2042,13 +2042,13 @@ def on_window_drop(self, x: int, y: int, window_id: int) -> None: g = dest_window.geometry dx = rel_x - (g.left + g.right) / 2 dy = rel_y - (g.top + g.bottom) / 2 - mode = active_tab.current_layout.drag_overlay_mode - if mode == 'axis_y': - direction: Literal['left', 'right', 'top', 'bottom'] = 'bottom' if dy > 0 else 'top' - elif mode == 'axis_x': - direction = 'right' if dx > 0 else 'left' - else: # 'free' (Splits) or 'full' (swap fallback) - direction = ('right' if dx > 0 else 'left') if abs(dx) >= abs(dy) else ('bottom' if dy > 0 else 'top') + match active_tab.current_layout.drag_overlay_mode: + case DragOverlayMode.axis_y: + direction: Literal['left', 'right', 'top', 'bottom'] = 'bottom' if dy > 0 else 'top' + case DragOverlayMode.axis_x: + direction = 'right' if dx > 0 else 'left' + case DragOverlayMode.free | DragOverlayMode.full: + direction = ('right' if dx > 0 else 'left') if abs(dx) >= abs(dy) else ('bottom' if dy > 0 else 'top') boss._insert_window_in_direction(w, dest_window, direction) def update_progress(self) -> None: diff --git a/kitty/window_list.py b/kitty/window_list.py index dde3fc193..f29fbd792 100644 --- a/kitty/window_list.py +++ b/kitty/window_list.py @@ -166,7 +166,7 @@ def is_visible_in_layout(self) -> bool: class WindowList: - _force_show_title_bars: bool = False + force_show_title_bars: bool = False def __init__(self, tab: TabType) -> None: self.all_windows: list[WindowType] = []