mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-05-13 08:26:56 +00:00
Merge branch 'master' of https://github.com/lxe/kitty into scrollbar
This commit is contained in:
commit
a6e64845ef
17 changed files with 716 additions and 59 deletions
|
|
@ -668,7 +668,7 @@ Detailed list of changes
|
|||
|
||||
- Wayland labwc: Fix kitty timing out waiting for compositor to quit fucking around with scales on labwc (:iss:`7540`)
|
||||
|
||||
- Fix :opt:`scrollback_indicator_opacity` not actually controlling the opacity (:iss:`7557`)
|
||||
- Fix ``scrollback_indicator_opacity`` not actually controlling the opacity (:iss:`7557`)
|
||||
|
||||
- URL detection: Fix IPv6 hostnames breaking URL detection (:iss:`7565`)
|
||||
|
||||
|
|
@ -752,7 +752,7 @@ Detailed list of changes
|
|||
using the panel kitten for all compositors that support the `requisite Wayland
|
||||
protocol <https://wayland.app/protocols/wlr-layer-shell-unstable-v1>`__ which is practically speaking all of them but GNOME (:pull:`2590`)
|
||||
|
||||
- Show a small :opt:`scrollback indicator <scrollback_indicator_opacity>` along the right window edge when viewing
|
||||
- Show a small scrollback indicator along the right window edge when viewing
|
||||
the scrollback to keep track of scroll position (:iss:`2502`)
|
||||
|
||||
- Wayland: Support fractional scales so that there is no wasted drawing at larger scale followed by resizing in the compositor
|
||||
|
|
|
|||
|
|
@ -197,9 +197,10 @@ The scrollback buffer
|
|||
-----------------------
|
||||
|
||||
|kitty| supports scrolling back to view history, just like most terminals. You
|
||||
can use either keyboard shortcuts or the mouse scroll wheel to do so. While
|
||||
you are browsing the scrollback a :opt:`small indicator <scrollback_indicator_opacity>`
|
||||
is displayed along the right edge of the window to show how far back you are.
|
||||
can use either keyboard shortcuts or the mouse scroll wheel to do so. |kitty|
|
||||
displays an interactive :opt:`scrollbar <scrollbar_opacity>` along the right edge
|
||||
of the window that shows your current position in the scrollback. You can click
|
||||
and drag the scrollbar to quickly navigate through the history.
|
||||
|
||||
However, |kitty| has an extra, neat feature. Sometimes you need to explore the scrollback
|
||||
buffer in more detail, maybe search for some text or refer to it side-by-side
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ typedef enum MouseShapes {
|
|||
/* end mouse shapes */
|
||||
} MouseShape;
|
||||
typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
|
||||
typedef enum { SCROLLBAR_TRACK_JUMP=1, SCROLLBAR_TRACK_PAGE=2 } ScrollbarTrackBehavior;
|
||||
typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED, CENTER_SCALED } BackgroundImageLayout;
|
||||
typedef struct ImageAnchorPosition {
|
||||
float canvas_x, canvas_y, image_x, image_y;
|
||||
|
|
|
|||
|
|
@ -1387,7 +1387,8 @@ def set_tab_bar_render_data(
|
|||
|
||||
def set_window_render_data(
|
||||
os_window_id: int, tab_id: int, window_id: int, screen: Screen,
|
||||
left: int, top: int, right: int, bottom: int
|
||||
left: int, top: int, right: int, bottom: int,
|
||||
spaces_left: int, spaces_top: int, spaces_right: int, spaces_bottom: int
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
|
|
|||
247
kitty/mouse.c
247
kitty/mouse.c
|
|
@ -146,6 +146,26 @@ 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 ScrollbarGeometry calculate_scrollbar_geometry(Window *w);
|
||||
static ScrollbarHitType get_scrollbar_hit_type(Window *w, double mouse_x, double mouse_y);
|
||||
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 void update_scrollbar_hover_state(Window *w, bool hovering);
|
||||
// }}}
|
||||
|
||||
static Window*
|
||||
window_for_id(id_type window_id) {
|
||||
if (global_state.callback_os_window && global_state.callback_os_window->num_tabs) {
|
||||
|
|
@ -164,6 +184,9 @@ send_mouse_leave_event_if_needed(id_type currently_over_window, int modifiers) {
|
|||
Window *left_window = window_for_id(global_state.mouse_hover_in_window);
|
||||
global_state.mouse_hover_in_window = currently_over_window;
|
||||
if (left_window) {
|
||||
if (left_window->scrollbar.is_hovering) {
|
||||
update_scrollbar_hover_state(left_window, false);
|
||||
}
|
||||
int sz = encode_mouse_event(left_window, 0, LEAVE, modifiers);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
|
|
@ -405,6 +428,11 @@ set_mouse_position(Window *w, bool *mouse_cell_changed, bool *cell_half_changed)
|
|||
|
||||
HANDLER(handle_move_event) {
|
||||
modifiers &= ~GLFW_LOCK_MASK;
|
||||
|
||||
if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (OPT(focus_follows_mouse)) {
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
if (window_idx != t->active_window) {
|
||||
|
|
@ -413,7 +441,12 @@ HANDLER(handle_move_event) {
|
|||
}
|
||||
bool mouse_cell_changed = false;
|
||||
bool cell_half_changed = false;
|
||||
if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) return;
|
||||
if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) {
|
||||
if (w->scrollbar.is_hovering) {
|
||||
update_scrollbar_hover_state(w, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (OPT(detect_urls)) detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y);
|
||||
if (should_handle_in_kitty(w, screen, button)) {
|
||||
|
|
@ -588,8 +621,15 @@ dispatch_possible_click(Window *w, int button, int modifiers) {
|
|||
|
||||
HANDLER(handle_button_event) {
|
||||
modifiers &= ~GLFW_LOCK_MASK;
|
||||
if (!global_state.callback_os_window) return;
|
||||
|
||||
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
|
||||
bool is_release = !global_state.callback_os_window->mouse_button_pressed[button];
|
||||
|
||||
if (handle_scrollbar_mouse(w, button, is_release ? RELEASE : PRESS, modifiers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window_idx != t->active_window && !is_release) {
|
||||
call_boss(switch_focus_to, "K", t->windows[window_idx].id);
|
||||
}
|
||||
|
|
@ -712,10 +752,17 @@ update_mouse_pointer_shape(void) {
|
|||
bool in_tab_bar;
|
||||
unsigned int window_idx = 0;
|
||||
Window *w = window_for_event(&window_idx, &in_tab_bar);
|
||||
if (in_tab_bar) { mouse_cursor_shape = POINTER_POINTER; }
|
||||
else if (w && w->render_data.screen) {
|
||||
screen_mark_url(w->render_data.screen, 0, 0, 0, 0);
|
||||
set_mouse_cursor_for_screen(w->render_data.screen);
|
||||
if (in_tab_bar) {
|
||||
mouse_cursor_shape = POINTER_POINTER;
|
||||
} else if (w) {
|
||||
if (handle_scrollbar_mouse(w, -1, MOVE, 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (w->render_data.screen) {
|
||||
screen_mark_url(w->render_data.screen, 0, 0, 0, 0);
|
||||
set_mouse_cursor_for_screen(w->render_data.screen);
|
||||
}
|
||||
}
|
||||
set_mouse_cursor(mouse_cursor_shape);
|
||||
}
|
||||
|
|
@ -748,6 +795,11 @@ enter_event(int modifiers) {
|
|||
send_mouse_leave_event_if_needed(w ? w->id : 0, modifiers);
|
||||
if (!w || in_tab_bar) return;
|
||||
global_state.mouse_hover_in_window = w->id;
|
||||
|
||||
if (handle_scrollbar_mouse(w, -1, MOVE, modifiers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mouse_cell_changed = false, cell_half_changed = false;
|
||||
if (!set_mouse_position(w, &mouse_cell_changed, &cell_half_changed)) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
|
|
@ -757,12 +809,194 @@ 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) return geom;
|
||||
|
||||
WindowGeometry *g = &w->render_data.geometry;
|
||||
geom.width = OPT(scrollbar_width);
|
||||
geom.gap = OPT(scrollbar_gap);
|
||||
geom.hitbox_expansion = OPT(scrollbar_hitbox_expansion);
|
||||
|
||||
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 void
|
||||
update_scrollbar_hover_state(Window *w, bool hovering) {
|
||||
if (!w) return;
|
||||
bool changed = w->scrollbar.is_hovering != hovering;
|
||||
w->scrollbar.is_hovering = hovering;
|
||||
|
||||
if (changed && OPT(scrollbar_autohide) && global_state.callback_os_window) {
|
||||
global_state.callback_os_window->needs_render = true;
|
||||
request_tick_callback();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
double hitbox_expansion_fraction = (double)OPT(scrollbar_hitbox_expansion) / 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) 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) 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;
|
||||
double delta_y = mouse_pane_fraction - w->scrollbar.drag_start_y;
|
||||
double visible_fraction = (double)screen->lines / (screen->lines + screen->historybuf->count);
|
||||
double min_thumb_height_fraction = (double)OPT(scrollbar_min_thumb_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});
|
||||
}
|
||||
|
|
@ -905,6 +1139,9 @@ mouse_event(const int button, int modifiers, int action) {
|
|||
if (global_state.mouse_hover_in_window) {
|
||||
Window *old_window = window_for_id(global_state.mouse_hover_in_window);
|
||||
if (old_window && old_window != w) {
|
||||
if (old_window->scrollbar.is_hovering) {
|
||||
update_scrollbar_hover_state(old_window, false);
|
||||
}
|
||||
global_state.mouse_hover_in_window = 0;
|
||||
screen_mark_url(old_window->render_data.screen, 0, 0, 0, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ definition.add_deprecation('deprecated_hide_window_decorations_aliases', 'x11_hi
|
|||
definition.add_deprecation('deprecated_macos_show_window_title_in_menubar_alias', 'macos_show_window_title_in_menubar')
|
||||
definition.add_deprecation('deprecated_send_text', 'send_text')
|
||||
definition.add_deprecation('deprecated_adjust_line_height', 'adjust_line_height', 'adjust_column_width', 'adjust_baseline')
|
||||
definition.add_deprecation('deprecated_scrollback_indicator_opacity', 'scrollback_indicator_opacity')
|
||||
|
||||
agr = definition.add_group
|
||||
egr = definition.end_group
|
||||
|
|
@ -441,12 +442,70 @@ is changed it will only affect newly created windows, not existing ones.
|
|||
'''
|
||||
)
|
||||
|
||||
opt('scrollback_indicator_opacity', '1.0',
|
||||
opt('scrollbar_opacity', '0.5',
|
||||
option_type='unit_float', ctype='float', long_text='''
|
||||
The opacity of the scrollback indicator which is a small colored rectangle that moves
|
||||
along the right hand side of the window as you scroll, indicating what fraction you
|
||||
have scrolled. The default is one which means fully opaque, aka visible.
|
||||
Set to a value between zero and one to make the indicator less visible.''')
|
||||
The opacity of the scrollbar handle. The default is 0.5 which means 50% opaque.
|
||||
Set to a value between zero and one.''')
|
||||
|
||||
opt('scrollbar_track_opacity', '0',
|
||||
option_type='unit_float', ctype='float', long_text='''
|
||||
The opacity of the scrollbar track (the background behind the scrollbar handle).
|
||||
The default is 0 which means completely transparent. Set to a value between zero and one.''')
|
||||
|
||||
opt('scrollbar_color', 'foreground',
|
||||
option_type='scrollbar_color', long_text='''
|
||||
The color of the scrollbar. The default value :code:`foreground` uses the
|
||||
current text color. You can specify any color as a hexadecimal RGB triplet (e.g.
|
||||
:code:`#ff0000` for red), or use one of the standard color names (e.g. :code:`red`),
|
||||
or use a color from the 256 color table (e.g. :code:`color120`).
|
||||
Additionally, special values like :code:`background`, :code:`foreground`, or any other
|
||||
configured color variable are supported. The scrollbar appearance is also affected by
|
||||
:opt:`scrollbar_opacity` and :opt:`scrollbar_track_opacity` which control transparency.''')
|
||||
|
||||
opt('scrollbar_interactive', 'yes',
|
||||
option_type='to_bool', ctype='bool', long_text='''
|
||||
Enable or disable interactive scrollbar functionality. When enabled, you can click
|
||||
and drag the scrollbar to scroll. When disabled, the scrollbar is only a visual
|
||||
indicator of the scroll position. Set to :code:`yes` to enable or :code:`no` to disable.''')
|
||||
|
||||
opt('scrollbar_width', '10',
|
||||
option_type='positive_int', ctype='uint', long_text='''
|
||||
The width of the scrollbar in pixels. The default is 10 pixels.''')
|
||||
|
||||
opt('scrollbar_gap', '5',
|
||||
option_type='positive_int', ctype='uint', long_text='''
|
||||
The gap between the scrollbar and the window edge in pixels. The default is 5 pixels.''')
|
||||
opt('scrollbar_min_thumb_height', '50',
|
||||
option_type='positive_int', ctype='uint', long_text='''
|
||||
The minimum height of the scrollbar thumb in pixels. This prevents the thumb from
|
||||
becoming too small when there is a lot of content to scroll. The default is 50 pixels.''')
|
||||
opt('scrollbar_hitbox_expansion', '5',
|
||||
option_type='positive_int', ctype='uint', long_text='''
|
||||
Extra pixels added to the scrollbar thumb hitbox for easier interaction.
|
||||
This makes it easier to grab the scrollbar even if the visual representation
|
||||
is thin. The default is 5 pixels on each side.''')
|
||||
|
||||
opt('scrollbar_radius', '5',
|
||||
option_type='positive_int', ctype='uint', long_text='''
|
||||
The radius of the scrollbar thumb corners in pixels. This controls how rounded
|
||||
the scrollbar thumb appears. Set to 0 for square corners, or a positive value
|
||||
for rounded corners. The default is 5 pixels.''')
|
||||
|
||||
opt('scrollbar_autohide', 'yes',
|
||||
option_type='to_bool', ctype='bool', long_text='''
|
||||
Hide the scrollbar by default and only show it when scrolling or hovering.
|
||||
When enabled, the scrollbar will only be visible when you are scrolling
|
||||
(not at the bottom) or when the mouse is hovering over the scrollbar area.
|
||||
Set to :code:`yes` to enable or :code:`no` to disable.''')
|
||||
|
||||
opt('scrollbar_track_behavior', 'jump',
|
||||
option_type='choices', ctype='scrollbar_track_behavior',
|
||||
choices=('jump', 'page'),
|
||||
long_text='''
|
||||
Control the behavior when clicking on the scrollbar track (the area outside
|
||||
the thumb). With :code:`jump`, clicking on the track will jump directly to
|
||||
that position. With :code:`page`, clicking on the track will scroll up or
|
||||
down by one page, similar to traditional scrollbar behavior.''')
|
||||
|
||||
|
||||
opt('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER',
|
||||
|
|
|
|||
59
kitty/options/parse.py
generated
59
kitty/options/parse.py
generated
|
|
@ -12,13 +12,14 @@ from kitty.options.utils import (
|
|||
clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, clone_source_strategies,
|
||||
config_or_absolute_path, confirm_close, copy_on_select, cursor_blink_interval, cursor_text_color,
|
||||
cursor_trail_decay, deprecated_adjust_line_height, deprecated_hide_window_decorations_aliases,
|
||||
deprecated_macos_show_window_title_in_menubar_alias, deprecated_send_text, disable_ligatures,
|
||||
edge_width, env, filter_notification, font_features, 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, 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,
|
||||
deprecated_macos_show_window_title_in_menubar_alias, deprecated_scrollback_indicator_opacity,
|
||||
deprecated_send_text, disable_ligatures, edge_width, env, filter_notification, font_features,
|
||||
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_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,
|
||||
|
|
@ -1196,9 +1197,6 @@ class Parser:
|
|||
def scrollback_fill_enlarged_window(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollback_fill_enlarged_window'] = to_bool(val)
|
||||
|
||||
def scrollback_indicator_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollback_indicator_opacity'] = unit_float(val)
|
||||
|
||||
def scrollback_lines(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollback_lines'] = scrollback_lines(val)
|
||||
|
||||
|
|
@ -1208,6 +1206,44 @@ class Parser:
|
|||
def scrollback_pager_history_size(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollback_pager_history_size'] = scrollback_pager_history_size(val)
|
||||
|
||||
def scrollbar_autohide(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_autohide'] = to_bool(val)
|
||||
|
||||
def scrollbar_color(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_color'] = scrollbar_color(val)
|
||||
|
||||
def scrollbar_gap(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_gap'] = positive_int(val)
|
||||
|
||||
def scrollbar_hitbox_expansion(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_hitbox_expansion'] = positive_int(val)
|
||||
|
||||
def scrollbar_interactive(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_interactive'] = to_bool(val)
|
||||
|
||||
def scrollbar_min_thumb_height(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_min_thumb_height'] = positive_int(val)
|
||||
|
||||
def scrollbar_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_opacity'] = unit_float(val)
|
||||
|
||||
def scrollbar_radius(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_radius'] = positive_int(val)
|
||||
|
||||
def scrollbar_track_behavior(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
val = val.lower()
|
||||
if val not in self.choices_for_scrollbar_track_behavior:
|
||||
raise ValueError(f"The value {val} is not a valid choice for scrollbar_track_behavior")
|
||||
ans["scrollbar_track_behavior"] = val
|
||||
|
||||
choices_for_scrollbar_track_behavior = frozenset(('jump', 'page'))
|
||||
|
||||
def scrollbar_track_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_track_opacity'] = unit_float(val)
|
||||
|
||||
def scrollbar_width(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['scrollbar_width'] = positive_int(val)
|
||||
|
||||
def select_by_word_characters(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['select_by_word_characters'] = str(val)
|
||||
|
||||
|
|
@ -1459,6 +1495,9 @@ class Parser:
|
|||
def adjust_baseline(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
deprecated_adjust_line_height('adjust_baseline', val, ans)
|
||||
|
||||
def scrollback_indicator_opacity(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
deprecated_scrollback_indicator_opacity('scrollback_indicator_opacity', val, ans)
|
||||
|
||||
def map(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
for k in parse_map(val):
|
||||
ans['map'].append(k)
|
||||
|
|
|
|||
147
kitty/options/to-c-generated.h
generated
147
kitty/options/to-c-generated.h
generated
|
|
@ -253,15 +253,132 @@ convert_from_opts_cursor_trail_color(PyObject *py_opts, Options *opts) {
|
|||
}
|
||||
|
||||
static void
|
||||
convert_from_python_scrollback_indicator_opacity(PyObject *val, Options *opts) {
|
||||
opts->scrollback_indicator_opacity = PyFloat_AsFloat(val);
|
||||
convert_from_python_scrollbar_opacity(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_opacity = PyFloat_AsFloat(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_scrollback_indicator_opacity(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollback_indicator_opacity");
|
||||
convert_from_opts_scrollbar_opacity(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_opacity");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_scrollback_indicator_opacity(ret, opts);
|
||||
convert_from_python_scrollbar_opacity(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_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_width(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_width = PyLong_AsUnsignedLong(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_gap(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_gap = PyLong_AsUnsignedLong(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_thumb_height(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_min_thumb_height = PyLong_AsUnsignedLong(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_scrollbar_min_thumb_height(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_min_thumb_height");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_scrollbar_min_thumb_height(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_scrollbar_hitbox_expansion(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_hitbox_expansion = PyLong_AsUnsignedLong(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_radius(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_radius = PyLong_AsUnsignedLong(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_autohide(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_autohide = PyObject_IsTrue(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_scrollbar_autohide(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_autohide");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_scrollbar_autohide(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_scrollbar_track_behavior(PyObject *val, Options *opts) {
|
||||
opts->scrollbar_track_behavior = scrollbar_track_behavior(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_scrollbar_track_behavior(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "scrollbar_track_behavior");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_scrollbar_track_behavior(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
|
|
@ -1228,7 +1345,25 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
|||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_cursor_trail_color(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_scrollback_indicator_opacity(py_opts, opts);
|
||||
convert_from_opts_scrollbar_opacity(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_interactive(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_gap(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_scrollbar_min_thumb_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_radius(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_scrollbar_autohide(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_scrollbar_track_behavior(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_scrollback_pager_history_size(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,17 @@ window_title_in(PyObject *title_in) {
|
|||
return ALL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return SCROLLBAR_TRACK_JUMP;
|
||||
}
|
||||
|
||||
static inline unsigned
|
||||
undercurl_style(PyObject *x) {
|
||||
RAII_PyObject(thick, PyUnicode_FromString("thick"));
|
||||
|
|
|
|||
27
kitty/options/types.py
generated
27
kitty/options/types.py
generated
|
|
@ -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_track_behavior = typing.Literal['jump', 'page']
|
||||
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']
|
||||
|
|
@ -411,10 +412,20 @@ option_names = (
|
|||
'resize_debounce_time',
|
||||
'resize_in_steps',
|
||||
'scrollback_fill_enlarged_window',
|
||||
'scrollback_indicator_opacity',
|
||||
'scrollback_lines',
|
||||
'scrollback_pager',
|
||||
'scrollback_pager_history_size',
|
||||
'scrollbar_autohide',
|
||||
'scrollbar_color',
|
||||
'scrollbar_gap',
|
||||
'scrollbar_hitbox_expansion',
|
||||
'scrollbar_interactive',
|
||||
'scrollbar_min_thumb_height',
|
||||
'scrollbar_opacity',
|
||||
'scrollbar_radius',
|
||||
'scrollbar_track_behavior',
|
||||
'scrollbar_track_opacity',
|
||||
'scrollbar_width',
|
||||
'select_by_word_characters',
|
||||
'select_by_word_characters_forward',
|
||||
'selection_background',
|
||||
|
|
@ -585,10 +596,20 @@ class Options:
|
|||
resize_debounce_time: tuple[float, float] = (0.1, 0.5)
|
||||
resize_in_steps: bool = False
|
||||
scrollback_fill_enlarged_window: bool = False
|
||||
scrollback_indicator_opacity: float = 1.0
|
||||
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_autohide: bool = True
|
||||
scrollbar_color: int = 0
|
||||
scrollbar_gap: int = 5
|
||||
scrollbar_hitbox_expansion: int = 5
|
||||
scrollbar_interactive: bool = True
|
||||
scrollbar_min_thumb_height: int = 50
|
||||
scrollbar_opacity: float = 0.5
|
||||
scrollbar_radius: int = 5
|
||||
scrollbar_track_behavior: choices_for_scrollbar_track_behavior = 'jump'
|
||||
scrollbar_track_opacity: float = 0
|
||||
scrollbar_width: int = 10
|
||||
select_by_word_characters: str = '@-./_~?&=%+#'
|
||||
select_by_word_characters_forward: str = ''
|
||||
selection_background: kitty.fast_data_types.Color | None = Color(255, 250, 205)
|
||||
|
|
@ -939,7 +960,7 @@ defaults.map = [
|
|||
]
|
||||
|
||||
if is_macos:
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), definition='copy_to_clipboard'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), definition='copy_or_noop'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=118), definition='paste_from_clipboard'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57354), definition='scroll_line_up'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57352), definition='scroll_line_up'))
|
||||
|
|
|
|||
|
|
@ -857,19 +857,25 @@ def allow_hyperlinks(x: str) -> int:
|
|||
return 1 if to_bool(x) else 0
|
||||
|
||||
|
||||
def titlebar_color(x: str) -> int:
|
||||
def color_with_special_values(x: str, special_values: dict[str, int], error_msg: str) -> int:
|
||||
x = x.strip('"')
|
||||
if x == 'system':
|
||||
return 0
|
||||
if x == 'background':
|
||||
return 1
|
||||
if x in special_values:
|
||||
return special_values[x]
|
||||
try:
|
||||
return (color_as_int(to_color(x)) << 8) | 2
|
||||
except ValueError:
|
||||
log_error(f'Ignoring invalid title bar color: {x}')
|
||||
log_error(error_msg.format(x=x))
|
||||
return 0
|
||||
|
||||
|
||||
def titlebar_color(x: str) -> int:
|
||||
return color_with_special_values(
|
||||
x,
|
||||
{'system': 0, 'background': 1},
|
||||
'Ignoring invalid title bar color: {x}'
|
||||
)
|
||||
|
||||
|
||||
def macos_titlebar_color(x: str) -> int:
|
||||
x = x.strip('"')
|
||||
if x == 'light':
|
||||
|
|
@ -1751,3 +1757,19 @@ def deprecated_adjust_line_height(key: str, x: str, opts_dict: dict[str, Any]) -
|
|||
opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(ans, ModificationUnit.percent))
|
||||
else:
|
||||
opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(int(x), ModificationUnit.pixel))
|
||||
|
||||
|
||||
def deprecated_scrollback_indicator_opacity(key: str, val: str, ans: dict[str, Any]) -> None:
|
||||
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_opacity instead.')
|
||||
from kitty.conf.utils import unit_float
|
||||
ans['scrollbar_opacity'] = unit_float(val)
|
||||
|
||||
|
||||
def scrollbar_color(x: str) -> int:
|
||||
return color_with_special_values(
|
||||
x,
|
||||
{'foreground': 0, 'selection_foreground': 1},
|
||||
'Ignoring invalid scrollbar color: {x}'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4921,6 +4921,16 @@ screen_selection_range_for_word(Screen *self, const index_type x, const index_ty
|
|||
#undef is_ok
|
||||
}
|
||||
|
||||
void
|
||||
screen_history_scroll_to_absolute(Screen *self, unsigned int target_scrolled_by) {
|
||||
if (self->linebuf != self->main_linebuf) return;
|
||||
if (target_scrolled_by > self->historybuf->count) target_scrolled_by = self->historybuf->count;
|
||||
if (target_scrolled_by != self->scrolled_by) {
|
||||
self->scrolled_by = target_scrolled_by;
|
||||
dirty_scroll(self);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
screen_history_scroll(Screen *self, int amt, bool upwards) {
|
||||
switch(amt) {
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ typedef struct SelectionUpdate {
|
|||
} SelectionUpdate;
|
||||
void screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_half, SelectionUpdate upd);
|
||||
bool screen_history_scroll(Screen *self, int amt, bool upwards);
|
||||
void screen_history_scroll_to_absolute(Screen *self, unsigned int target_scrolled_by);
|
||||
PyObject* as_text_history_buf(HistoryBuf *self, PyObject *args, ANSIBuf *output);
|
||||
Line* screen_visual_line(Screen *self, index_type y);
|
||||
void screen_mark_url(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y);
|
||||
|
|
|
|||
123
kitty/shaders.c
123
kitty/shaders.c
|
|
@ -10,9 +10,11 @@
|
|||
#include "cleanup.h"
|
||||
#include "colors.h"
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "window_logo.h"
|
||||
#include "srgb_gamma.h"
|
||||
#include "uniforms_generated.h"
|
||||
#include "state.h"
|
||||
|
||||
enum {
|
||||
CELL_PROGRAM, CELL_FG_PROGRAM, CELL_BG_PROGRAM, CELL_PROGRAM_SENTINEL,
|
||||
|
|
@ -739,22 +741,9 @@ draw_visual_bell(const UIRenderData *ui) {
|
|||
|
||||
static bool
|
||||
has_scrollbar(Screen *screen) {
|
||||
return OPT(scrollback_indicator_opacity) > 0 && screen->linebuf == screen->main_linebuf && screen->scrolled_by;
|
||||
return OPT(scrollbar_opacity) > 0 && screen->linebuf == screen->main_linebuf && screen->historybuf->count > 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
draw_scroll_indicator(color_type bar_color, GLfloat alpha, float frac, const UIRenderData *ui) {
|
||||
bind_program(TINT_PROGRAM);
|
||||
#define C(shift) srgb_color((bar_color >> shift) & 0xFF) * alpha
|
||||
glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), alpha);
|
||||
#undef C
|
||||
float bar_width = 0.5f * gl_size(ui->cell_width, ui->screen_width);
|
||||
float bar_height = gl_size(ui->cell_height, ui->screen_height);
|
||||
float bottom = -1.f + MAX(0, 2.f - bar_height) * frac;
|
||||
glUniform4f(tint_program_layout.uniforms.edges, 1.f - bar_width, bottom + bar_height, 1.f, bottom);
|
||||
draw_quad(true, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static unsigned
|
||||
render_a_bar(const UIRenderData *ui, WindowBarData *bar, PyObject *title, bool along_bottom) {
|
||||
|
|
@ -878,13 +867,115 @@ draw_window_number(const UIRenderData *ui) {
|
|||
#undef lr
|
||||
}
|
||||
|
||||
// Helper function to extract and apply opacity to color components
|
||||
static void
|
||||
set_color_uniform_with_opacity(color_type color, float opacity) {
|
||||
float r = srgb_color((color >> 16) & 0xFF) * opacity;
|
||||
float g = srgb_color((color >> 8) & 0xFF) * opacity;
|
||||
float b = srgb_color(color & 0xFF) * opacity;
|
||||
glUniform4f(tint_program_layout.uniforms.tint_color, r, g, b, opacity);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_scrollbar(const UIRenderData *ui) {
|
||||
if (!has_scrollbar(ui->screen)) return;
|
||||
Screen *screen = ui->screen;
|
||||
color_type bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg).rgb;
|
||||
Window *window = ui->window;
|
||||
if (!window) return;
|
||||
|
||||
color_type bar_color;
|
||||
unsigned int val = OPT(scrollbar_color);
|
||||
switch (val & 0xff) {
|
||||
case 0: bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg).rgb; break;
|
||||
case 1: bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_fg, screen->color_profile->configured.highlight_fg).rgb; break;
|
||||
default: bar_color = val >> 8; break;
|
||||
}
|
||||
|
||||
// Division by zero is safe here because has_scrollbar() ensures historybuf->count > 0
|
||||
float bar_frac = (float)screen->scrolled_by / (float)screen->historybuf->count;
|
||||
draw_scroll_indicator(bar_color, OPT(scrollback_indicator_opacity), bar_frac, ui);
|
||||
|
||||
if (OPT(scrollbar_autohide) && !window->scrollbar.is_hovering && screen->scrolled_by == 0) return;
|
||||
|
||||
float opacity = OPT(scrollbar_opacity);
|
||||
float track_opacity = OPT(scrollbar_track_opacity);
|
||||
|
||||
GLsizei scrollbar_width_px = OPT(scrollbar_width);
|
||||
GLsizei scrollbar_gap_px = OPT(scrollbar_gap);
|
||||
unsigned int scrollbar_radius = OPT(scrollbar_radius);
|
||||
|
||||
// Calculate window boundaries including padding
|
||||
GLsizei window_right_edge = ui->screen_left + ui->screen_width + window->render_data.geometry.spaces.right;
|
||||
GLsizei window_top_edge = ui->screen_top - window->render_data.geometry.spaces.top;
|
||||
GLsizei window_height = ui->screen_height + window->render_data.geometry.spaces.top + window->render_data.geometry.spaces.bottom;
|
||||
|
||||
// Position scrollbar on right side with gap
|
||||
GLsizei scrollbar_left = window_right_edge - scrollbar_width_px - scrollbar_gap_px;
|
||||
GLsizei scrollbar_top = window_top_edge + scrollbar_gap_px;
|
||||
GLsizei scrollbar_height = window_height - 2 * scrollbar_gap_px;
|
||||
|
||||
// Calculate thumb size and position
|
||||
float visible_fraction = (float)screen->lines / (float)(screen->lines + screen->historybuf->count);
|
||||
float min_thumb_height_fraction = (float)OPT(scrollbar_min_thumb_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)
|
||||
const float GL_COORD_SPAN = 2.0f;
|
||||
float thumb_height_gl = thumb_height_fraction * GL_COORD_SPAN;
|
||||
float available_space = GL_COORD_SPAN - thumb_height_gl;
|
||||
float thumb_bottom_gl = -1.0f + available_space * bar_frac;
|
||||
float thumb_top_gl = thumb_bottom_gl + thumb_height_gl;
|
||||
|
||||
// Store thumb position for mouse interaction (normalized window coordinates)
|
||||
float scrollbar_top_in_window = (float)(window_top_edge + scrollbar_gap_px) / (float)ui->full_framebuffer_height;
|
||||
float scrollbar_height_in_window = (float)(window_height - 2 * scrollbar_gap_px) / (float)ui->full_framebuffer_height;
|
||||
float thumb_top_fraction = (1.0f - thumb_top_gl) / 2.0f;
|
||||
float thumb_bottom_fraction = (1.0f - thumb_bottom_gl) / 2.0f;
|
||||
window->scrollbar.thumb_top = scrollbar_top_in_window + thumb_top_fraction * scrollbar_height_in_window;
|
||||
window->scrollbar.thumb_bottom = scrollbar_top_in_window + thumb_bottom_fraction * scrollbar_height_in_window;
|
||||
|
||||
// Set viewport for scrollbar area
|
||||
save_viewport_using_top_left_origin(
|
||||
scrollbar_left, scrollbar_top, scrollbar_width_px, scrollbar_height,
|
||||
ui->full_framebuffer_height
|
||||
);
|
||||
|
||||
// Draw scrollbar track (background)
|
||||
bind_program(TINT_PROGRAM);
|
||||
set_color_uniform_with_opacity(bar_color, track_opacity);
|
||||
glUniform4f(tint_program_layout.uniforms.edges, -1.f, 1.f, 1.f, -1.f);
|
||||
draw_quad(true, 0);
|
||||
|
||||
// Draw scrollbar thumb (handle)
|
||||
if (scrollbar_radius > 0) {
|
||||
// Rounded thumb - use separate viewport and rounded rect program
|
||||
GLsizei thumb_height_px = (GLsizei)(thumb_height_fraction * scrollbar_height);
|
||||
GLsizei thumb_top_px = scrollbar_top + (GLsizei)(thumb_top_fraction * scrollbar_height);
|
||||
|
||||
restore_viewport();
|
||||
|
||||
bind_program(ROUNDED_RECT_PROGRAM);
|
||||
color_vec4(rounded_rect_program_layout.uniforms.color, bar_color, opacity);
|
||||
color_vec4(rounded_rect_program_layout.uniforms.background_color, 0, 0.0f);
|
||||
|
||||
float y = (float)ui->full_framebuffer_height - (float)(thumb_top_px + thumb_height_px);
|
||||
glUniform4f(rounded_rect_program_layout.uniforms.rect,
|
||||
(float)scrollbar_left, y,
|
||||
(float)scrollbar_width_px, (float)thumb_height_px);
|
||||
|
||||
float thickness = (float)MAX(scrollbar_width_px, thumb_height_px);
|
||||
glUniform2f(rounded_rect_program_layout.uniforms.params, thickness, (float)scrollbar_radius);
|
||||
|
||||
save_viewport_using_top_left_origin(scrollbar_left, thumb_top_px,
|
||||
scrollbar_width_px, thumb_height_px,
|
||||
ui->full_framebuffer_height);
|
||||
draw_quad(true, 0);
|
||||
restore_viewport();
|
||||
} else {
|
||||
set_color_uniform_with_opacity(bar_color, opacity);
|
||||
glUniform4f(tint_program_layout.uniforms.edges, -1.f, thumb_top_gl, 1.f, thumb_bottom_gl);
|
||||
draw_quad(true, 0);
|
||||
restore_viewport();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
|
|||
w->visible = true;
|
||||
w->title = title;
|
||||
Py_XINCREF(title);
|
||||
w->scrollbar.is_hovering = false;
|
||||
if (!set_window_logo(w, OPT(default_window_logo), OPT(window_logo_position), OPT(window_logo_alpha), true, NULL, 0)) {
|
||||
log_error("Failed to load default window logo: %s", OPT(default_window_logo));
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
|
|
@ -987,16 +988,20 @@ PYWRAP1(set_window_padding) {
|
|||
|
||||
PYWRAP1(set_window_render_data) {
|
||||
#define B(name) &(g.name)
|
||||
#define S(name) &(g.spaces.name)
|
||||
id_type os_window_id, tab_id, window_id;
|
||||
WindowGeometry g = {0};
|
||||
Screen *screen;
|
||||
PA("KKKOIIII", &os_window_id, &tab_id, &window_id, &screen, B(left), B(top), B(right), B(bottom));
|
||||
PA("KKKOIIIIIIII", &os_window_id, &tab_id, &window_id, &screen,
|
||||
B(left), B(top), B(right), B(bottom),
|
||||
S(left), S(top), S(right), S(bottom));
|
||||
|
||||
WITH_WINDOW(os_window_id, tab_id, window_id);
|
||||
init_window_render_data(&window->render_data, g, screen);
|
||||
END_WITH_WINDOW;
|
||||
Py_RETURN_NONE;
|
||||
#undef B
|
||||
#undef S
|
||||
}
|
||||
|
||||
PYWRAP1(update_window_visibility) {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,17 @@ typedef struct Options {
|
|||
WindowTitleIn macos_show_window_title_in;
|
||||
char *bell_path, *bell_theme;
|
||||
float background_opacity, dim_opacity, scrollback_indicator_opacity;
|
||||
bool scrollbar_interactive;
|
||||
float scrollbar_opacity;
|
||||
float scrollbar_track_opacity;
|
||||
color_type scrollbar_color;
|
||||
unsigned int scrollbar_width;
|
||||
unsigned int scrollbar_gap;
|
||||
unsigned int scrollbar_min_thumb_height;
|
||||
unsigned int scrollbar_hitbox_expansion;
|
||||
unsigned int scrollbar_radius;
|
||||
bool scrollbar_autohide;
|
||||
ScrollbarTrackBehavior scrollbar_track_behavior;
|
||||
float text_contrast, text_gamma_adjustment;
|
||||
bool text_old_gamma;
|
||||
|
||||
|
|
@ -144,6 +155,9 @@ typedef struct WindowLogoRenderData {
|
|||
|
||||
typedef struct {
|
||||
unsigned int left, top, right, bottom;
|
||||
struct {
|
||||
unsigned int left, top, right, bottom;
|
||||
} spaces;
|
||||
} WindowGeometry;
|
||||
|
||||
typedef struct WindowRenderData {
|
||||
|
|
@ -214,6 +228,13 @@ typedef struct Window {
|
|||
PendingClick *clicks;
|
||||
size_t num, capacity;
|
||||
} pending_clicks;
|
||||
struct {
|
||||
double thumb_top, thumb_bottom;
|
||||
bool is_dragging;
|
||||
double drag_start_y;
|
||||
double drag_start_scrolled_by;
|
||||
bool is_hovering;
|
||||
} scrollbar;
|
||||
} Window;
|
||||
|
||||
typedef struct BorderRect {
|
||||
|
|
|
|||
|
|
@ -970,7 +970,9 @@ class Window:
|
|||
mark_os_window_dirty(self.os_window_id)
|
||||
|
||||
self.geometry = g = new_geometry
|
||||
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen, *g[:4])
|
||||
set_window_render_data(self.os_window_id, self.tab_id, self.id, self.screen,
|
||||
g.left, g.top, g.right, g.bottom,
|
||||
g.spaces.left, g.spaces.top, g.spaces.right, g.spaces.bottom)
|
||||
self.update_effective_padding()
|
||||
if update_ime_position:
|
||||
update_ime_position_for_window(self.id, True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue