From 2a714928dbd8dc3274a3276fa1cbbcaf0d542ee4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Feb 2025 10:32:54 +0530 Subject: [PATCH] When dragging in rectangle select mode use a crosshair mouse cursor --- docs/changelog.rst | 2 ++ gen/cursors.py | 2 +- kitty/glfw.c | 2 +- kitty/mouse.c | 12 +++++---- kitty/options/definition.py | 45 +++---------------------------- kitty/options/parse.py | 13 +++------ kitty/options/to-c-generated.h | 2 +- kitty/options/to-c.h | 6 +++++ kitty/options/types.py | 3 +-- kitty/options/utils.py | 49 ++++++++++++++++++++++++++++++++++ kitty/state.h | 2 +- 11 files changed, 77 insertions(+), 61 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 16d6f15df..e44cc91d6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -228,6 +228,8 @@ Detailed list of changes - hints/unicode_input kittens: Do not lose keypresses that are sent very rapidly via an automation tool immediately after the kitten is launched (:iss:`7089`) +- When dragging in rectangle select mode use a crosshair mouse cursor configurable via :opt:`pointer_shape_when_dragging` + 0.37.0 [2024-10-30] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/gen/cursors.py b/gen/cursors.py index f3cb3465b..570a44115 100755 --- a/gen/cursors.py +++ b/gen/cursors.py @@ -118,7 +118,7 @@ def main(args: list[str]=sys.argv) -> None: patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map)) patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map)) patch_file( - 'kitty/options/definition.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map), + 'kitty/options/utils.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map), start_marker='# ', end_marker='', ) patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join( diff --git a/kitty/glfw.c b/kitty/glfw.c index 3a9501602..aa5784a21 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -734,7 +734,7 @@ set_glfw_mouse_pointer_shape_in_window(GLFWwindow *w, MouseShape type) { case NO_DROP_POINTER: set_glfw_mouse_cursor(w, GLFW_NO_DROP_CURSOR); break; case GRAB_POINTER: set_glfw_mouse_cursor(w, GLFW_GRAB_CURSOR); break; case GRABBING_POINTER: set_glfw_mouse_cursor(w, GLFW_GRABBING_CURSOR); break; - /* end enum to glfw */ +/* end enum to glfw */ } } diff --git a/kitty/mouse.c b/kitty/mouse.c index aaedf46f3..d95c0df47 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -263,9 +263,11 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_ #define HANDLER(name) static void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) static void -set_mouse_cursor_when_dragging(void) { - if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) { - mouse_cursor_shape = OPT(pointer_shape_when_dragging); +set_mouse_cursor_when_dragging(Screen *screen) { + MouseShape expected_shape = OPT(pointer_shape_when_dragging); + if (screen && screen->selections.count && screen->selections.items[0].rectangle_select) expected_shape = OPT(pointer_shape_when_dragging_rectangle); + if (mouse_cursor_shape != expected_shape) { + mouse_cursor_shape = expected_shape; set_mouse_cursor(mouse_cursor_shape); } } @@ -276,7 +278,7 @@ update_drag(Window *w) { if (screen && 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){0}); } - set_mouse_cursor_when_dragging(); + set_mouse_cursor_when_dragging(screen); } static bool @@ -759,7 +761,7 @@ mouse_selection(Window *w, int code, int button) { extend_selection(w, false, false); break; } - set_mouse_cursor_when_dragging(); + set_mouse_cursor_when_dragging(screen); #undef S } diff --git a/kitty/options/definition.py b/kitty/options/definition.py index f17a0742e..0ff6dea03 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -7,6 +7,7 @@ import string from kitty.conf.types import Action, Definition from kitty.constants import website_url +from kitty.options.utils import pointer_shape_names definition = Definition( 'kitty', @@ -679,43 +680,6 @@ mouse enters it. ''' ) -pointer_shape_names = ( -# start pointer shape names (auto generated by gen-key-constants.py do not edit) - 'arrow', - 'beam', - 'text', - 'pointer', - 'hand', - 'help', - 'wait', - 'progress', - 'crosshair', - 'cell', - 'vertical-text', - 'move', - 'e-resize', - 'ne-resize', - 'nw-resize', - 'n-resize', - 'se-resize', - 'sw-resize', - 's-resize', - 'w-resize', - 'ew-resize', - 'ns-resize', - 'nesw-resize', - 'nwse-resize', - 'zoom-in', - 'zoom-out', - 'alias', - 'copy', - 'not-allowed', - 'no-drop', - 'grab', - 'grabbing', -# end pointer shape names -) - opt('pointer_shape_when_grabbed', 'arrow', choices=pointer_shape_names, ctype='pointer_shape', long_text=''' @@ -731,10 +695,9 @@ The default shape of the mouse pointer. ''' ) -opt('pointer_shape_when_dragging', 'beam', - choices=pointer_shape_names, ctype='pointer_shape', - long_text=''' -The default shape of the mouse pointer when dragging across text. +opt('pointer_shape_when_dragging', 'beam crosshair', option_type='pointer_shape_when_dragging', ctype='!dragging_pointer_shape', long_text=''' +The default shape of the mouse pointer when dragging across text. The optional second value +sets the shape when dragging in rectangular selection mode. ''' ) diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 6659bf577..25d6c0232 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -16,9 +16,9 @@ from kitty.options.utils import ( edge_width, env, filter_notification, font_features, hide_window_decorations, macos_option_as_alt, macos_titlebar_color, menu_map, modify_font, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec, parse_map, parse_mouse_map, paste_actions, - remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size, - shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge, - tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, + pointer_shape_when_dragging, remote_control_password, resize_debounce_time, scrollback_lines, + scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol, + tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator, tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size, to_layout_names, to_modifiers, transparent_background_colors, underline_exclusion, url_prefixes, url_style, visual_bell_duration, visual_window_select_characters, window_border_width, @@ -1158,12 +1158,7 @@ class Parser: choices_for_placement_strategy = frozenset(('top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right')) def pointer_shape_when_dragging(self, val: str, ans: dict[str, typing.Any]) -> None: - val = val.lower() - if val not in self.choices_for_pointer_shape_when_dragging: - raise ValueError(f"The value {val} is not a valid choice for pointer_shape_when_dragging") - ans["pointer_shape_when_dragging"] = val - - choices_for_pointer_shape_when_dragging = choices_for_default_pointer_shape + ans['pointer_shape_when_dragging'] = pointer_shape_when_dragging(val) def pointer_shape_when_grabbed(self, val: str, ans: dict[str, typing.Any]) -> None: val = val.lower() diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index 28e9cbdca..b1ac92ef1 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -501,7 +501,7 @@ convert_from_opts_default_pointer_shape(PyObject *py_opts, Options *opts) { static void convert_from_python_pointer_shape_when_dragging(PyObject *val, Options *opts) { - opts->pointer_shape_when_dragging = pointer_shape(val); + dragging_pointer_shape(val, opts); } static void diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index 6609a5f60..bc959f19f 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -300,6 +300,12 @@ pointer_shape(PyObject *shape_name) { return TEXT_POINTER; } +static inline void +dragging_pointer_shape(PyObject *parts, Options *opts) { + opts->pointer_shape_when_dragging = pointer_shape(PyTuple_GET_ITEM(parts, 0)); + opts->pointer_shape_when_dragging_rectangle = pointer_shape(PyTuple_GET_ITEM(parts, 1)); +} + static inline int macos_colorspace(PyObject *csname) { if (PyUnicode_CompareWithASCIIString(csname, "srgb") == 0) return 1; diff --git a/kitty/options/types.py b/kitty/options/types.py index b1315c4b9..ae04efde0 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -26,7 +26,6 @@ choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11'] choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3'] choices_for_macos_show_window_title_in = typing.Literal['all', 'menubar', 'none', 'window'] choices_for_placement_strategy = typing.Literal['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'] -choices_for_pointer_shape_when_dragging = choices_for_default_pointer_shape choices_for_pointer_shape_when_grabbed = choices_for_default_pointer_shape choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart'] choices_for_tab_bar_align = typing.Literal['left', 'center', 'right'] @@ -572,7 +571,7 @@ class Options: open_url_with: list[str] = ['default'] paste_actions: frozenset[str] = frozenset({'confirm', 'quote-urls-at-prompt'}) placement_strategy: choices_for_placement_strategy = 'center' - pointer_shape_when_dragging: choices_for_pointer_shape_when_dragging = 'beam' + pointer_shape_when_dragging: tuple[str, str] = ('beam', 'crosshair') pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow' remember_window_size: bool = True repaint_delay: int = 10 diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 79e3a59cd..cf90c7503 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -1581,6 +1581,55 @@ def visual_bell_duration(spec: str) -> tuple[float, EasingFunction, EasingFuncti return parse_animation(spec, interval=0.) +pointer_shape_names = ( +# start pointer shape names (auto generated by gen-key-constants.py do not edit) + 'arrow', + 'beam', + 'text', + 'pointer', + 'hand', + 'help', + 'wait', + 'progress', + 'crosshair', + 'cell', + 'vertical-text', + 'move', + 'e-resize', + 'ne-resize', + 'nw-resize', + 'n-resize', + 'se-resize', + 'sw-resize', + 's-resize', + 'w-resize', + 'ew-resize', + 'ns-resize', + 'nesw-resize', + 'nwse-resize', + 'zoom-in', + 'zoom-out', + 'alias', + 'copy', + 'not-allowed', + 'no-drop', + 'grab', + 'grabbing', +# end pointer shape names +) + + +def pointer_shape_when_dragging(spec: str) -> tuple[str, str]: + parts = spec.split(maxsplit=1) + first = parts[0] + if first not in pointer_shape_names: + raise ValueError(f'{first} is not a valid pointer shape name') + second = parts[1] if len(parts) > 1 else first + if second not in pointer_shape_names: + raise ValueError(f'{second} is not a valid pointer shape name') + return first, second + + def transparent_background_colors(spec: str) -> tuple[tuple[Color, float], ...]: if not spec: return () diff --git a/kitty/state.h b/kitty/state.h index 47e1db837..2b11c6777 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -90,7 +90,7 @@ typedef struct { struct { monotonic_t on_end, on_pause; } resize_debounce_time; MouseShape pointer_shape_when_grabbed; MouseShape default_pointer_shape; - MouseShape pointer_shape_when_dragging; + MouseShape pointer_shape_when_dragging, pointer_shape_when_dragging_rectangle; struct { UrlPrefix *values; size_t num, max_prefix_len;