mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-07-03 11:12:30 +08:00
Implement smooth animated scrolling for scroll_line_up and scroll_line_down
Fixes #9784
This commit is contained in:
committed by
Kovid Goyal
parent
50cc4f6630
commit
35ca3a178d
@@ -170,6 +170,12 @@ Detailed list of changes
|
||||
|
||||
- A new option :opt:`palette_generate` to automatically generate the 256 color palette from the first 16 colors (:pull:`9426`)
|
||||
|
||||
- :ac:`scroll_line_up` and :ac:`scroll_line_down` now support an optional
|
||||
``smooth`` argument that performs smooth animated scrolling, timed to complete
|
||||
within the platform's keyboard repeat interval. The default key mappings use
|
||||
this argument. Releasing the key or triggering any other scroll action
|
||||
immediately finishes the animation.
|
||||
|
||||
- For builtin key mappings automatically :ref:`fallback <mapping-fallback>` to matching the US-PC layout key when the pressed key has no matches and is a non-English character (:pull:`9671`)
|
||||
|
||||
- Allow drag and drop of windows to re-arrange them, move them to another
|
||||
|
||||
@@ -1647,6 +1647,12 @@ class Boss:
|
||||
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
|
||||
return self.mappings.dispatch_possible_special_key(ev)
|
||||
|
||||
def on_shortcut_key_release(self, ev: KeyEvent) -> bool:
|
||||
window = self.active_window
|
||||
if window is not None:
|
||||
window.finish_scroll_animation()
|
||||
return False
|
||||
|
||||
def cancel_current_visual_select(self) -> None:
|
||||
if self.current_visual_select:
|
||||
self.current_visual_select.cancel()
|
||||
|
||||
@@ -1326,6 +1326,9 @@ class Screen:
|
||||
def scroll(self, amt: int, upwards: bool) -> bool:
|
||||
pass
|
||||
|
||||
def scroll_to_absolute(self, amt: float) -> None:
|
||||
pass
|
||||
|
||||
def fractional_scroll(self, amt: float) -> bool:
|
||||
pass
|
||||
|
||||
@@ -1629,6 +1632,10 @@ def get_click_interval() -> float:
|
||||
pass
|
||||
|
||||
|
||||
def glfw_get_keyboard_repeat_interval() -> float:
|
||||
pass
|
||||
|
||||
|
||||
def send_data_to_peer(peer_id: int, data: Union[str, bytes], is_async_response: bool = False) -> None:
|
||||
pass
|
||||
|
||||
|
||||
10
kitty/glfw.c
10
kitty/glfw.c
@@ -2743,6 +2743,15 @@ get_click_interval(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
return PyFloat_FromDouble(monotonic_t_to_s_double(OPT(click_interval)));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
glfw_get_keyboard_repeat_interval(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
#define DEFAULT_KEYBOARD_REPEAT_INTERVAL_MS 30ll
|
||||
monotonic_t interval = ms_to_monotonic_t(DEFAULT_KEYBOARD_REPEAT_INTERVAL_MS);
|
||||
glfwGetKeyboardRepeatDelay(NULL, &interval);
|
||||
return PyFloat_FromDouble(monotonic_t_to_s_double(interval));
|
||||
#undef DEFAULT_KEYBOARD_REPEAT_INTERVAL_MS
|
||||
}
|
||||
|
||||
id_type
|
||||
add_main_loop_timer(monotonic_t interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback) {
|
||||
return glfwAddTimer(interval, repeats, callback, callback_data, free_callback);
|
||||
@@ -3031,6 +3040,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(x11_display, METH_NOARGS),
|
||||
METHODB(wayland_compositor_data, METH_NOARGS),
|
||||
METHODB(get_click_interval, METH_NOARGS),
|
||||
METHODB(glfw_get_keyboard_repeat_interval, METH_NOARGS),
|
||||
METHODB(is_layer_shell_supported, METH_NOARGS),
|
||||
METHODB(x11_window_id, METH_O),
|
||||
METHODB(strip_csi, METH_O),
|
||||
|
||||
@@ -292,7 +292,8 @@ on_key_input(const GLFWkeyevent *ev) {
|
||||
screen = w->render_data.screen;
|
||||
} else if (w->last_special_key_pressed == key) {
|
||||
w->last_special_key_pressed = 0;
|
||||
debug("ignoring release event for previous press that was handled as shortcut\n");
|
||||
dispatch_key_event(on_shortcut_key_release);
|
||||
debug("dispatched release event for shortcut key\n");
|
||||
return;
|
||||
}
|
||||
if (w->buffered_keys.enabled) {
|
||||
|
||||
@@ -20,6 +20,14 @@ static MouseShape mouse_cursor_shape = TEXT_POINTER;
|
||||
typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE, LEAVE } MouseAction;
|
||||
#define debug debug_input
|
||||
|
||||
static void
|
||||
finish_scroll_animation(Screen *screen) {
|
||||
if (screen->callbacks != Py_None) {
|
||||
PyObject *ret = PyObject_CallMethod(screen->callbacks, "finish_scroll_animation", NULL);
|
||||
if (ret == NULL) PyErr_Print(); else Py_DECREF(ret);
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding of mouse events {{{
|
||||
#define SHIFT_INDICATOR (1 << 2)
|
||||
#define ALT_INDICATOR (1 << 3)
|
||||
@@ -340,6 +348,7 @@ static bool
|
||||
do_drag_scroll(Window *w, bool upwards) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
finish_scroll_animation(screen);
|
||||
screen_history_scroll(screen, SCROLL_LINE, upwards);
|
||||
update_drag(w);
|
||||
if (mouse_cursor_shape != DEFAULT_POINTER) {
|
||||
@@ -506,6 +515,7 @@ handle_scrollbar_track_click(Window *w, double mouse_y) {
|
||||
if (!w) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
if (!validate_scrollbar_state(w)) return;
|
||||
finish_scroll_animation(screen);
|
||||
|
||||
if (OPT(scrollbar_jump_on_click)) {
|
||||
ScrollbarGeometry geom = calculate_scrollbar_geometry(w);
|
||||
@@ -563,6 +573,7 @@ static void
|
||||
handle_scrollbar_drag(Window *w, double mouse_y) {
|
||||
if (!w || !w->scrollbar.is_dragging || !validate_scrollbar_state(w)) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
finish_scroll_animation(screen);
|
||||
ScrollbarGeometry geom = calculate_scrollbar_geometry(w);
|
||||
double scrollbar_height = geom.bottom - geom.top;
|
||||
double mouse_pane_fraction = (mouse_y - geom.top) / scrollbar_height;
|
||||
@@ -1486,6 +1497,7 @@ scroll_event(const GLFWScrollEvent *ev) {
|
||||
case GLFW_MOMENTUM_PHASE_MAY_BEGIN:
|
||||
break;
|
||||
}
|
||||
finish_scroll_animation(screen);
|
||||
if (ev->y_offset != 0.0) {
|
||||
if (screen->modes.mouse_tracking_mode == NO_TRACKING && pixel_scroll_enabled_for_screen(screen) && (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES || ev->offset_type == GLFW_SCROLL_OFFEST_V120)) {
|
||||
double delta_pixels;
|
||||
|
||||
@@ -2901,32 +2901,32 @@ egr() # }}}
|
||||
agr('shortcuts.scrolling', 'Scrolling')
|
||||
|
||||
map('Scroll line up',
|
||||
'scroll_line_up kitty_mod+up scroll_line_up',
|
||||
'scroll_line_up kitty_mod+up scroll_line_up smooth',
|
||||
)
|
||||
map('Scroll line up',
|
||||
'scroll_line_up --allow-fallback=shifted,ascii kitty_mod+k scroll_line_up',
|
||||
'scroll_line_up --allow-fallback=shifted,ascii kitty_mod+k scroll_line_up smooth',
|
||||
)
|
||||
map('Scroll line up',
|
||||
'scroll_line_up opt+cmd+page_up scroll_line_up',
|
||||
'scroll_line_up opt+cmd+page_up scroll_line_up smooth',
|
||||
only='macos',
|
||||
)
|
||||
map('Scroll line up',
|
||||
'scroll_line_up cmd+up scroll_line_up',
|
||||
'scroll_line_up cmd+up scroll_line_up smooth',
|
||||
only='macos',
|
||||
)
|
||||
|
||||
map('Scroll line down',
|
||||
'scroll_line_down kitty_mod+down scroll_line_down',
|
||||
'scroll_line_down kitty_mod+down scroll_line_down smooth',
|
||||
)
|
||||
map('Scroll line down',
|
||||
'scroll_line_down --allow-fallback=shifted,ascii kitty_mod+j scroll_line_down',
|
||||
'scroll_line_down --allow-fallback=shifted,ascii kitty_mod+j scroll_line_down smooth',
|
||||
)
|
||||
map('Scroll line down',
|
||||
'scroll_line_down opt+cmd+page_down scroll_line_down',
|
||||
'scroll_line_down opt+cmd+page_down scroll_line_down smooth',
|
||||
only='macos',
|
||||
)
|
||||
map('Scroll line down',
|
||||
'scroll_line_down cmd+down scroll_line_down',
|
||||
'scroll_line_down cmd+down scroll_line_down smooth',
|
||||
only='macos',
|
||||
)
|
||||
|
||||
|
||||
16
kitty/options/types.py
generated
16
kitty/options/types.py
generated
@@ -870,13 +870,13 @@ defaults.map = [
|
||||
# pass_selection_to_program
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=111), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='pass_selection_to_program'),
|
||||
# scroll_line_up
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=57352), definition='scroll_line_up'),
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=57352), definition='scroll_line_up smooth'),
|
||||
# scroll_line_up
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=107), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_up'),
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=107), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_up smooth'),
|
||||
# scroll_line_down
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=57353), definition='scroll_line_down'),
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=57353), definition='scroll_line_down smooth'),
|
||||
# scroll_line_down
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=106), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_down'),
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=106), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='scroll_line_down smooth'),
|
||||
# scroll_page_up
|
||||
KeyDefinition(trigger=SingleKey(mods=256, key=57354), definition='scroll_page_up'),
|
||||
# scroll_page_down
|
||||
@@ -1022,10 +1022,10 @@ defaults.map = [
|
||||
if is_macos:
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=99), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), definition='copy_or_noop'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=118), options=KeyMapOptions(when_focus_on='', new_mode='', mode='', on_unknown='beep', on_action='keep', timeout=None, allow_fallback=(KeyFallbackType.shifted, KeyFallbackType.alternate)), 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'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57355), definition='scroll_line_down'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57353), definition='scroll_line_down'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57354), definition='scroll_line_up smooth'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57352), definition='scroll_line_up smooth'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=10, key=57355), definition='scroll_line_down smooth'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57353), definition='scroll_line_down smooth'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57354), definition='scroll_page_up'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57355), definition='scroll_page_down'))
|
||||
defaults.map.append(KeyDefinition(trigger=SingleKey(mods=8, key=57356), definition='scroll_home'))
|
||||
|
||||
@@ -88,6 +88,11 @@ def parse_send_text_bytes(text: str) -> bytes:
|
||||
return defines.expand_ansi_c_escapes(text).encode('utf-8')
|
||||
|
||||
|
||||
@func_with_args('scroll_line_up', 'scroll_line_down')
|
||||
def scroll_line_updown(func: str, rest: str) -> FuncArgsType:
|
||||
return func, [rest.strip().lower() == 'smooth']
|
||||
|
||||
|
||||
@func_with_args('scroll_prompt_to_top')
|
||||
def scroll_prompt_to_top(func: str, rest: str) -> FuncArgsType:
|
||||
return func, [to_bool(rest) if rest else False]
|
||||
|
||||
@@ -5149,6 +5149,16 @@ fractional_scroll(Screen *self, PyObject *amt) {
|
||||
return Py_NewRef(screen_fractional_scroll(self, y) ? Py_True : Py_False);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
scroll_to_absolute(Screen *self, PyObject *amt) {
|
||||
double y;
|
||||
if (PyFloat_Check(amt)) y = PyFloat_AS_DOUBLE(amt);
|
||||
else if (PyLong_Check(amt)) y = PyLong_AsDouble(amt);
|
||||
else { PyErr_SetString(PyExc_TypeError, "amt must be a number"); return NULL; }
|
||||
screen_history_scroll_to_absolute(self, y);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
scroll(Screen *self, PyObject *args) {
|
||||
int amt, upwards;
|
||||
@@ -6055,6 +6065,7 @@ static PyMethodDef methods[] = {
|
||||
MND(text_for_marked_url, METH_VARARGS)
|
||||
MND(is_rectangle_select, METH_NOARGS)
|
||||
MND(scroll, METH_VARARGS)
|
||||
MND(scroll_to_absolute, METH_O)
|
||||
MND(fractional_scroll, METH_O)
|
||||
MND(scroll_to_prompt, METH_VARARGS)
|
||||
MND(set_last_visited_prompt, METH_VARARGS)
|
||||
|
||||
@@ -71,6 +71,7 @@ from .fast_data_types import (
|
||||
get_mouse_data_for_window,
|
||||
get_options,
|
||||
get_window_logo_settings_if_not_default,
|
||||
glfw_get_keyboard_repeat_interval,
|
||||
is_css_pointer_name_valid,
|
||||
is_modifier_key,
|
||||
last_focused_os_window_id,
|
||||
@@ -80,6 +81,7 @@ from .fast_data_types import (
|
||||
move_cursor_to_mouse_if_in_prompt,
|
||||
pointer_name_to_css_name,
|
||||
pt_to_px,
|
||||
remove_timer,
|
||||
replace_c0_codes_except_nl_space_tab,
|
||||
set_redirect_keys_to_overlay,
|
||||
set_window_logo,
|
||||
@@ -121,6 +123,20 @@ from .utils import (
|
||||
|
||||
MatchPatternType = Union[Pattern[str], tuple[Pattern[str], Optional[Pattern[str]]]]
|
||||
|
||||
# Target ~60 fps for scroll animation ticks
|
||||
_SCROLL_ANIMATION_FRAME_INTERVAL: float = 1.0 / 60.0
|
||||
|
||||
|
||||
class ScrollAnimation:
|
||||
__slots__ = ('timer', 'start', 'duration', 'total', 'start_scrolled_by')
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.timer: int = 0
|
||||
self.start: float = 0.
|
||||
self.duration: float = 0.
|
||||
self.total: float = 0.
|
||||
self.start_scrolled_by: int = 0
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kittens.tui.handler import OpenUrlHandler
|
||||
@@ -759,6 +775,7 @@ class Window:
|
||||
self.screen.copy_colors_from(copy_colors_from.screen)
|
||||
self.remote_control_passwords = remote_control_passwords
|
||||
self.allow_remote_control = allow_remote_control
|
||||
self._scroll_animation = ScrollAnimation()
|
||||
|
||||
def remote_control_allowed(self, pcmd: dict[str, Any], extra_data: dict[str, Any]) -> bool:
|
||||
if not self.allow_remote_control:
|
||||
@@ -2388,27 +2405,89 @@ class Window:
|
||||
def scroll_fractional_lines(self, amt: float) -> bool | None:
|
||||
' Scroll fractionally, negative values are up and positive values are down '
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.fractional_scroll(amt)
|
||||
return None
|
||||
return True
|
||||
|
||||
@ac('sc', 'Scroll up by one line when in main screen. To scroll by different amounts, you can map the remote_control scroll-window action.')
|
||||
def scroll_line_up(self) -> bool | None:
|
||||
def _scroll_animation_tick(self, timer_id: int | None) -> None:
|
||||
a = self._scroll_animation
|
||||
if not a.timer:
|
||||
return
|
||||
now = monotonic()
|
||||
elapsed = now - a.start
|
||||
progress = min(1.0, elapsed / a.duration) if a.duration > 0 else 1.0
|
||||
if progress >= 1.0:
|
||||
# Ensure we land exactly on a line boundary with pixel_scroll_offset_y = 0
|
||||
self.screen.scroll_to_absolute(max(0, a.start_scrolled_by - a.total))
|
||||
a.timer = 0
|
||||
else:
|
||||
# Use absolute positioning to avoid pixel rounding errors from incremental fractional scrolls
|
||||
self.screen.scroll_to_absolute(max(0.0, a.start_scrolled_by - a.total * progress))
|
||||
|
||||
def finish_scroll_animation(self) -> None:
|
||||
' Finish any in-progress scroll animation immediately '
|
||||
a = self._scroll_animation
|
||||
if a.timer:
|
||||
remove_timer(a.timer)
|
||||
a.timer = 0
|
||||
# Scroll to the exact integer target line, ensuring pixel_scroll_offset_y = 0
|
||||
self.screen.scroll_to_absolute(max(0, a.start_scrolled_by - a.total))
|
||||
|
||||
def _start_scroll_animation(self, lines: float) -> None:
|
||||
' Start a smooth scroll animation for the given number of lines (negative=up, positive=down) '
|
||||
self.finish_scroll_animation()
|
||||
if not self.screen.is_main_linebuf():
|
||||
return
|
||||
duration = glfw_get_keyboard_repeat_interval()
|
||||
if duration <= 0:
|
||||
self.screen.fractional_scroll(lines)
|
||||
return
|
||||
a = self._scroll_animation
|
||||
a.start = monotonic()
|
||||
a.duration = duration
|
||||
a.total = lines
|
||||
a.start_scrolled_by = self.screen.scrolled_by
|
||||
a.timer = add_timer(self._scroll_animation_tick, min(_SCROLL_ANIMATION_FRAME_INTERVAL, duration / 2), True)
|
||||
|
||||
@ac('sc', '''
|
||||
Scroll up by one line when in main screen. To scroll by different amounts, you can map the remote_control
|
||||
scroll-window action. Pass the ``smooth`` argument to have the scrolling be animated over the keyboard
|
||||
repeat interval. For example::
|
||||
|
||||
map kitty_mod+up scroll_line_up smooth
|
||||
''')
|
||||
def scroll_line_up(self, smooth: bool = False) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_LINE, True)
|
||||
if smooth:
|
||||
self._start_scroll_animation(-1.0)
|
||||
else:
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_LINE, True)
|
||||
return None
|
||||
return True
|
||||
|
||||
@ac('sc', 'Scroll down by one line when in main screen. To scroll by different amounts, you can map the remote_control scroll-window action.')
|
||||
def scroll_line_down(self) -> bool | None:
|
||||
@ac('sc', '''
|
||||
Scroll down by one line when in main screen. To scroll by different amounts, you can map the remote_control
|
||||
scroll-window action. Pass the ``smooth`` argument to have the scrolling be animated over the keyboard
|
||||
repeat interval. For example::
|
||||
|
||||
map kitty_mod+down scroll_line_down smooth
|
||||
''')
|
||||
def scroll_line_down(self, smooth: bool = False) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.screen.scroll(SCROLL_LINE, False)
|
||||
if smooth:
|
||||
self._start_scroll_animation(1.0)
|
||||
else:
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_LINE, False)
|
||||
return None
|
||||
return True
|
||||
|
||||
@ac('sc', 'Scroll up by one page when in main screen. To scroll by different amounts, you can map the remote_control scroll-window action.')
|
||||
def scroll_page_up(self) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_PAGE, True)
|
||||
return None
|
||||
return True
|
||||
@@ -2416,6 +2495,7 @@ class Window:
|
||||
@ac('sc', 'Scroll down by one page when in main screen. To scroll by different amounts, you can map the remote_control scroll-window action.')
|
||||
def scroll_page_down(self) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_PAGE, False)
|
||||
return None
|
||||
return True
|
||||
@@ -2423,6 +2503,7 @@ class Window:
|
||||
@ac('sc', 'Scroll to the top of the scrollback buffer when in main screen')
|
||||
def scroll_home(self) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_FULL, True)
|
||||
return None
|
||||
return True
|
||||
@@ -2430,6 +2511,7 @@ class Window:
|
||||
@ac('sc', 'Scroll to the bottom of the scrollback buffer when in main screen')
|
||||
def scroll_end(self) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll(SCROLL_FULL, False)
|
||||
return None
|
||||
return True
|
||||
@@ -2455,6 +2537,7 @@ class Window:
|
||||
''')
|
||||
def scroll_to_prompt(self, num_of_prompts: int = -1, scroll_offset: int = 0) -> bool | None:
|
||||
if self.screen.is_main_linebuf():
|
||||
self.finish_scroll_animation()
|
||||
self.screen.scroll_to_prompt(num_of_prompts, scroll_offset)
|
||||
return None
|
||||
return True
|
||||
|
||||
@@ -153,6 +153,9 @@ class Callbacks:
|
||||
def on_activity_since_last_focus(self) -> None:
|
||||
pass
|
||||
|
||||
def finish_scroll_animation(self) -> None:
|
||||
pass
|
||||
|
||||
def on_mouse_event(self, event):
|
||||
ev = MouseEvent(**event)
|
||||
opts = get_options()
|
||||
|
||||
Reference in New Issue
Block a user