mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-09 07:07:19 +02:00
Refactor scrollbar mouse handlers into a block
This commit is contained in:
395
kitty/mouse.c
395
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,
|
||||
|
||||
Reference in New Issue
Block a user