Merge branch 'mouse-drag-resize' of https://github.com/mcrmck/kitty

This commit is contained in:
Kovid Goyal
2026-02-26 13:53:31 +05:30
4 changed files with 140 additions and 1 deletions

View File

@@ -13,6 +13,7 @@ from collections.abc import Callable, Container, Generator, Iterable, Iterator,
from contextlib import contextmanager, suppress from contextlib import contextmanager, suppress
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from math import floor
from gettext import ngettext from gettext import ngettext
from time import sleep from time import sleep
from typing import ( from typing import (
@@ -381,6 +382,7 @@ class Boss:
global_shortcuts: dict[str, SingleKey], global_shortcuts: dict[str, SingleKey],
talk_fd: int = -1, talk_fd: int = -1,
): ):
self.drag_resize_active = False
self.atexit = Atexit() self.atexit = Atexit()
set_layout_options(opts) set_layout_options(opts)
self.clipboard = Clipboard() self.clipboard = Clipboard()
@@ -2415,6 +2417,52 @@ class Boss:
if tab: if tab:
tab.set_active_window(window_id) tab.set_active_window(window_id)
def drag_resize_start(self, x: float, y: float, cell_width: int, cell_height: int) -> None:
tab = self.active_tab
if not tab:
return
(horizontal, vertical) = tab.current_layout.drag_resize_target_windows(x, y, tab.windows)
self.drag_resize_cell_width = cell_width
self.drag_resize_cell_height = cell_height
self.drag_resize_target_horizontal = horizontal
self.drag_resize_target_vertical = vertical
self.drag_resize_active = True
self.drag_resize_initial_x = x
self.drag_resize_initial_y = y
self.drag_resize_last_step_x = 0
self.drag_resize_last_step_y = 0
def drag_resize_update(self, x: float, y: float) -> None:
if not self.drag_resize_active:
return
if self.drag_resize_target_horizontal is not None:
step_x = floor((x - self.drag_resize_initial_x) / self.drag_resize_cell_width)
dx = step_x - self.drag_resize_last_step_x
if not dx == 0:
self.resize_layout_window(self.drag_resize_target_horizontal, float(dx), True, False)
self.drag_resize_last_step_x = step_x
if self.drag_resize_target_vertical is not None:
step_y = floor((y - self.drag_resize_initial_y) / self.drag_resize_cell_height)
dy = step_y - self.drag_resize_last_step_y
if not dy == 0:
self.resize_layout_window(self.drag_resize_target_vertical, float(dy), False, False)
self.drag_resize_last_step_y = step_y
def drag_resize_end(self) -> None:
self.drag_resize_cell_width = 0
self.drag_resize_cell_height = 0
self.drag_resize_target_horizontal = None
self.drag_resize_target_vertical = None
self.drag_resize_active = False
self.drag_resize_initial_x = 0.0
self.drag_resize_initial_y = 0.0
self.drag_resize_last_step_x = 0
self.drag_resize_last_step_y = 0
def open_kitty_website(self) -> None: def open_kitty_website(self) -> None:
self.open_url(website_url()) self.open_url(website_url())

View File

@@ -452,6 +452,43 @@ class Layout:
def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool: def set_layout_state(self, layout_state: dict[str, Any], map_group_id: WindowMapper) -> bool:
return True return True
def drag_resize_target_windows(self, x: float, y: float, all_windows: WindowList) -> tuple[WindowType, WindowType]:
# Identify the window where the click occurred and which horizontal and
# vertical half it was in
click_window, left_half_clicked, top_half_clicked = (None, False, False)
for w in all_windows.all_windows:
g = w.geometry
if x >= g.left and x <= g.right and y >= g.top and y <= g.bottom:
click_window = w
left_half_clicked = g.left <= x and x <= g.left + (float(g.right - g.left) / 2.0)
top_half_clicked = g.top <= y and y <= g.top + (float(g.bottom - g.top) / 2.0)
break
if click_window is None:
raise Exception("Failed to determine click window")
neighbors = self.neighbors_for_window(click_window, all_windows)
left = neighbors.get("left", [])
right = neighbors.get("right", [])
top = neighbors.get("top", [])
bottom = neighbors.get("bottom", [])
# Infer which window should be horizontally resized based on click
# position and layout state
horizontal_target = click_window
if ((left_half_clicked and len(left) > 0) or
(not left_half_clicked and len(left) > 0 and len(right) == 0)):
horizontal_target = all_windows.id_map[left[0]]
# Infer which window should be vertically resized based on click
# position and layout state
vertical_target = click_window
if ((top_half_clicked and len(top) > 0) or
(not top_half_clicked and len(top) > 0 and len(bottom) == 0)):
vertical_target = all_windows.id_map[top[0]]
return (horizontal_target, vertical_target)
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()
ans['opts'] = self.layout_opts.serialized() ans['opts'] = self.layout_opts.serialized()

