Move back to individual settings for scrollbar

We need to split out the color settings so that they can be set in
themes anyway, so ...
This commit is contained in:
Kovid Goyal 2025-09-14 17:48:59 +05:30
parent 6caa550efd
commit 80260e6eb1
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
15 changed files with 372 additions and 231 deletions

View file

@ -48,7 +48,7 @@ def main(args: list[str]=sys.argv) -> None:
if opt.parser_func.__name__ in ('to_color_or_none', 'cursor_text_color'):
nullable_colors.append(opt.name)
all_colors.append(opt.name)
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color', 'scrollbar_color'):
all_colors.append(opt.name)
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
patch_color_list('tools/themes/collection.go', all_colors, 'ALL')

View file

@ -864,11 +864,6 @@ PyInit_fast_data_types(void) {
PyModule_AddIntMacro(m, COLOR_IS_SPECIAL);
PyModule_AddIntMacro(m, COLOR_IS_INDEX);
PyModule_AddIntMacro(m, COLOR_IS_RGB);
PyModule_AddIntMacro(m, SCROLLBAR_ALWAYS);
PyModule_AddIntMacro(m, SCROLLBAR_NEVER);
PyModule_AddIntMacro(m, SCROLLBAR_ON_HOVERED);
PyModule_AddIntMacro(m, SCROLLBAR_ON_SCROLLED);
PyModule_AddIntMacro(m, SCROLLBAR_ON_SCROLL_AND_HOVER);
#ifdef __APPLE__
// Apple says its SHM_NAME_MAX but SHM_NAME_MAX is not actually declared in typical CrApple style.
// This value is based on experimentation and from qsharedmemory.cpp in Qt

View file

@ -113,7 +113,6 @@ typedef enum MouseShapes {
/* end mouse shapes */
} MouseShape;
typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
typedef enum { SCROLLBAR_TRACK_JUMP, SCROLLBAR_TRACK_PAGE } ScrollbarTrackBehavior;
typedef enum { SCROLLBAR_NEVER, SCROLLBAR_ON_SCROLLED, SCROLLBAR_ON_HOVERED, SCROLLBAR_ON_SCROLL_AND_HOVER, SCROLLBAR_ALWAYS } ScrollbarVisibilityPolicy;
typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED, CENTER_SCALED } BackgroundImageLayout;
typedef struct ImageAnchorPosition {

View file

@ -22,7 +22,7 @@ from .constants import extensions_dir, is_macos, is_wayland, kitty_base_dir, kit
from .fast_data_types import Color, SingleKey, current_fonts, glfw_get_system_color_theme, gpu_driver_version_string, num_users, wayland_compositor_data
from .options.types import Options as KittyOpts
from .options.types import defaults, secret_options
from .options.utils import KeyboardMode, KeyDefinition, ScrollbarSettings
from .options.utils import KeyboardMode, KeyDefinition
from .rgb import color_as_sharp, color_from_int
from .types import MouseEvent, Shortcut, mod_to_names
from .utils import shlex_split
@ -100,10 +100,6 @@ def compare_opts(opts: KittyOpts, global_shortcuts: dict[str, SingleKey] | None,
print(' ', val[k])
else:
print(pformat(val))
elif isinstance(val, ScrollbarSettings):
print(f'{f}:')
for ssd in ScrollbarSettings().differences(val):
print(f'\t{ssd}')
else:
val = getattr(opts, f)
if isinstance(val, Color):

View file

@ -20,11 +20,6 @@ COLOR_IS_SPECIAL: int
COLOR_NOT_SET: int
COLOR_IS_RGB: int
COLOR_IS_INDEX: int
SCROLLBAR_ALWAYS: int
SCROLLBAR_NEVER: int
SCROLLBAR_ON_HOVERED: int
SCROLLBAR_ON_SCROLLED: int
SCROLLBAR_ON_SCROLL_AND_HOVER: int
GLFW_LAYER_SHELL_NONE: int
GLFW_LAYER_SHELL_PANEL: int
GLFW_LAYER_SHELL_TOP: int

View file

@ -446,9 +446,9 @@ calculate_scrollbar_geometry(Window *w) {
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;
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;
@ -474,7 +474,7 @@ get_scrollbar_hit_type(Window *w, double mouse_x, double mouse_y) {
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;
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)) {
@ -490,7 +490,7 @@ handle_scrollbar_track_click(Window *w, double mouse_y) {
Screen *screen = w->render_data.screen;
if (!validate_scrollbar_state(w)) return;
if (OPT(scrollbar.track_behavior) == SCROLLBAR_TRACK_JUMP) {
if (OPT(scrollbar_jump_on_click)) {
ScrollbarGeometry geom = calculate_scrollbar_geometry(w);
double scrollbar_height = geom.bottom - geom.top;
double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height;
@ -552,7 +552,7 @@ handle_scrollbar_drag(Window *w, double mouse_y) {
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 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;
@ -573,7 +573,7 @@ handle_scrollbar_drag(Window *w, double mouse_y) {
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;
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;

View file

@ -7,7 +7,7 @@ import string
from kitty.conf.types import Action, Definition
from kitty.constants import website_url
from kitty.options.utils import default_scrollbar, pointer_shape_names
from kitty.options.utils import pointer_shape_names
definition = Definition(
'kitty',
@ -442,61 +442,76 @@ is changed it will only affect newly created windows, not existing ones.
'''
)
opt('scrollbar', '', option_type='scrollbar', ctype='!scrollbar',
long_text=f'''
Control the appearance and behavior of the scrollbar that is displayed when
scrolling through the scrollback. Accepts a number of space separated settings in key=value
format, documented below. To have the scrollbar always visible, use:
:code:`visible_when=always`. To disable it completely use: :code:`visible_when=never`.
To have it be visible but not react to the mouse, use :code:`interactive=no`. For example:
:code:`visible_when=always interactive=no`.
opt('scrollbar', 'scrolled', ctype='scrollbar', choices=(
'scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered'), long_text='''\
Control when the scrollbar is displayed.
The various settings, with their default values:
:code:`scrolled`
means when the scrolling backwards has started.
:code:`hovered`
means when the mouse is hovering on the right edge of the window.
:code:`scrolled-and-hovered`
means when the mouse is over the scrollbar region *and* scrolling backwards has started.
:code:`always`
means whenever any scrollback is present
:code:`never`
means disable the scrollbar.
''')
**color** :code:`={default_scrollbar.color_as_string(default_scrollbar.color)}`
the color of the scrollbar handle. Use the special keywords: :code:`foreground` or :code:`selection_background` to use those colors.
opt('scrollbar_interactive', 'yes', option_type='to_bool', ctype='bool', long_text='''
If disabled, the scrollbar will not be controllable via th emouse and all mouse events
will pass through the scrollbar.''')
**track_color** :code:`={default_scrollbar.color_as_string(default_scrollbar.color)}`
the color of the scrollbar track. Use the special keywords: :code:`foreground` or :code:`selection_background` to use those colors.
opt('scrollbar_jump_on_click', 'yes', option_type='to_bool', ctype='bool', long_text='''
When enabled clicking in the scrollbar track will cause the scroll position to
jump to the clicked location, otherwise the scroll position will only move
towards the position by a single screenful, which is how traditional scrollbars behave.''')
**opacity** :code:`={default_scrollbar.opacity}`
the opacity of the scrollbar handle (0 invisible to 1 fully opaque)
opt('scrollbar_width', '0.5', option_type='positive_float', ctype='float', long_text='''
The width of the scroll bar in units of cell width.
''')
**track_opacity** :code:`={default_scrollbar.track_opacity}`
the opacity of the scrollbar track
opt('scrollbar_handle_opacity', '0.5', option_type='positive_float', ctype='float', long_text='''
The opacity of the scrollbar handle, 0 being fully transparent and 1 being full opaque.
''')
**track_hover_opacity** :code:`={default_scrollbar.track_opacity}`
the opacity of the scrollbar track when the mouse is over it.
opt('scrollbar_radius', '0.3', option_type='positive_float', ctype='float', long_text='''
The radius (curvature) of the scrollbar handle in units of cell width. Should be less than
:opt:`scrollbar_width`.
''')
**visible_when** :code:`=scrolled`
when the scrollbar is displayed.
:code:`scrolled` means when the scrolling backwards has started.
:code:`hovered` means when the mouse is hovering on the right edge of the window.
:code:`scrolled-and-hovered` means when the mouse is over the scrollbar region *and* scrolling backwards has started.
:code:`always` means whenever any scrollback is present
:code:`never` means disable the scrollbar.
opt('scrollbar_gap', '0.1', option_type='positive_float', ctype='float', long_text='''
The gap between the scrollbar and the window edge in units of cell width.
''')
**interactive** :code:`={'yes' if default_scrollbar.interactive else 'no'}`
whether the scrollbar can be controlled by the mouse.
opt('scrollbar_min_handle_height', '1', option_type='positive_float', ctype='float', long_text='''
The minimum height of the scrollbar handle in units of cell height. Prevents the handle
from becoming too small when there is a lot of scrollback.''')
**track_click** :code:`={'jump' if default_scrollbar.jump_on_track_click else 'page'}`
the behavior of clicking on the scrollbar track. :code:`jump` means scroll to the position of
the click and :code:`page` means scroll by a single screen towards the position of the click.
opt('scrollbar_hitbox_expansion', '0.25', option_type='positive_float', ctype='float', long_text='''
The extra area around the handle to allow easier grabbing of the scollbar in units of cell width.''')
**width** :code:`={default_scrollbar.width}`
the width of the scrollbar in units of cells
opt('scrollbar_track_opacity', '0', option_type='positive_float', ctype='float', long_text='''
The opacity of the scrollbar track, 0 being fully transparent and 1 being full opaque.
''')
**radius** :code:`={default_scrollbar.radius}`
the radius (curvature) of the scrollbar handle in units of cells
opt('scrollbar_track_hover_opacity', '0.1', option_type='positive_float', ctype='float', long_text='''
The opacity of the scrollbar track when the mouse is over the scrollbar,
0 being fully transparent and 1 being full opaque.
''')
**gap** :code:`={default_scrollbar.gap}`
the gap between the scrollbar and the window edge in units of cells
opt('scrollbar_handle_color', 'foreground', option_type='scrollbar_color', ctype='uint', long_text='''
The color of the scrollbar handle. A value of :code:`foreground` means to use
the current foreground text color, a value of :code:`selection_background` means to
use the current selection background color. Also, you can use an
arbitrary color, such as :code:`#12af59` or :code:`red`.
''')
**min_handle_height** :code:`={default_scrollbar.min_handle_height}`
the minimum height of the scrollbar handle in units of cells
**hitbox_expansion** :code:`={default_scrollbar.hitbox_expansion}`
the extra area around the handle to allow easier grabbing of the scollbar in units of cells
opt('scrollbar_track_color', 'foreground', option_type='scrollbar_color', ctype='uint', long_text='''
The color of the scrollbar track. A value of :code:`foreground` means to use
the current foreground text color, a value of :code:`selection_background` means to
use the current selection background color. Also, you can use an
arbitrary color, such as :code:`#12af59` or :code:`red`.
''')
opt('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER',

57
kitty/options/parse.py generated
View file

@ -17,13 +17,13 @@ from kitty.options.utils import (
hide_window_decorations, macos_option_as_alt, macos_titlebar_color, menu_map, modify_font,
mouse_hide_wait, narrow_symbols, notify_on_cmd_finish, optional_edge_width, parse_font_spec,
parse_map, parse_mouse_map, paste_actions, pointer_shape_when_dragging, remote_control_password,
resize_debounce_time, scrollback_lines, scrollback_pager_history_size, scrollbar, 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,
text_fg_override_threshold, 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,
window_logo_scale, window_size
resize_debounce_time, scrollback_lines, scrollback_pager_history_size, scrollbar_color,
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, text_fg_override_threshold, 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, window_logo_scale, window_size
)
@ -1207,7 +1207,48 @@ class Parser:
ans['scrollback_pager_history_size'] = scrollback_pager_history_size(val)
def scrollbar(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar'] = scrollbar(val)
val = val.lower()
if val not in self.choices_for_scrollbar:
raise ValueError(f"The value {val} is not a valid choice for scrollbar")
ans["scrollbar"] = val
choices_for_scrollbar = frozenset(('scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered'))
def scrollbar_gap(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_gap'] = positive_float(val)
def scrollbar_handle_color(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_handle_color'] = scrollbar_color(val)
def scrollbar_handle_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_handle_opacity'] = positive_float(val)
def scrollbar_hitbox_expansion(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_hitbox_expansion'] = positive_float(val)
def scrollbar_interactive(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_interactive'] = to_bool(val)
def scrollbar_jump_on_click(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_jump_on_click'] = to_bool(val)
def scrollbar_min_handle_height(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_min_handle_height'] = positive_float(val)
def scrollbar_radius(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_radius'] = positive_float(val)
def scrollbar_track_color(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_track_color'] = scrollbar_color(val)
def scrollbar_track_hover_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_track_hover_opacity'] = positive_float(val)
def scrollbar_track_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_track_opacity'] = positive_float(val)
def scrollbar_width(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['scrollbar_width'] = positive_float(val)
def select_by_word_characters(self, val: str, ans: dict[str, typing.Any]) -> None:
ans['select_by_word_characters'] = str(val)

View file

@ -254,7 +254,7 @@ convert_from_opts_cursor_trail_color(PyObject *py_opts, Options *opts) {
static void
convert_from_python_scrollbar(PyObject *val, Options *opts) {
scrollbar(val, opts);
opts->scrollbar = scrollbar(val);
}
static void
@ -265,6 +265,162 @@ convert_from_opts_scrollbar(PyObject *py_opts, Options *opts) {
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_interactive(PyObject *val, Options *opts) {
opts->scrollbar_interactive = PyObject_IsTrue(val);
}
static void
convert_from_opts_scrollbar_interactive(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_interactive");
if (ret == NULL) return;
convert_from_python_scrollbar_interactive(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_jump_on_click(PyObject *val, Options *opts) {
opts->scrollbar_jump_on_click = PyObject_IsTrue(val);
}
static void
convert_from_opts_scrollbar_jump_on_click(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_jump_on_click");
if (ret == NULL) return;
convert_from_python_scrollbar_jump_on_click(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_width(PyObject *val, Options *opts) {
opts->scrollbar_width = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_width(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_width");
if (ret == NULL) return;
convert_from_python_scrollbar_width(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_handle_opacity(PyObject *val, Options *opts) {
opts->scrollbar_handle_opacity = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_handle_opacity(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_handle_opacity");
if (ret == NULL) return;
convert_from_python_scrollbar_handle_opacity(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_radius(PyObject *val, Options *opts) {
opts->scrollbar_radius = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_radius(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_radius");
if (ret == NULL) return;
convert_from_python_scrollbar_radius(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_gap(PyObject *val, Options *opts) {
opts->scrollbar_gap = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_gap(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_gap");
if (ret == NULL) return;
convert_from_python_scrollbar_gap(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_min_handle_height(PyObject *val, Options *opts) {
opts->scrollbar_min_handle_height = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_min_handle_height(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_min_handle_height");
if (ret == NULL) return;
convert_from_python_scrollbar_min_handle_height(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_hitbox_expansion(PyObject *val, Options *opts) {
opts->scrollbar_hitbox_expansion = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_hitbox_expansion(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_hitbox_expansion");
if (ret == NULL) return;
convert_from_python_scrollbar_hitbox_expansion(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_track_opacity(PyObject *val, Options *opts) {
opts->scrollbar_track_opacity = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_track_opacity(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_opacity");
if (ret == NULL) return;
convert_from_python_scrollbar_track_opacity(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_track_hover_opacity(PyObject *val, Options *opts) {
opts->scrollbar_track_hover_opacity = PyFloat_AsFloat(val);
}
static void
convert_from_opts_scrollbar_track_hover_opacity(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_hover_opacity");
if (ret == NULL) return;
convert_from_python_scrollbar_track_hover_opacity(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_handle_color(PyObject *val, Options *opts) {
opts->scrollbar_handle_color = PyLong_AsUnsignedLong(val);
}
static void
convert_from_opts_scrollbar_handle_color(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_handle_color");
if (ret == NULL) return;
convert_from_python_scrollbar_handle_color(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollbar_track_color(PyObject *val, Options *opts) {
opts->scrollbar_track_color = PyLong_AsUnsignedLong(val);
}
static void
convert_from_opts_scrollbar_track_color(PyObject *py_opts, Options *opts) {
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_color");
if (ret == NULL) return;
convert_from_python_scrollbar_track_color(ret, opts);
Py_DECREF(ret);
}
static void
convert_from_python_scrollback_pager_history_size(PyObject *val, Options *opts) {
opts->scrollback_pager_history_size = PyLong_AsUnsignedLong(val);
@ -1230,6 +1386,30 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_interactive(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_jump_on_click(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_width(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_handle_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_radius(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_gap(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_min_handle_height(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_hitbox_expansion(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_track_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_track_hover_opacity(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_handle_color(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollbar_track_color(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollback_pager_history_size(py_opts, opts);
if (PyErr_Occurred()) return false;
convert_from_opts_scrollback_fill_enlarged_window(py_opts, opts);

View file

@ -58,35 +58,17 @@ window_title_in(PyObject *title_in) {
return ALL;
}
static inline void
scrollbar(PyObject *src, Options *opts) {
#define G(conv, attr) { RAII_PyObject(x, PyObject_GetAttrString(src, #attr)); if (x) opts->scrollbar.attr = conv(x); }
G(PyFloat_AsFloat, opacity);
G(PyFloat_AsFloat, track_opacity);
G(PyFloat_AsFloat, track_hover_opacity);
G(PyFloat_AsFloat, width);
G(PyFloat_AsFloat, radius);
G(PyFloat_AsFloat, gap);
G(PyFloat_AsFloat, min_handle_height);
G(PyFloat_AsFloat, hitbox_expansion);
G(PyLong_AsUnsignedLong, color);
G(PyLong_AsUnsignedLong, track_color);
G(PyLong_AsLong, visible_when);
G(PyObject_IsTrue, interactive);
#undef G
RAII_PyObject(x, PyObject_GetAttrString(src, "jump_on_track_click"));
opts->scrollbar.track_behavior = (x && PyObject_IsTrue(x)) ? SCROLLBAR_TRACK_JUMP : SCROLLBAR_TRACK_PAGE;
}
static inline ScrollbarTrackBehavior
scrollbar_track_behavior(PyObject *val) {
const char *v = PyUnicode_AsUTF8(val);
switch(v[0]) {
case 'j': return SCROLLBAR_TRACK_JUMP;
case 'p': return SCROLLBAR_TRACK_PAGE;
default: break;
static inline ScrollbarVisibilityPolicy
scrollbar(PyObject *src) {
const char *q = PyUnicode_AsUTF8(src);
switch (q[0]) {
case 'a': return SCROLLBAR_ALWAYS;
case 'n': return SCROLLBAR_NEVER;
case 'h': return SCROLLBAR_ON_HOVERED;
case 's':
return strcmp(q, "scrolled") == 0 ? SCROLLBAR_ON_SCROLLED : SCROLLBAR_ON_SCROLL_AND_HOVER;
}
return SCROLLBAR_TRACK_JUMP;
return SCROLLBAR_ON_SCROLLED;
}
static inline unsigned

29
kitty/options/types.py generated
View file

@ -12,7 +12,7 @@ from kitty.fonts import FontSpec
import kitty.fonts
from kitty.options.utils import (
AliasMap, KeyDefinition, KeyboardModeMap, MouseHideWait, MouseMap, MouseMapping, NotifyOnCmdFinish,
ScrollbarSettings, TabBarMarginHeight
TabBarMarginHeight
)
import kitty.options.utils
from kitty.types import FloatEdges
@ -27,6 +27,7 @@ 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_grabbed = choices_for_default_pointer_shape
choices_for_scrollbar = typing.Literal['scrolled', 'always', 'never', 'hovered', 'scrolled-and-hovered']
choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart']
choices_for_tab_bar_align = typing.Literal['left', 'center', 'right']
choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant', 'custom']
@ -415,6 +416,18 @@ option_names = (
'scrollback_pager',
'scrollback_pager_history_size',
'scrollbar',
'scrollbar_gap',
'scrollbar_handle_color',
'scrollbar_handle_opacity',
'scrollbar_hitbox_expansion',
'scrollbar_interactive',
'scrollbar_jump_on_click',
'scrollbar_min_handle_height',
'scrollbar_radius',
'scrollbar_track_color',
'scrollbar_track_hover_opacity',
'scrollbar_track_opacity',
'scrollbar_width',
'select_by_word_characters',
'select_by_word_characters_forward',
'selection_background',
@ -588,7 +601,19 @@ class Options:
scrollback_lines: int = 2000
scrollback_pager: list[str] = ['less', '--chop-long-lines', '--RAW-CONTROL-CHARS', '+INPUT_LINE_NUMBER']
scrollback_pager_history_size: int = 0
scrollbar: ScrollbarSettings = ScrollbarSettings()
scrollbar: choices_for_scrollbar = 'scrolled'
scrollbar_gap: float = 0.1
scrollbar_handle_color: int = 0
scrollbar_handle_opacity: float = 0.5
scrollbar_hitbox_expansion: float = 0.25
scrollbar_interactive: bool = True
scrollbar_jump_on_click: bool = True
scrollbar_min_handle_height: float = 1.0
scrollbar_radius: float = 0.3
scrollbar_track_color: int = 0
scrollbar_track_hover_opacity: float = 0.1
scrollbar_track_opacity: float = 0
scrollbar_width: float = 0.5
select_by_word_characters: str = '@-./_~?&=%+#'
select_by_word_characters_forward: str = ''
selection_background: kitty.fast_data_types.Color | None = Color(255, 250, 205)

View file

@ -41,7 +41,7 @@ from kitty.constants import is_macos
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_HOLLOW, CURSOR_UNDERLINE, NO_CURSOR_SHAPE, Color, Shlex, SingleKey
from kitty.fonts import FontModification, FontSpec, ModificationType, ModificationUnit, ModificationValue
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup
from kitty.rgb import color_as_int, color_from_int
from kitty.rgb import color_as_int
from kitty.types import FloatEdges, MouseEvent
from kitty.utils import expandvars, log_error, resolve_abs_or_config_path, shlex_split
@ -859,11 +859,11 @@ def allow_hyperlinks(x: str) -> int:
def color_with_special_values(x: str, special_values: dict[str, int], error_msg: str) -> int:
x = x.strip('"')
if x in special_values:
return special_values[x]
if (ans := special_values.get(x)) is not None:
return ans & 0xff
try:
return (color_as_int(to_color(x)) << 8) | 2
except ValueError:
return (color_as_int(to_color(x)) << 8) | len(special_values)
except Exception:
log_error(error_msg.format(x=x))
return 0
@ -876,6 +876,14 @@ def titlebar_color(x: str) -> int:
)
def scrollbar_color(x: str) -> int:
return color_with_special_values(
x,
{'foreground': 0, 'selection_background': 1},
'Ignoring invalid scroll bar color: {x}'
)
def macos_titlebar_color(x: str) -> int:
x = x.strip('"')
if x == 'light':
@ -1704,107 +1712,6 @@ def transparent_background_colors(spec: str) -> tuple[tuple[Color, float], ...]:
return tuple(ans[:7])
class ScrollbarSettings(NamedTuple):
opacity: float = 0.5
track_opacity: float = 0
track_hover_opacity: float = 0.1
color: int = 0
track_color: int = 0
interactive: bool = True
width: float = 0.5
radius: float = 0.3
gap: float = 0.1
min_handle_height: float = 1.0
hitbox_expansion: float = 0.25
jump_on_track_click: bool = True
visible_when: int = 1
@classmethod
def color_as_string(cls, color_val: int) -> str:
match color_val:
case 0:
return 'foreground'
case 1:
return 'selection_background'
return color_from_int(color_val >> 8).as_sharp
def differences(self, other: 'ScrollbarSettings') -> Iterator[str]:
for key in self._fields:
if getattr(self, key) != (o := getattr(other, key)):
yield f'{key}: {o}'
def __repr__(self) -> str:
defaults = self._field_defaults
parts = []
for field_name in self._fields:
if (value := getattr(self, field_name)) != defaults[field_name]:
parts.append(f'{field_name}={value!r}')
return f'{self.__class__.__name__}({", ".join(parts)})'
default_scrollbar = ScrollbarSettings()
def scrollbar(spec: str) -> ScrollbarSettings:
if not spec:
return default_scrollbar
ans = ScrollbarSettings()
def scrollbar_color(x: str) -> int:
return color_with_special_values(
x, {'foreground': 0, 'selection_background': 1}, 'Ignoring invalid scrollbar color: {x}')
for x in shlex_split(spec):
k, sep, v = x.partition('=')
if sep != '=':
log_error(f'Ignoring invalid scrollbar option: {x}')
continue
match k:
case 'opacity':
ans = ans._replace(opacity=unit_float(v))
case 'track_opacity':
ans = ans._replace(track_opacity=unit_float(v))
case 'track_hover_opacity':
ans = ans._replace(track_hover_opacity=unit_float(v))
case 'color':
ans = ans._replace(color=scrollbar_color(v))
case 'track_color':
ans = ans._replace(track_color=scrollbar_color(v))
case 'interactive':
ans = ans._replace(interactive=to_bool(v))
case 'width':
ans = ans._replace(width=positive_float(v))
case 'radius':
ans = ans._replace(radius=positive_float(v))
case 'gap':
ans = ans._replace(gap=positive_float(v))
case 'min_handle_height':
ans = ans._replace(min_handle_height=positive_float(v))
case 'hitbox_expansion':
ans = ans._replace(hitbox_expansion=positive_float(v))
case 'track_click':
ans = ans._replace(jump_on_track_click=v == 'jump')
case 'visible_when':
val = -1
match v:
case 'never':
val = defines.SCROLLBAR_NEVER
case 'scrolled':
val = defines.SCROLLBAR_ON_SCROLLED
case 'hovered':
val = defines.SCROLLBAR_ON_HOVERED
case 'scrolled-and-hovered':
val = defines.SCROLLBAR_ON_SCROLL_AND_HOVER
case 'always':
val = defines.SCROLLBAR_ALWAYS
case _:
log_error(f'Ignoring unknown scrollbar visibility policy: {v}')
if val > -1:
ans = ans._replace(visible_when=val)
case _:
log_error(f'Ignoring unknown scrollbar option: {k}')
return ans
def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: dict[str, Any]) -> None:
if not hasattr(deprecated_hide_window_decorations_aliases, key):
setattr(deprecated_hide_window_decorations_aliases, key, True)
@ -1864,4 +1771,8 @@ def deprecated_scrollback_indicator_opacity(key: str, val: str, ans: dict[str, A
if not hasattr(deprecated_scrollback_indicator_opacity, key):
setattr(deprecated_scrollback_indicator_opacity, key, True)
log_error(f'The option {key} is deprecated. Use scrollbar instead.')
ans['scrollbar'] = ScrollbarSettings(opacity=unit_float(val))
op = unit_float(val)
if op <= 0.001:
ans['scrollbar'] = 'never'
else:
ans['scrollbar_handle_opacity'] = op

View file

@ -743,7 +743,7 @@ draw_visual_bell(const UIRenderData *ui) {
static bool
has_scrollbar(Window *w, Screen *screen) {
if (screen->linebuf != screen->main_linebuf || !screen->historybuf->count) return false;
switch (OPT(scrollbar.visible_when)) {
switch (OPT(scrollbar)) {
case SCROLLBAR_NEVER: return false;
case SCROLLBAR_ALWAYS: return true;
case SCROLLBAR_ON_SCROLLED: return screen->scrolled_by > 0;
@ -887,8 +887,10 @@ set_color_uniform_with_opacity(color_type color, float opacity) {
static color_type
scrollbar_color(Screen *screen, unsigned val) {
switch (val & 0xff) {
case 0: return colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg).rgb;
case 1: return colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_fg).rgb;
#define C(which) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which).rgb
case 0: return C(default_fg);
case 1: return C(highlight_bg);
#undef C
default: return val >> 8;
}
}
@ -899,13 +901,13 @@ draw_scrollbar(const UIRenderData *ui) {
Window *window = ui->window;
if (!window || !screen || !has_scrollbar(window, screen)) return;
color_type bar_color = scrollbar_color(screen, OPT(scrollbar.color)), track_color = scrollbar_color(screen, OPT(scrollbar.track_color));
color_type bar_color = scrollbar_color(screen, OPT(scrollbar_handle_color)), track_color = scrollbar_color(screen, OPT(scrollbar_track_color));
float bar_frac = (float)screen->scrolled_by / MAX(1u, (float)screen->historybuf->count);
float opacity = OPT(scrollbar.opacity);
float track_opacity = window->scrollbar.is_hovering ? OPT(scrollbar.track_hover_opacity) : OPT(scrollbar.track_opacity);
GLsizei scrollbar_width_px = (GLsizei)(OPT(scrollbar.width) * ui->cell_width);
GLsizei scrollbar_gap_px = (GLsizei)(OPT(scrollbar.gap) * ui->cell_width);
unsigned scrollbar_radius = (unsigned)(OPT(scrollbar.radius) * ui->cell_width);
float opacity = OPT(scrollbar_handle_opacity);
float track_opacity = window->scrollbar.is_hovering ? OPT(scrollbar_track_hover_opacity) : OPT(scrollbar_track_opacity);
GLsizei scrollbar_width_px = (GLsizei)(OPT(scrollbar_width) * ui->cell_width);
GLsizei scrollbar_gap_px = (GLsizei)(OPT(scrollbar_gap) * ui->cell_width);
unsigned scrollbar_radius = (unsigned)(OPT(scrollbar_radius) * ui->cell_width);
// Calculate window boundaries including padding
GLsizei window_right_edge = ui->screen_left + ui->screen_width + window->render_data.geometry.spaces.right;
@ -919,7 +921,7 @@ draw_scrollbar(const UIRenderData *ui) {
// Calculate thumb size and position
float visible_fraction = (float)screen->lines / (float)(screen->lines + screen->historybuf->count);
float min_thumb_height_fraction = (OPT(scrollbar.min_handle_height) * ui->cell_height) / (float)window_height;
float min_thumb_height_fraction = (OPT(scrollbar_min_handle_height) * ui->cell_height) / (float)window_height;
float thumb_height_fraction = MAX(min_thumb_height_fraction, visible_fraction);
// Convert to OpenGL coordinates (range -1.0 to 1.0, total span = 2.0)

View file

@ -71,14 +71,12 @@ typedef struct Options {
WindowTitleIn macos_show_window_title_in;
char *bell_path, *bell_theme;
float background_opacity, dim_opacity;
struct {
float opacity, track_opacity, track_hover_opacity;
color_type color, track_color;
bool interactive;
float width, radius, gap, min_handle_height, hitbox_expansion;
ScrollbarTrackBehavior track_behavior;
ScrollbarVisibilityPolicy visible_when;
} scrollbar;
ScrollbarVisibilityPolicy scrollbar;
bool scrollbar_interactive, scrollbar_jump_on_click;
float scrollbar_width, scrollbar_radius, scrollbar_gap, scrollbar_min_handle_height, scrollbar_hitbox_expansion;
float scrollbar_handle_opacity, scrollbar_track_opacity, scrollbar_track_hover_opacity;
color_type scrollbar_handle_color, scrollbar_track_color;
float text_contrast, text_gamma_adjustment;
bool text_old_gamma;

View file

@ -310,6 +310,8 @@ var AllColorSettingNames = map[string]bool{ // {{{
"mark2_foreground": true,
"mark3_background": true,
"mark3_foreground": true,
"scrollbar_handle_color": true,
"scrollbar_track_color": true,
"selection_background": true,
"selection_foreground": true,
"tab_bar_background": true,