diff --git a/kitty/borders.py b/kitty/borders.py index 32463afbb..bf2d9c629 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -24,16 +24,17 @@ class Border(NamedTuple): right: int bottom: int color: BorderColor + is_actual_border: bool = False def vertical_edge(rects: list[Border], color: BorderColor, width: int, top: int, bottom: int, left: int) -> None: if width > 0: - rects.append(Border(left, top, left + width, bottom, color)) + rects.append(Border(left, top, left + width, bottom, color, True)) def horizontal_edge(rects: list[Border], color: BorderColor, height: int, left: int, right: int, top: int) -> None: if height > 0: - rects.append(Border(left, top, right, top + height, color)) + rects.append(Border(left, top, right, top + height, color, True)) def add_borders(rects: list[Border], color: BorderColor, wg: WindowGroup) -> None: @@ -111,10 +112,10 @@ class Borders: if opts.draw_window_borders_for_single_window and num_visible_groups == 1: os_window_focused = current_focused_os_window_id() == self.os_window_id - for i, wg in enumerate(groups): - window_bg = color_as_int(wg.default_bg) - window_bg = (window_bg << 8) | BorderColor.window_bg - if draw_borders and not draw_minimal_borders: + if draw_borders and not draw_minimal_borders: + for i, wg in enumerate(groups): + window_bg = color_as_int(wg.default_bg) + window_bg = (window_bg << 8) | BorderColor.window_bg # Draw the border rectangles if wg is active_group and draw_active_borders and os_window_focused: color = BorderColor.active @@ -124,5 +125,5 @@ class Borders: if draw_minimal_borders: for border_line in current_layout.get_minimal_borders(all_windows): - rects.append(Border(*border_line.edges, border_line.color)) + rects.append(Border(*border_line.edges, border_line.color, is_actual_border=True)) set_borders_rects(self.os_window_id, self.tab_id, rects) diff --git a/kitty/boss.py b/kitty/boss.py index d0e1e393f..b8f6fd24a 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -2419,13 +2419,15 @@ class Boss: if tab: tab.set_active_window(window_id) - def drag_resize_start(self, x: float, y: float, cell_width: int, cell_height: int) -> bool: - if tab := self.active_tab: - horizontal, vertical = tab.current_layout.drag_resize_target_windows(x, y, tab.windows) - if horizontal is None or vertical is None: - return False + def drag_resize_start( + self, horizontal_allowed: bool, vertical_allowed: float, 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, vertical = tab.current_layout.drag_resize_target_windows(w, x, y, tab.windows) self.drag_resize_of_window = WindowResizeDrag( - is_active=True, horizontal_target_window_id=horizontal.id, vertical_target_window_id=vertical.id, + 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, cell_width=cell_width, cell_height=cell_height, initial_x=x, initial_y=y) return True return False diff --git a/kitty/data-types.h b/kitty/data-types.h index 467cdbb9b..600980a7a 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -269,7 +269,7 @@ typedef struct { } ColorProfile; typedef struct { - unsigned int width, height; + unsigned width, height; } CellPixelSize; typedef struct {int x;} *SPRITE_MAP_HANDLE; diff --git a/kitty/layout/base.py b/kitty/layout/base.py index bd0e5e77b..3812bc7f6 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -453,36 +453,26 @@ class Layout: return True def drag_resize_target_windows( - self, x: float, y: float, all_windows: WindowList - ) -> tuple[WindowType | None, WindowType | None]: - # 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: - return None, None + self, click_window: WindowType, x: float, y: float, all_windows: WindowList + ) -> tuple[WindowType, WindowType]: + g = click_window.geometry + left_half_clicked = x <= g.left + (g.right - g.left) / 2 + top_half_clicked = y <= g.top + (g.bottom - g.top) / 2 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", ()) + horizontal_target = vertical_target = click_window # 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]] diff --git a/kitty/mouse.c b/kitty/mouse.c index 6ec66ceb9..e5a8419fc 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -253,6 +253,15 @@ contains_mouse(Window *w) { return (w->visible && window_left(w) <= x && x < window_right(w) && window_top(w) <= y && y < window_bottom(w)); } +static void +border_contains_mouse(BorderRect *br, int tolerance, bool *horizontal, bool *vertical) { + 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 < br->px.right + tolerance && (int)br->px.top - tolerance <= y && y < br->px.bottom + tolerance) { + if (br->px.right - br->px.left > br->px.bottom - br->px.top) *horizontal = true; else *vertical = true; + } +} + + static double distance_to_window(Window *w) { double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; @@ -827,17 +836,6 @@ HANDLER(handle_button_event) { Screen *screen = w->render_data.screen; if (!screen) return; - if (!global_state.active_drag_resize && button == GLFW_MOUSE_BUTTON_LEFT && !is_release && modifiers == GLFW_MOD_CONTROL) { - RAII_PyObject(r, PyObject_CallMethod( - global_state.boss, "drag_resize_start", "ddII", osw->mouse_x, osw->mouse_y, screen->cell_size.width, screen->cell_size.height)); - if (r == NULL) { PyErr_Print(); return; } - if (PyObject_IsTrue(r)) { - global_state.active_drag_resize = w->id; - mouse_cursor_shape = NESW_RESIZE_POINTER; - set_mouse_cursor(mouse_cursor_shape); - return; - } - } bool a, b; if (!set_mouse_position(w, &a, &b)) return; @@ -896,7 +894,7 @@ mouse_in_region(Region *r) { } static Window* -window_for_event(unsigned int *window_idx, bool *in_tab_bar) { +window_for_event(unsigned int *window_idx, bool *in_tab_bar, int *window_border) { Region central, tab_bar; os_window_regions(global_state.callback_os_window, ¢ral, &tab_bar); const bool in_central = mouse_in_region(¢ral); @@ -910,6 +908,16 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) { } if (in_central && global_state.callback_os_window->num_tabs > 0) { Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; + if (window_border) { + bool horizontal = false, vertical = false; + for (unsigned i = 0; i < t->border_rects.num_border_rects && !(horizontal && vertical); i++) { + BorderRect *br = t->border_rects.rect_buf + i; + if (br->is_actual_border) border_contains_mouse(br, 0, &horizontal, &vertical); + } + *window_border = 0; + if (horizontal) *window_border |= 1; + if (vertical) *window_border |= 2; + } for (unsigned int i = 0; i < t->num_windows; i++) { if (contains_mouse(t->windows + i) && t->windows[i].render_data.screen) { *window_idx = i; @@ -943,7 +951,7 @@ focus_in_event(void) { bool in_tab_bar; unsigned int window_idx = 0; mouse_cursor_shape = TEXT_POINTER; - Window *w = window_for_event(&window_idx, &in_tab_bar); + Window *w = window_for_event(&window_idx, &in_tab_bar, NULL); if (w && w->render_data.screen) { screen_mark_url(w->render_data.screen, 0, 0, 0, 0); set_mouse_cursor_for_screen(w->render_data.screen); @@ -956,7 +964,7 @@ update_mouse_pointer_shape(void) { mouse_cursor_shape = TEXT_POINTER; bool in_tab_bar; unsigned int window_idx = 0; - Window *w = window_for_event(&window_idx, &in_tab_bar); + Window *w = window_for_event(&window_idx, &in_tab_bar, NULL); if (in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; } else if (w) { @@ -994,7 +1002,7 @@ enter_event(int modifiers) { // since the last report. if (global_state.redirect_mouse_handling || global_state.active_drag_in_window || global_state.tracked_drag_in_window) return; unsigned window_idx; bool in_tab_bar; - Window *w = window_for_event(&window_idx, &in_tab_bar); + Window *w = window_for_event(&window_idx, &in_tab_bar, NULL); set_currently_hovered_window(w ? w->id : 0, modifiers); if (!w || in_tab_bar) return; @@ -1082,7 +1090,7 @@ mouse_event(const int button, int modifiers, int action) { else { debug("%s mouse_button: %d %s", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", button, format_mods(modifiers)); } } if (global_state.redirect_mouse_handling) { - w = window_for_event(&window_idx, &in_tab_bar); + w = window_for_event(&window_idx, &in_tab_bar, NULL); call_boss(mouse_event, "OK iiii dd", (in_tab_bar ? Py_True : Py_False), (w ? w->id : 0), action, modifiers, button, currently_pressed_button(), @@ -1157,7 +1165,8 @@ mouse_event(const int button, int modifiers, int action) { } return; } - w = window_for_event(&window_idx, &in_tab_bar); + int window_border; + w = window_for_event(&window_idx, &in_tab_bar, &window_border); set_currently_hovered_window(w ? w->id : 0, modifiers); if (in_tab_bar || global_state.tab_being_dragged.id) { @@ -1167,6 +1176,21 @@ mouse_event(const int button, int modifiers, int action) { } else if (w) { debug("grabbed: %d\n", w->render_data.screen->modes.mouse_tracking_mode != 0); handle_event(w, button, modifiers, window_idx); + } else if (window_border) { + debug("window border: %d\n", window_border); + w = window_for_event(&window_idx, &in_tab_bar, NULL); + if (!w) w = closest_window_for_event(&window_idx); + if (window_border & 1) mouse_cursor_shape = window_border & 2 ? NESW_RESIZE_POINTER : NS_RESIZE_POINTER; + else if (window_border & 2) mouse_cursor_shape = EW_RESIZE_POINTER; + if (w && button == GLFW_MOUSE_BUTTON_LEFT && w->render_data.screen) { + RAII_PyObject(r, PyObject_CallMethod( + global_state.boss, "drag_resize_start", "OOddKII", + window_border & 2 ? Py_True : Py_False, window_border & 1 ? Py_True : Py_False, + osw->mouse_x, osw->mouse_y, w->id, + w->render_data.screen->cell_size.width, w->render_data.screen->cell_size.height)); + if (r == NULL) { PyErr_Print(); return; } + if (PyObject_IsTrue(r)) global_state.active_drag_resize = w->id; + } } else if (button == GLFW_MOUSE_BUTTON_LEFT && global_state.callback_os_window->mouse_button_pressed[button]) { // initial click, clamp it to the closest window w = closest_window_for_event(&window_idx); @@ -1273,7 +1297,7 @@ scroll_event(const GLFWScrollEvent *ev) { osw->mouse_x = mouse_x * osw->viewport_x_ratio; osw->mouse_y = mouse_y * osw->viewport_y_ratio; } - Window *w = window_for_event(&window_idx, &in_tab_bar); + Window *w = window_for_event(&window_idx, &in_tab_bar, NULL); if (!w && !in_tab_bar) { // fallback to last active window Tab *t = osw->tabs + osw->active_tab; diff --git a/kitty/state.c b/kitty/state.c index de3b6b768..6d3df8780 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -612,14 +612,16 @@ pyset_borders_rects(PyObject *self UNUSED, PyObject *args) { ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false); for (unsigned i = 0; i < br->num_border_rects; i++) { PyObject *pr = PyList_GET_ITEM(rects, i); - unsigned long left, top, right, bottom, color; - if (!PyArg_ParseTuple(pr, "kkkkk", &left, &top, &right, &bottom, &color)) return NULL; + unsigned long color; int is_actual_border; BorderRect *r = br->rect_buf + i; - r->left = gl_pos_x(left, osw->viewport_width); - r->top = gl_pos_y(top, osw->viewport_height); - r->right = r->left + gl_size(right - left, osw->viewport_width); - r->bottom = r->top - gl_size(bottom - top, osw->viewport_height); - r->color = color; + if (!PyArg_ParseTuple( + pr, "IIIIkp", &r->px.left, &r->px.top, &r->px.right, &r->px.bottom, &color, &is_actual_border + )) 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->is_actual_border = is_actual_border; } END_WITH_TAB Py_RETURN_NONE; diff --git a/kitty/state.h b/kitty/state.h index da34cbbe6..dfeecba43 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -237,7 +237,9 @@ typedef struct Window { typedef struct BorderRect { float left, top, right, bottom; + struct { unsigned left, top, right, bottom; } px; uint32_t color; + bool is_actual_border; } BorderRect; typedef struct BorderRects {