View File

@@ -923,6 +923,35 @@ closest_window_for_event(unsigned int *window_idx) {
return ans; return ans;
} }
static void
drag_resize_start(double mouse_x, double mouse_y, unsigned int cell_width, unsigned int cell_height) {
call_boss(drag_resize_start, "ddII", mouse_x, mouse_y, cell_width, cell_height);
}
static void
drag_resize_update(double mouse_x, double mouse_y) {
call_boss(drag_resize_update, "dd", mouse_x, mouse_y);
}
static void
drag_resize_end(void) {
call_boss(drag_resize_end, "");
}
static bool
is_in_window(double mouse_x, double mouse_y) {
Tab *active_tab = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < active_tab->num_windows; ++i) {
WindowGeometry *g = &active_tab->windows[i].render_data.geometry;
if (g->left <= mouse_x && mouse_x <= g->right && g->top <= mouse_y && mouse_y <= g->bottom) {
return true;
}
}
return false;
}
void void
focus_in_event(void) { focus_in_event(void) {
// Ensure that no URL is highlighted and the mouse cursor is in default shape // Ensure that no URL is highlighted and the mouse cursor is in default shape
@@ -1060,6 +1089,31 @@ mouse_event(const int button, int modifiers, int action) {
bool in_tab_bar; bool in_tab_bar;
unsigned int window_idx = 0; unsigned int window_idx = 0;
Window *w = NULL; Window *w = NULL;
OSWindow *osw = global_state.callback_os_window;
// Handle mouse drag window resizing
if (!global_state.active_drag_resize && button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS && modifiers & GLFW_MOD_CONTROL) {
if (is_in_window(osw->mouse_x, osw->mouse_y)) {
drag_resize_start(osw->mouse_x, osw->mouse_y, osw->fonts_data->fcm.cell_width, osw->fonts_data->fcm.cell_height);
global_state.active_drag_resize = true;
mouse_cursor_shape = MOVE_POINTER;
set_mouse_cursor(mouse_cursor_shape);
return;
}
} else if (global_state.active_drag_resize) {
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) {
drag_resize_end();
global_state.active_drag_resize = false;
mouse_cursor_shape = DEFAULT_POINTER;
set_mouse_cursor(mouse_cursor_shape);
return;
} else if (button < 0) {
drag_resize_update(osw->mouse_x, osw->mouse_y);
return;
}
}
if (OPT(debug_keyboard)) { if (OPT(debug_keyboard)) {
if (button < 0) { debug("%s x: %.1f y: %.1f ", "\x1b[36mMove\x1b[m", global_state.callback_os_window->mouse_x, global_state.callback_os_window->mouse_y); } if (button < 0) { debug("%s x: %.1f y: %.1f ", "\x1b[36mMove\x1b[m", global_state.callback_os_window->mouse_x, global_state.callback_os_window->mouse_y); }
else { debug("%s mouse_button: %d %s", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", button, format_mods(modifiers)); } else { debug("%s mouse_button: %d %s", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", button, format_mods(modifiers)); }

View File

@@ -367,7 +367,7 @@ typedef struct GlobalState {
bool has_pending_resizes, has_pending_closes; bool has_pending_resizes, has_pending_closes;
bool check_for_active_animated_images; bool check_for_active_animated_images;
struct { double x, y; } default_dpi; struct { double x, y; } default_dpi;
id_type active_drag_in_window, tracked_drag_in_window, mouse_hover_in_window; id_type active_drag_in_window, tracked_drag_in_window, mouse_hover_in_window, active_drag_resize;
int active_drag_button, tracked_drag_button; int active_drag_button, tracked_drag_button;
CloseRequest quit_request; CloseRequest quit_request;
bool redirect_mouse_handling; bool redirect_mouse_handling;