diff --git a/kitty/mouse.c b/kitty/mouse.c index f5e6e865f..8d6fbf32c 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -146,23 +146,6 @@ encode_mouse_scroll(Window *w, int button, int mods) { // }}} -// Scrollbar types and function declarations {{{ -typedef enum { - SCROLLBAR_HIT_NONE, - SCROLLBAR_HIT_TRACK, - SCROLLBAR_HIT_THUMB -} ScrollbarHitType; - -typedef struct { - double left, right, top, bottom; - double width, gap, hitbox_expansion; -} ScrollbarGeometry; - -static bool handle_scrollbar_mouse(Window *w, int button, MouseAction action, int modifiers); -static void handle_scrollbar_drag(Window *w, double mouse_y); -static void end_drag(Window *w); -// }}} - static Window* window_for_id(id_type window_id) { if (global_state.callback_os_window && global_state.callback_os_window->num_tabs) { @@ -436,6 +419,204 @@ set_mouse_position(Window *w, bool *mouse_cell_changed, bool *cell_half_changed) return true; } +// Scrollbar {{{ +typedef enum { + SCROLLBAR_HIT_NONE, + SCROLLBAR_HIT_TRACK, + SCROLLBAR_HIT_THUMB +} ScrollbarHitType; + +typedef struct { + double left, right, top, bottom; + double width, gap, hitbox_expansion; +} ScrollbarGeometry; + + +static bool +validate_scrollbar_state(Window *w) { + return w && w->render_data.screen && + w->render_data.screen->historybuf && + w->render_data.screen->historybuf->count > 0; +} + +static ScrollbarGeometry +calculate_scrollbar_geometry(Window *w) { + ScrollbarGeometry geom = {0}; + if (!w || !w->render_data.screen) return geom; + + WindowGeometry *g = &w->render_data.geometry; + unsigned cell_width = w->render_data.screen->cell_size.width; + geom.width = OPT(scrollbar.width) * cell_width; + geom.gap = OPT(scrollbar.gap) * cell_width; + geom.hitbox_expansion = OPT(scrollbar.hitbox_expansion) * cell_width; + + double right_edge = g->right + g->spaces.right; + geom.left = right_edge - geom.gap - geom.width - geom.hitbox_expansion; + geom.right = right_edge + geom.gap; + geom.top = g->top - g->spaces.top; + geom.bottom = g->bottom + g->spaces.bottom; + + return geom; +} + +static ScrollbarHitType +get_scrollbar_hit_type(Window *w, double mouse_x, double mouse_y) { + if (!w || !validate_scrollbar_state(w)) return SCROLLBAR_HIT_NONE; + + ScrollbarGeometry geom = calculate_scrollbar_geometry(w); + + if (mouse_x < geom.left || mouse_x > geom.right || + mouse_y < geom.top || mouse_y > geom.bottom) { + return SCROLLBAR_HIT_NONE; + } + + OSWindow *os_window = global_state.callback_os_window; + if (!os_window) return SCROLLBAR_HIT_TRACK; + double mouse_window_fraction = mouse_y / os_window->viewport_height; + unsigned cell_width = w->render_data.screen->cell_size.width; + double hitbox_expansion_fraction = (double)(OPT(scrollbar.hitbox_expansion) * cell_width) / os_window->viewport_height; + + if (mouse_window_fraction >= (w->scrollbar.thumb_top - hitbox_expansion_fraction) && + mouse_window_fraction <= (w->scrollbar.thumb_bottom + hitbox_expansion_fraction)) { + return SCROLLBAR_HIT_THUMB; + } + + return SCROLLBAR_HIT_TRACK; +} + +static void +handle_scrollbar_track_click(Window *w, double mouse_y) { + if (!w) return; + Screen *screen = w->render_data.screen; + if (!validate_scrollbar_state(w)) return; + + if (OPT(scrollbar.track_behavior) == SCROLLBAR_TRACK_JUMP) { + ScrollbarGeometry geom = calculate_scrollbar_geometry(w); + double scrollbar_height = geom.bottom - geom.top; + double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; + unsigned int target_scrolled_by = (unsigned int)(screen->historybuf->count * (1.0 - mouse_pane_fraction)); + screen_history_scroll_to_absolute(screen, target_scrolled_by); + } else { + OSWindow *os_window = global_state.callback_os_window; + if (!os_window) return; + double mouse_window_fraction = mouse_y / os_window->viewport_height; + bool click_above_thumb = mouse_window_fraction < w->scrollbar.thumb_top; + screen_history_scroll(screen, SCROLL_PAGE, click_above_thumb); + } +} + +static void +end_drag(Window *w) { + Screen *screen = w->render_data.screen; + global_state.active_drag_in_window = 0; + global_state.active_drag_button = -1; + w->last_drag_scroll_at = 0; + w->scrollbar.is_dragging = false; + + if (global_state.callback_os_window && + get_scrollbar_hit_type(w, + global_state.callback_os_window->mouse_x, + global_state.callback_os_window->mouse_y + ) == SCROLLBAR_HIT_NONE) { + mouse_cursor_shape = TEXT_POINTER; + set_mouse_cursor(mouse_cursor_shape); + } + + if (screen->selections.in_progress) { + screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=true}); + } +} + + +static void +start_scrollbar_drag(Window *w, double mouse_y) { + if (!w) return; + Screen *screen = w->render_data.screen; + if (!validate_scrollbar_state(w)) return; + + ScrollbarGeometry geom = calculate_scrollbar_geometry(w); + double scrollbar_height = geom.bottom - geom.top; + double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; + w->scrollbar.is_dragging = true; + w->scrollbar.drag_start_y = mouse_pane_fraction; + w->scrollbar.drag_start_scrolled_by = screen->scrolled_by; +} + +static void +handle_scrollbar_drag(Window *w, double mouse_y) { + if (!w || !w->scrollbar.is_dragging || !validate_scrollbar_state(w)) return; + Screen *screen = w->render_data.screen; + ScrollbarGeometry geom = calculate_scrollbar_geometry(w); + double scrollbar_height = geom.bottom - geom.top; + double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; + double delta_y = mouse_pane_fraction - w->scrollbar.drag_start_y; + double visible_fraction = (double)screen->lines / (screen->lines + screen->historybuf->count); + unsigned cell_height = screen->cell_size.height; + double min_thumb_height_fraction = ((double)OPT(scrollbar.min_handle_height) * cell_height) / scrollbar_height; + double thumb_height = MAX(min_thumb_height_fraction, visible_fraction); + double available_space = 1.0 - thumb_height; + + if (available_space > 0) { + double scroll_fraction = delta_y / available_space; + double target = w->scrollbar.drag_start_scrolled_by - scroll_fraction * screen->historybuf->count; + unsigned int new_scrolled_by; + if (target < 0) new_scrolled_by = 0; + else if (target > screen->historybuf->count) new_scrolled_by = screen->historybuf->count; + else new_scrolled_by = (unsigned int)target; + + if (new_scrolled_by != screen->scrolled_by) { + screen_history_scroll_to_absolute(screen, new_scrolled_by); + } + } +} + + +static bool +handle_scrollbar_mouse(Window *w, int button, MouseAction action, int modifiers UNUSED) { + if (!w || !OPT(scrollbar.interactive) || !global_state.callback_os_window || global_state.active_drag_in_window == w->id || global_state.tracked_drag_in_window == w->id) return false; + + double mouse_x = global_state.callback_os_window->mouse_x; + double mouse_y = global_state.callback_os_window->mouse_y; + + if (action == MOVE && w->scrollbar.is_dragging) { + handle_scrollbar_drag(w, mouse_y); + mouse_cursor_shape = DEFAULT_POINTER; + set_mouse_cursor(mouse_cursor_shape); + return true; + } + + ScrollbarHitType hit_type = get_scrollbar_hit_type(w, mouse_x, mouse_y); + bool hovering = (hit_type != SCROLLBAR_HIT_NONE); + update_scrollbar_hover_state(w, hovering); + + if (!hovering) return false; + + mouse_cursor_shape = DEFAULT_POINTER; + set_mouse_cursor(mouse_cursor_shape); + + if (button == GLFW_MOUSE_BUTTON_LEFT && action != MOVE) { + bool is_release = (action == RELEASE); + + if (is_release) { + if (w->scrollbar.is_dragging) { + end_drag(w); + } else if (hit_type == SCROLLBAR_HIT_TRACK) { + handle_scrollbar_track_click(w, mouse_y); + } + } else { + if (hit_type == SCROLLBAR_HIT_THUMB) { + start_scrollbar_drag(w, mouse_y); + global_state.active_drag_in_window = w->id; + global_state.active_drag_button = button; + } + } + } + + return true; +} +// }}} + + HANDLER(handle_move_event) { modifiers &= ~GLFW_LOCK_MASK; @@ -811,186 +992,6 @@ enter_event(int modifiers) { if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf); } } -static bool -validate_scrollbar_state(Window *w) { - return w && w->render_data.screen && - w->render_data.screen->historybuf && - w->render_data.screen->historybuf->count > 0; -} - -static ScrollbarGeometry -calculate_scrollbar_geometry(Window *w) { - ScrollbarGeometry geom = {0}; - if (!w || !w->render_data.screen) return geom; - - WindowGeometry *g = &w->render_data.geometry; - unsigned cell_width = w->render_data.screen->cell_size.width; - geom.width = OPT(scrollbar.width) * cell_width; - geom.gap = OPT(scrollbar.gap) * cell_width; - geom.hitbox_expansion = OPT(scrollbar.hitbox_expansion) * cell_width; - - double right_edge = g->right + g->spaces.right; - geom.left = right_edge - geom.gap - geom.width - geom.hitbox_expansion; - geom.right = right_edge + geom.gap; - geom.top = g->top - g->spaces.top; - geom.bottom = g->bottom + g->spaces.bottom; - - return geom; -} - -static ScrollbarHitType -get_scrollbar_hit_type(Window *w, double mouse_x, double mouse_y) { - if (!w || !validate_scrollbar_state(w)) return SCROLLBAR_HIT_NONE; - - ScrollbarGeometry geom = calculate_scrollbar_geometry(w); - - if (mouse_x < geom.left || mouse_x > geom.right || - mouse_y < geom.top || mouse_y > geom.bottom) { - return SCROLLBAR_HIT_NONE; - } - - OSWindow *os_window = global_state.callback_os_window; - if (!os_window) return SCROLLBAR_HIT_TRACK; - double mouse_window_fraction = mouse_y / os_window->viewport_height; - unsigned cell_width = w->render_data.screen->cell_size.width; - double hitbox_expansion_fraction = (double)(OPT(scrollbar.hitbox_expansion) * cell_width) / os_window->viewport_height; - - if (mouse_window_fraction >= (w->scrollbar.thumb_top - hitbox_expansion_fraction) && - mouse_window_fraction <= (w->scrollbar.thumb_bottom + hitbox_expansion_fraction)) { - return SCROLLBAR_HIT_THUMB; - } - - return SCROLLBAR_HIT_TRACK; -} - -static void -handle_scrollbar_track_click(Window *w, double mouse_y) { - if (!w) return; - Screen *screen = w->render_data.screen; - if (!validate_scrollbar_state(w)) return; - - if (OPT(scrollbar.track_behavior) == SCROLLBAR_TRACK_JUMP) { - ScrollbarGeometry geom = calculate_scrollbar_geometry(w); - double scrollbar_height = geom.bottom - geom.top; - double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; - unsigned int target_scrolled_by = (unsigned int)(screen->historybuf->count * (1.0 - mouse_pane_fraction)); - screen_history_scroll_to_absolute(screen, target_scrolled_by); - } else { - OSWindow *os_window = global_state.callback_os_window; - if (!os_window) return; - double mouse_window_fraction = mouse_y / os_window->viewport_height; - bool click_above_thumb = mouse_window_fraction < w->scrollbar.thumb_top; - screen_history_scroll(screen, SCROLL_PAGE, click_above_thumb); - } -} - -static void -start_scrollbar_drag(Window *w, double mouse_y) { - if (!w) return; - Screen *screen = w->render_data.screen; - if (!validate_scrollbar_state(w)) return; - - ScrollbarGeometry geom = calculate_scrollbar_geometry(w); - double scrollbar_height = geom.bottom - geom.top; - double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; - w->scrollbar.is_dragging = true; - w->scrollbar.drag_start_y = mouse_pane_fraction; - w->scrollbar.drag_start_scrolled_by = screen->scrolled_by; -} - -static bool -handle_scrollbar_mouse(Window *w, int button, MouseAction action, int modifiers UNUSED) { - if (!w || !OPT(scrollbar.interactive) || !global_state.callback_os_window || global_state.active_drag_in_window == w->id || global_state.tracked_drag_in_window == w->id) return false; - - double mouse_x = global_state.callback_os_window->mouse_x; - double mouse_y = global_state.callback_os_window->mouse_y; - - if (action == MOVE && w->scrollbar.is_dragging) { - handle_scrollbar_drag(w, mouse_y); - mouse_cursor_shape = DEFAULT_POINTER; - set_mouse_cursor(mouse_cursor_shape); - return true; - } - - ScrollbarHitType hit_type = get_scrollbar_hit_type(w, mouse_x, mouse_y); - bool hovering = (hit_type != SCROLLBAR_HIT_NONE); - update_scrollbar_hover_state(w, hovering); - - if (!hovering) return false; - - mouse_cursor_shape = DEFAULT_POINTER; - set_mouse_cursor(mouse_cursor_shape); - - if (button == GLFW_MOUSE_BUTTON_LEFT && action != MOVE) { - bool is_release = (action == RELEASE); - - if (is_release) { - if (w->scrollbar.is_dragging) { - end_drag(w); - } else if (hit_type == SCROLLBAR_HIT_TRACK) { - handle_scrollbar_track_click(w, mouse_y); - } - } else { - if (hit_type == SCROLLBAR_HIT_THUMB) { - start_scrollbar_drag(w, mouse_y); - global_state.active_drag_in_window = w->id; - global_state.active_drag_button = button; - } - } - } - - return true; -} - -static void -handle_scrollbar_drag(Window *w, double mouse_y) { - if (!w || !w->scrollbar.is_dragging || !validate_scrollbar_state(w)) return; - Screen *screen = w->render_data.screen; - ScrollbarGeometry geom = calculate_scrollbar_geometry(w); - double scrollbar_height = geom.bottom - geom.top; - double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height; - double delta_y = mouse_pane_fraction - w->scrollbar.drag_start_y; - double visible_fraction = (double)screen->lines / (screen->lines + screen->historybuf->count); - unsigned cell_height = screen->cell_size.height; - double min_thumb_height_fraction = ((double)OPT(scrollbar.min_handle_height) * cell_height) / scrollbar_height; - double thumb_height = MAX(min_thumb_height_fraction, visible_fraction); - double available_space = 1.0 - thumb_height; - - if (available_space > 0) { - double scroll_fraction = delta_y / available_space; - double target = w->scrollbar.drag_start_scrolled_by - scroll_fraction * screen->historybuf->count; - unsigned int new_scrolled_by; - if (target < 0) new_scrolled_by = 0; - else if (target > screen->historybuf->count) new_scrolled_by = screen->historybuf->count; - else new_scrolled_by = (unsigned int)target; - - if (new_scrolled_by != screen->scrolled_by) { - screen_history_scroll_to_absolute(screen, new_scrolled_by); - } - } -} - -static void -end_drag(Window *w) { - Screen *screen = w->render_data.screen; - global_state.active_drag_in_window = 0; - global_state.active_drag_button = -1; - w->last_drag_scroll_at = 0; - w->scrollbar.is_dragging = false; - - if (global_state.callback_os_window && - get_scrollbar_hit_type(w, - global_state.callback_os_window->mouse_x, - global_state.callback_os_window->mouse_y - ) == SCROLLBAR_HIT_NONE) { - mouse_cursor_shape = TEXT_POINTER; - set_mouse_cursor(mouse_cursor_shape); - } - - if (screen->selections.in_progress) { - screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, (SelectionUpdate){.ended=true}); - } -} typedef enum MouseSelectionType { MOUSE_SELECTION_NORMAL,