mouse scrolling and selection

This commit is contained in:
Piotr Kocia 2025-06-14 01:59:02 +02:00
parent a163484f25
commit 5fc55b8fbd
7 changed files with 147 additions and 66 deletions

View file

@ -8,6 +8,7 @@
#include "selection.h"
#include "terminal.h"
#include "url-mode.h"
#include "vimode.h"
#include "util.h"
void
@ -47,6 +48,7 @@ cmd_scrollback_up(struct terminal *term, int rows)
selection_view_up(term, new_view);
term->grid->view = new_view;
vimode_view_up(term, 0);
if (rows < term->rows) {
term_damage_scroll(
@ -101,6 +103,7 @@ cmd_scrollback_down(struct terminal *term, int rows)
selection_view_down(term, new_view);
term->grid->view = new_view;
vimode_view_down(term, 0);
if (rows < term->rows) {
term_damage_scroll(

101
input.c
View file

@ -501,11 +501,17 @@ execute_binding(struct seat *seat, struct terminal *term,
term_theme_toggle(term);
return true;
case BIND_ACTION_SELECT_BEGIN:
selection_start(
term, (struct coord){.row = seat->mouse.row, .col = seat->mouse.col}, SELECTION_CHAR_WISE, false);
case BIND_ACTION_SELECT_BEGIN: {
struct coord const point = {.row = seat->mouse.row, .col = seat->mouse.col};
selection_start(term, point, SELECTION_CHAR_WISE, false);
// TODO (kociap): Single click causes selection to start
// instead of just repositioning the cursor.
vimode_mouse_selection_begin(term, point, VI_MODE_VISUAL);
return true;
}
// TODO (kociap): Add vimode selection to the remaining selection
// bindings.
case BIND_ACTION_SELECT_BEGIN_BLOCK:
selection_start(
term, (struct coord){.row = seat->mouse.row, .col = seat->mouse.col}, SELECTION_BLOCK, false);
@ -2566,6 +2572,8 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
break;
case TERM_SURF_GRID:
// TODO (kociap): unsure what this does and when it
// triggers.
selection_finalize(seat, old_moused, seat->pointer.serial);
break;
@ -2721,7 +2729,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
xassert(seat->mouse.row >= 0 && seat->mouse.row < term->rows);
/* Cursor has moved to a different cell since last time */
bool cursor_is_on_new_cell
const bool cursor_is_on_new_cell
= old_col != seat->mouse.col || old_row != seat->mouse.row;
if (cursor_is_on_new_cell) {
@ -2746,47 +2754,49 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
if (auto_scroll_direction == SELECTION_SCROLL_NOT)
selection_stop_scroll_timer(term);
/* Update selection */
if (!term->vimode.active) {
if (auto_scroll_direction != SELECTION_SCROLL_NOT) {
/*
* Start 'selection auto-scrolling'
*
* The speed of the scrolling is proportional to the
* distance between the mouse and the grid; the
* further away the mouse is, the faster we scroll.
*
* Note that the speed is measured in 'intervals (in
* ns) between each timed scroll of a single line'.
*
* Thus, the further away the mouse is, the smaller
* interval value we use.
*/
if (auto_scroll_direction != SELECTION_SCROLL_NOT) {
/*
* Start 'selection auto-scrolling'
*
* The speed of the scrolling is proportional to the
* distance between the mouse and the grid; the
* further away the mouse is, the faster we scroll.
*
* Note that the speed is measured in 'intervals (in
* ns) between each timed scroll of a single line'.
*
* Thus, the further away the mouse is, the smaller
* interval value we use.
*/
int distance = auto_scroll_direction == SELECTION_SCROLL_UP
? term->margins.top - y
: y - (term->height - term->margins.bottom);
int distance = auto_scroll_direction == SELECTION_SCROLL_UP
? term->margins.top - y
: y - (term->height - term->margins.bottom);
xassert(distance > 0);
int divisor
= distance * term->conf->scrollback.multiplier / term->scale;
xassert(distance > 0);
int divisor
= distance * term->conf->scrollback.multiplier / term->scale;
selection_start_scroll_timer(
term, 400000000 / (divisor > 0 ? divisor : 1),
auto_scroll_direction, seat->mouse.col);
}
if (term->selection.ongoing &&
(cursor_is_on_new_cell ||
(term->selection.coords.end.row < 0 &&
seat->mouse.x >= term->margins.left &&
seat->mouse.x < term->width - term->margins.right &&
seat->mouse.y >= term->margins.top &&
seat->mouse.y < term->height - term->margins.bottom)))
{
selection_update(term, (struct coord){.row = seat->mouse.row, .col = seat->mouse.col});
}
selection_start_scroll_timer(
term, 400000000 / (divisor > 0 ? divisor : 1),
auto_scroll_direction, seat->mouse.col);
}
const bool mouse_in_bounds =
seat->mouse.x >= term->margins.left &&
seat->mouse.x < term->width - term->margins.right &&
seat->mouse.y >= term->margins.top &&
seat->mouse.y < term->height - term->margins.bottom;
const bool selection_ongoing =
(!term->vimode.active && term->selection.ongoing) ||
term->vimode.selection.mouse_button_pressed;
if (selection_ongoing && (cursor_is_on_new_cell ||
(term->selection.coords.end.row < 0 && mouse_in_bounds))) {
struct coord point = {.row = seat->mouse.row,
.col = seat->mouse.col};
selection_update(term, point);
vimode_mouse_move(term, point);
}
/* Send mouse event to client application */
if (!term_mouse_grabbed(term, seat) &&
@ -3210,9 +3220,6 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
break;
case TERM_SURF_GRID: {
// TODO (kociap): unsure whether mouse should cancel vimode
// or work in tandem.
vimode_cancel(term);
urls_reset(term);
bool cursor_is_on_grid = seat->mouse.col >= 0 && seat->mouse.row >= 0;
@ -3248,7 +3255,11 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
}
case WL_POINTER_BUTTON_STATE_RELEASED:
selection_finalize(seat, term, serial);
if (!term->vimode.active) {
selection_finalize(seat, term, serial);
} else {
vimode_mouse_selection_end(term);
}
if (send_to_client && !term_mouse_grabbed(term, seat)) {
term_mouse_up(

View file

@ -741,7 +741,7 @@ selection_start(struct terminal *term, struct coord const start,
kind == SELECTION_WORD_WISE ? "word-wise" :
kind == SELECTION_LINE_WISE ? "line-wise" :
kind == SELECTION_BLOCK ? "block" : "<unknown>",
row, col);
start.row, start.col);
term->selection.kind = kind;
term->selection.ongoing = true;

View file

@ -3094,6 +3094,7 @@ term_scroll_partial(struct terminal *term, struct scroll_region region, int rows
/* Cancel selections that cannot be scrolled */
if (unlikely(term->selection.coords.end.row >= 0)) {
// TODO (kociap): selection cancelled on scroll.
/*
* Selection is (partly) inside either the top or bottom
* scrolling regions, or on (at least one) of the lines

View file

@ -634,6 +634,7 @@ struct terminal {
struct coord cursor;
struct {
bool mouse_button_pressed;
struct coord start;
} selection;

View file

@ -460,6 +460,32 @@ void vimode_cancel(struct terminal *term)
render_refresh(term);
}
void vimode_view_up(struct terminal *term, int const delta)
{
if (!term->vimode.active) {
return;
}
damage_cursor_cell(term);
term->vimode.cursor.row += delta;
clip_cursor_to_view(term);
update_selection(term);
update_highlights(term);
}
void vimode_view_down(struct terminal *const term, int const delta)
{
if (!term->vimode.active) {
return;
}
damage_cursor_cell(term);
term->vimode.cursor.row -= delta;
clip_cursor_to_view(term);
update_selection(term);
update_highlights(term);
}
static ssize_t matches_cell(const struct terminal *term,
const struct cell *cell, char32_t const *const buf,
size_t const len, size_t search_ofs)
@ -819,19 +845,6 @@ void vimode_search_add_chars(struct terminal *term, const char *src,
on_search_string_updated(term);
}
void vimode_view_down(struct terminal *const term, int const delta)
{
if (!term->vimode.active) {
return;
}
LOG_DBG("VIMODE VIEW DOWN [delta=%d]", delta);
damage_cursor_cell(term);
term->vimode.cursor.row -= delta;
clip_cursor_to_view(term);
update_highlights(term);
}
enum c32_class {
CLASS_BLANK,
CLASS_PUNCTUATION,
@ -1738,3 +1751,36 @@ void vimode_input(struct seat *seat, struct terminal *term,
}
}
}
void vimode_mouse_selection_begin(struct terminal *const term,
struct coord const point,
enum vi_mode const vmode)
{
if (term->vimode.active == false) {
return;
}
if (!is_mode_visual(vmode)) {
return;
}
term->vimode.selection.mouse_button_pressed = true;
term->vimode.selection.start = point;
term->vimode.mode = vmode;
damage_cursor_cell(term);
term->vimode.cursor = cursor_from_view_relative(term, point);
damage_cursor_cell(term);
}
void vimode_mouse_selection_end(struct terminal *const term)
{
term->vimode.selection.mouse_button_pressed = false;
}
void vimode_mouse_move(struct terminal *const term, struct coord const point)
{
damage_cursor_cell(term);
term->vimode.cursor = cursor_from_view_relative(term, point);
damage_cursor_cell(term);
update_selection(term);
}

View file

@ -7,11 +7,11 @@
void vimode_begin(struct terminal *term);
/* vimode_search_begin
*
* Enter search mode directly without needing to interact with the
* vimode first. Enters vimode as well.
*/
// vimode_search_begin
//
// Enter search mode directly without needing to interact with the
// vimode first. Enters vimode as well.
//
void vimode_search_begin(struct terminal *term);
void vimode_cancel(struct terminal *term);
@ -20,6 +20,27 @@ void vimode_input(struct seat *seat, struct terminal *term,
xkb_keysym_t sym, xkb_mod_mask_t mods,
xkb_mod_mask_t consumed, const xkb_keysym_t *raw_syms,
size_t raw_count, uint32_t serial);
// vimode_mouse_selection_begin
//
// Enter visual selection mode guided by a mouse. The position of the
// vimode cursor is updated to match the position of the mouse.
//
// Does nothing if vimode is not active.
//
// Parameters:
// point - view-relative position of the mouse.
// vmode - the visual mode to be started. If a non-visual mode is
// passed, the function does nothing.
//
void vimode_mouse_selection_begin(struct terminal *term, struct coord point,
enum vi_mode vmode);
void vimode_mouse_selection_end(struct terminal *term);
void vimode_mouse_move(struct terminal *term, struct coord point);
void vimode_view_up(struct terminal *term, int new_view);
void vimode_view_down(struct terminal *term, int new_view);
void vimode_search_add_chars(struct terminal *term, const char *text,
size_t len);
@ -34,5 +55,3 @@ struct search_match_iterator search_matches_new_iter(struct terminal *term,
char32_t const *const buf,
size_t const len);
struct range search_matches_next(struct search_match_iterator *iter);
void vimode_view_down(struct terminal *term, int delta);