mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-02 12:44:01 +02:00
Merge branch 'feat/scroll-per-pixel' of https://github.com/idelice/kitty
This commit is contained in:
@@ -206,6 +206,16 @@ displays an interactive :opt:`scrollbar` 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.
|
||||
|
||||
If you use a high‑precision input device such as a touchpad, you can enable
|
||||
smooth, per‑pixel scrollback with :opt:`pixel_scroll`. For example, add this to
|
||||
your :file:`kitty.conf`::
|
||||
|
||||
pixel_scroll yes
|
||||
|
||||
You can further tune the sensitivity with :opt:`touch_scroll_multiplier`. Note
|
||||
that this only affects scrolling kitty's own scrollback, not applications
|
||||
running inside the terminal that handle their own scrolling.
|
||||
|
||||
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
|
||||
while typing in a follow-up command. |kitty| allows you to do this by pressing
|
||||
|
||||
@@ -21,6 +21,7 @@ layout(std140) uniform CellRenderData {
|
||||
uniform float gamma_lut[256];
|
||||
uniform uint draw_bg_bitfield;
|
||||
uniform usampler2D sprite_decorations_map;
|
||||
uniform float row_offset;
|
||||
|
||||
// Have to use fixed locations here as all variants of the cell program share the same VAOs
|
||||
layout(location=0) in uvec3 colors;
|
||||
@@ -212,7 +213,7 @@ CellData set_vertex_position(vec3 cell_fg, vec3 cell_bg) {
|
||||
uint column = instance_id - row * columns;
|
||||
/* The position of this vertex, at a corner of the cell */
|
||||
float left = -1.0 + column * dx;
|
||||
float top = 1.0 - row * dy;
|
||||
float top = 1.0 - (float(row) + row_offset) * dy;
|
||||
uvec2 pos = cell_pos_map[gl_VertexID];
|
||||
gl_Position = vec4(vec2(left, left + dx)[pos.x], vec2(top, top - dy)[pos.y], 0, 1);
|
||||
// The character sprite being rendered
|
||||
|
||||
@@ -249,6 +249,7 @@ grman_pause_rendering(GraphicsManager *self, GraphicsManager *dest) {
|
||||
dest->window_id = self->window_id;
|
||||
dest->layers_dirty = true;
|
||||
dest->last_scrolled_by = 0;
|
||||
dest->last_scroll_offset_lines = 0.0f;
|
||||
|
||||
iter_images(self) {
|
||||
Image *clone = calloc(1, sizeof(Image)), *img = i.data->val;
|
||||
@@ -1202,9 +1203,10 @@ resolve_parent_offset(const GraphicsManager *self, const ImageRef *ref, int32_t
|
||||
|
||||
|
||||
bool
|
||||
grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) {
|
||||
if (self->last_scrolled_by != scrolled_by) set_layers_dirty(self);
|
||||
grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scroll_offset_lines, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) {
|
||||
if (self->last_scrolled_by != scrolled_by || self->last_scroll_offset_lines != scroll_offset_lines) set_layers_dirty(self);
|
||||
self->last_scrolled_by = scrolled_by;
|
||||
self->last_scroll_offset_lines = scroll_offset_lines;
|
||||
if (!self->layers_dirty) return false;
|
||||
self->layers_dirty = false;
|
||||
size_t i;
|
||||
@@ -1216,7 +1218,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
||||
float screen_bottom = screen_top - screen_height;
|
||||
float screen_width_px = num_cols * cell.width;
|
||||
float screen_height_px = num_rows * cell.height;
|
||||
float y0 = screen_top - dy * scrolled_by;
|
||||
float y0 = screen_top - dy * ((float)scrolled_by + scroll_offset_lines);
|
||||
|
||||
// Iterate over all visible refs and create render data
|
||||
self->render_data.count = 0;
|
||||
@@ -2367,10 +2369,10 @@ W(shm_unlink) {
|
||||
}
|
||||
|
||||
W(update_layers) {
|
||||
unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy;
|
||||
unsigned int scrolled_by, sx, sy; float xstart, ystart, dx, dy, scroll_offset_lines = 0.0f;
|
||||
CellPixelSize cell;
|
||||
PA("IffffIIII", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height);
|
||||
grman_update_layers(self, scrolled_by, xstart, ystart, dx, dy, sx, sy, cell);
|
||||
PA("IffffIIII|f", &scrolled_by, &xstart, &ystart, &dx, &dy, &sx, &sy, &cell.width, &cell.height, &scroll_offset_lines);
|
||||
grman_update_layers(self, scrolled_by, scroll_offset_lines, xstart, ystart, dx, dy, sx, sy, cell);
|
||||
PyObject *ans = PyTuple_New(self->render_data.count);
|
||||
for (size_t i = 0; i < self->render_data.count; i++) {
|
||||
ImageRenderData *r = self->render_data.item + i;
|
||||
|
||||
@@ -145,6 +145,7 @@ typedef struct {
|
||||
// The number of images below MIN_ZINDEX / 2, then the number of refs between MIN_ZINDEX / 2 and -1 inclusive, then the number of refs above 0 inclusive.
|
||||
size_t num_of_below_refs, num_of_negative_refs, num_of_positive_refs;
|
||||
unsigned int last_scrolled_by;
|
||||
float last_scroll_offset_lines;
|
||||
size_t used_storage;
|
||||
PyObject *disk_cache;
|
||||
bool has_images_needing_animation, context_made_current_for_this_command;
|
||||
@@ -202,7 +203,7 @@ GraphicsManager* grman_alloc(bool for_paused_rendering);
|
||||
void grman_clear(GraphicsManager*, bool, CellPixelSize fg);
|
||||
const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize fg);
|
||||
void grman_put_cell_image(GraphicsManager *self, uint32_t row, uint32_t col, uint32_t image_id, uint32_t placement_id, uint32_t x, uint32_t y, uint32_t w, uint32_t h, CellPixelSize cell);
|
||||
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize);
|
||||
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scroll_offset_lines, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize);
|
||||
void grman_scroll_images(GraphicsManager *self, const ScrollData*, CellPixelSize fg);
|
||||
void grman_resize(GraphicsManager*, index_type, index_type, index_type, index_type, index_type, index_type);
|
||||
void grman_rescale(GraphicsManager *self, CellPixelSize fg);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "state.h"
|
||||
#include "screen.h"
|
||||
#include "charsets.h"
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
@@ -1232,6 +1233,11 @@ scroll_phase(GLFWMomentumType t) {
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
pixel_scroll_enabled_for_screen(const Screen *screen) {
|
||||
return OPT(pixel_scroll) && screen->linebuf == screen->main_linebuf;
|
||||
}
|
||||
|
||||
void
|
||||
scroll_event(const GLFWScrollEvent *ev) {
|
||||
debug("\x1b[36mScroll\x1b[m %s x: %f y: %f momentum: %s modifiers: %s\n", scroll_offset_type(ev->offset_type), ev->x_offset, ev->y_offset, scroll_phase(ev->momentum_type), format_mods(ev->keyboard_modifiers));
|
||||
@@ -1285,30 +1291,41 @@ scroll_event(const GLFWScrollEvent *ev) {
|
||||
case GLFW_MOMENTUM_PHASE_MAY_BEGIN:
|
||||
break;
|
||||
}
|
||||
int s;
|
||||
if (ev->y_offset != 0.0) {
|
||||
s = scale_scroll(screen->modes.mouse_tracking_mode, ev->y_offset, ev->offset_type, &screen->pending_scroll_pixels_y, global_state.callback_os_window->fonts_data->fcm.cell_height);
|
||||
if (s) {
|
||||
bool upwards = s > 0;
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_scroll(w, upwards ? 4 : 5, ev->keyboard_modifiers);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf);
|
||||
}
|
||||
}
|
||||
if (!screen->modes.mouse_tracking_mode && pixel_scroll_enabled_for_screen(screen) && (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES || ev->offset_type == GLFW_SCROLL_OFFEST_V120)) {
|
||||
double delta_pixels = 0.0;
|
||||
if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) {
|
||||
delta_pixels = ev->y_offset * OPT(touch_scroll_multiplier);
|
||||
} else {
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
if (screen->selections.in_progress) update_drag(w);
|
||||
const double offset_lines = (ev->y_offset / 120.) * OPT(wheel_scroll_multiplier);
|
||||
delta_pixels = offset_lines * global_state.callback_os_window->fonts_data->fcm.cell_height;
|
||||
}
|
||||
screen->pending_scroll_pixels_y = 0.0;
|
||||
if (screen_apply_pixel_scroll(screen, delta_pixels) && screen->selections.in_progress) update_drag(w);
|
||||
} else {
|
||||
int s = scale_scroll(screen->modes.mouse_tracking_mode, ev->y_offset, ev->offset_type, &screen->pending_scroll_pixels_y, global_state.callback_os_window->fonts_data->fcm.cell_height);
|
||||
if (s) {
|
||||
bool upwards = s > 0;
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_scroll(w, upwards ? 4 : 5, ev->keyboard_modifiers);
|
||||
if (sz > 0) {
|
||||
mouse_event_buf[sz] = 0;
|
||||
for (s = abs(s); s > 0; s--) {
|
||||
write_escape_code_to_child(screen, ESC_CSI, mouse_event_buf);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (screen->linebuf == screen->main_linebuf) {
|
||||
screen_history_scroll(screen, abs(s), upwards);
|
||||
if (screen->selections.in_progress) update_drag(w);
|
||||
}
|
||||
else fake_scroll(w, abs(s), upwards);
|
||||
}
|
||||
else fake_scroll(w, abs(s), upwards);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ev->x_offset != 0.0) {
|
||||
s = scale_scroll(screen->modes.mouse_tracking_mode, ev->x_offset, ev->offset_type, &screen->pending_scroll_pixels_x, global_state.callback_os_window->fonts_data->fcm.cell_width);
|
||||
int s = scale_scroll(screen->modes.mouse_tracking_mode, ev->x_offset, ev->offset_type, &screen->pending_scroll_pixels_x, global_state.callback_os_window->fonts_data->fcm.cell_width);
|
||||
if (s) {
|
||||
if (screen->modes.mouse_tracking_mode) {
|
||||
int sz = encode_mouse_scroll(w, s > 0 ? 6 : 7, ev->keyboard_modifiers);
|
||||
|
||||
@@ -586,6 +586,16 @@ opt('touch_scroll_multiplier', '1.0',
|
||||
Multiplier for the number of lines scrolled by a touchpad. Note that this is
|
||||
only used for high precision scrolling devices on platforms such as macOS and
|
||||
Wayland. Use negative numbers to change scroll direction.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('pixel_scroll', 'no',
|
||||
option_type='to_bool', ctype='bool',
|
||||
long_text='''
|
||||
Enable per-pixel scrolling for high precision input devices (for example
|
||||
touchpads). When enabled, kitty's own scrollback can move by sub-line increments
|
||||
instead of only whole lines. This does not affect applications running inside
|
||||
the terminal (for example full-screen TUIs) that handle scrolling themselves.
|
||||
'''
|
||||
)
|
||||
egr() # }}}
|
||||
|
||||
3
kitty/options/parse.py
generated
3
kitty/options/parse.py
generated
@@ -1389,6 +1389,9 @@ class Parser:
|
||||
def touch_scroll_multiplier(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['touch_scroll_multiplier'] = float(val)
|
||||
|
||||
def pixel_scroll(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['pixel_scroll'] = to_bool(val)
|
||||
|
||||
def transparent_background_colors(self, val: str, ans: dict[str, typing.Any]) -> None:
|
||||
ans['transparent_background_colors'] = transparent_background_colors(val)
|
||||
|
||||
|
||||
15
kitty/options/to-c-generated.h
generated
15
kitty/options/to-c-generated.h
generated
@@ -499,6 +499,19 @@ convert_from_opts_touch_scroll_multiplier(PyObject *py_opts, Options *opts) {
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_pixel_scroll(PyObject *val, Options *opts) {
|
||||
opts->pixel_scroll = PyObject_IsTrue(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_pixel_scroll(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "pixel_scroll");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_pixel_scroll(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_mouse_hide_wait(PyObject *val, Options *opts) {
|
||||
mouse_hide_wait(val, opts);
|
||||
@@ -1422,6 +1435,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_touch_scroll_multiplier(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_pixel_scroll(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_mouse_hide_wait(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_url_color(py_opts, opts);
|
||||
|
||||
4
kitty/options/types.py
generated
4
kitty/options/types.py
generated
@@ -464,6 +464,7 @@ option_names = (
|
||||
'text_composition_strategy',
|
||||
'text_fg_override_threshold',
|
||||
'touch_scroll_multiplier',
|
||||
'pixel_scroll',
|
||||
'transparent_background_colors',
|
||||
'undercurl_style',
|
||||
'underline_exclusion',
|
||||
@@ -651,6 +652,7 @@ class Options:
|
||||
text_composition_strategy: str = 'platform'
|
||||
text_fg_override_threshold: tuple[float, typing.Literal['%', 'ratio']] = (0.0, '%')
|
||||
touch_scroll_multiplier: float = 1.0
|
||||
pixel_scroll: bool = False
|
||||
transparent_background_colors: tuple[tuple[kitty.fast_data_types.Color, float], ...] = ()
|
||||
undercurl_style: choices_for_undercurl_style = 'thin-sparse'
|
||||
underline_exclusion: tuple[float, typing.Literal['', 'px', 'pt']] = (1.0, '')
|
||||
@@ -1111,4 +1113,4 @@ special_colors = frozenset({
|
||||
})
|
||||
|
||||
|
||||
secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass')
|
||||
secret_options = ('remote_control_password', 'file_transfer_confirmation_bypass')
|
||||
|
||||
215
kitty/screen.c
215
kitty/screen.c
@@ -162,6 +162,15 @@ new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
init_tabstops(self->alt_tabstops, self->columns);
|
||||
self->key_encoding_flags = self->main_key_encoding_flags;
|
||||
if (!init_overlay_line(self, self->columns, false)) { Py_CLEAR(self); return NULL; }
|
||||
self->blank_line_cpu = PyMem_Calloc(self->columns, sizeof(CPUCell));
|
||||
self->blank_line_gpu = PyMem_Calloc(self->columns, sizeof(GPUCell));
|
||||
if (!self->blank_line_cpu || !self->blank_line_gpu) { Py_CLEAR(self); return NULL; }
|
||||
self->blank_line.cpu_cells = self->blank_line_cpu;
|
||||
self->blank_line.gpu_cells = self->blank_line_gpu;
|
||||
self->blank_line.xnum = self->columns;
|
||||
self->blank_line.ynum = 0;
|
||||
self->blank_line.attrs.val = 0;
|
||||
self->blank_line.text_cache = self->text_cache;
|
||||
self->hyperlink_pool = alloc_hyperlink_pool();
|
||||
if (!self->hyperlink_pool) { Py_CLEAR(self); return PyErr_NoMemory(); }
|
||||
self->as_ansi_buf.hyperlink_pool = self->hyperlink_pool;
|
||||
@@ -550,6 +559,17 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
}
|
||||
// Resize overlay line
|
||||
if (!init_overlay_line(self, columns, true)) return false;
|
||||
PyMem_Free(self->blank_line_cpu);
|
||||
PyMem_Free(self->blank_line_gpu);
|
||||
self->blank_line_cpu = PyMem_Calloc(columns, sizeof(CPUCell));
|
||||
self->blank_line_gpu = PyMem_Calloc(columns, sizeof(GPUCell));
|
||||
if (!self->blank_line_cpu || !self->blank_line_gpu) return false;
|
||||
self->blank_line.cpu_cells = self->blank_line_cpu;
|
||||
self->blank_line.gpu_cells = self->blank_line_gpu;
|
||||
self->blank_line.xnum = columns;
|
||||
self->blank_line.ynum = 0;
|
||||
self->blank_line.attrs.val = 0;
|
||||
self->blank_line.text_cache = self->text_cache;
|
||||
|
||||
// Resize main linebuf
|
||||
RAII_PyObject(prompt_copy, NULL);
|
||||
@@ -1482,6 +1502,8 @@ cursor_within_margins(Screen *self) {
|
||||
return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom;
|
||||
}
|
||||
|
||||
static void reset_pixel_scroll(Screen *self);
|
||||
|
||||
// Remove all cell images from a portion of the screen and mark lines that
|
||||
// contain image placeholders as dirty to make sure they are redrawn. This is
|
||||
// needed when we perform commands that may move some lines without marking them
|
||||
@@ -1528,6 +1550,7 @@ void
|
||||
screen_toggle_screen_buffer(Screen *self, bool save_cursor, bool clear_alt_screen) {
|
||||
bool to_alt = self->linebuf == self->main_linebuf;
|
||||
self->active_hyperlink_id = 0;
|
||||
reset_pixel_scroll(self);
|
||||
if (to_alt) {
|
||||
if (clear_alt_screen) {
|
||||
linebuf_clear(self->alt_linebuf, BLANK_CHAR);
|
||||
@@ -2420,9 +2443,35 @@ dirty_scroll(Screen *self) {
|
||||
screen_pause_rendering(self, false, 0);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pixel_scroll_enabled(const Screen *self) {
|
||||
return OPT(pixel_scroll) && self->linebuf == self->main_linebuf && !self->paused_rendering.expires_at;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pixel_scroll_enabled_for_render(const Screen *self) {
|
||||
return OPT(pixel_scroll) && !self->paused_rendering.expires_at && self->linebuf == self->main_linebuf;
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
render_lines_for_screen(const Screen *self) {
|
||||
return self->lines + (pixel_scroll_enabled_for_render(self) ? 2u : 0u);
|
||||
}
|
||||
|
||||
static inline int
|
||||
render_row_offset_for_screen(const Screen *self) {
|
||||
return pixel_scroll_enabled_for_render(self) ? 1 : 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
reset_pixel_scroll(Screen *self) {
|
||||
self->pixel_scroll_offset_y = 0.0;
|
||||
}
|
||||
|
||||
static void
|
||||
screen_clear_scrollback(Screen *self) {
|
||||
historybuf_clear(self->historybuf);
|
||||
reset_pixel_scroll(self);
|
||||
if (self->scrolled_by != 0) {
|
||||
self->scrolled_by = 0;
|
||||
dirty_scroll(self);
|
||||
@@ -3120,7 +3169,10 @@ screen_history_scroll_to_prompt(Screen *self, int num_of_prompts_to_jump, int sc
|
||||
self->scrolled_by = y >= 0 ? 0 : -y;
|
||||
screen_set_last_visited_prompt(self, 0);
|
||||
}
|
||||
if (old != self->scrolled_by) dirty_scroll(self);
|
||||
if (old != self->scrolled_by) {
|
||||
reset_pixel_scroll(self);
|
||||
dirty_scroll(self);
|
||||
}
|
||||
return old != self->scrolled_by;
|
||||
}
|
||||
|
||||
@@ -3347,6 +3399,32 @@ update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
|
||||
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
|
||||
}
|
||||
|
||||
static Line*
|
||||
render_line_for_virtual_y(Screen *self, int y, Line *line, index_type *lnum, bool *is_history) {
|
||||
if (self->scrolled_by) {
|
||||
if (y < (int)self->scrolled_by) {
|
||||
int idx = (int)self->scrolled_by - 1 - y;
|
||||
if (idx >= 0 && (unsigned)idx < self->historybuf->count) {
|
||||
historybuf_init_line(self->historybuf, idx, line);
|
||||
line->xnum = self->columns;
|
||||
line->ynum = (index_type)MIN(MAX(y, 0), (int)self->lines - 1);
|
||||
if (lnum) *lnum = (index_type)idx;
|
||||
if (is_history) *is_history = true;
|
||||
return line;
|
||||
}
|
||||
return &self->blank_line;
|
||||
}
|
||||
y -= self->scrolled_by;
|
||||
}
|
||||
if (y >= 0 && y < (int)self->lines) {
|
||||
linebuf_init_line_at(self->linebuf, (index_type)y, line);
|
||||
if (lnum) *lnum = (index_type)y;
|
||||
if (is_history) *is_history = false;
|
||||
return line;
|
||||
}
|
||||
return &self->blank_line;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
screen_reset_dirty(Screen *self) {
|
||||
@@ -3466,24 +3544,22 @@ screen_render_line_graphics(Screen *self, Line *line, int32_t row) {
|
||||
static void
|
||||
screen_update_only_line_graphics_data(Screen *self) {
|
||||
unsigned int history_line_added_count = self->history_line_added_count;
|
||||
index_type lnum;
|
||||
index_type lnum = 0;
|
||||
if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
|
||||
screen_reset_dirty(self);
|
||||
self->scroll_changed = false;
|
||||
for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) {
|
||||
lnum = self->scrolled_by - 1 - y;
|
||||
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
|
||||
screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by);
|
||||
if (self->historybuf->line->attrs.has_dirty_text) {
|
||||
historybuf_mark_line_clean(self->historybuf, lnum);
|
||||
}
|
||||
}
|
||||
for (index_type y = self->scrolled_by; y < self->lines; y++) {
|
||||
lnum = y - self->scrolled_by;
|
||||
linebuf_init_line(self->linebuf, lnum);
|
||||
if (self->linebuf->line->attrs.has_dirty_text) {
|
||||
screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by);
|
||||
linebuf_mark_line_clean(self->linebuf, lnum);
|
||||
const unsigned int render_lines = render_lines_for_screen(self);
|
||||
const int render_row_offset = render_row_offset_for_screen(self);
|
||||
Line line = {.text_cache = self->text_cache};
|
||||
for (unsigned int render_row = 0; render_row < render_lines; render_row++) {
|
||||
const int virtual_y = (int)render_row - render_row_offset;
|
||||
bool is_history = false;
|
||||
Line *linep = render_line_for_virtual_y(self, virtual_y, &line, &lnum, &is_history);
|
||||
if (linep == &self->blank_line) continue;
|
||||
screen_render_line_graphics(self, linep, virtual_y - (int)self->scrolled_by);
|
||||
if (linep->attrs.has_dirty_text) {
|
||||
if (is_history) historybuf_mark_line_clean(self->historybuf, lnum);
|
||||
else linebuf_mark_line_clean(self->linebuf, lnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3512,34 +3588,41 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
|
||||
index_type lnum;
|
||||
screen_reset_dirty(self);
|
||||
update_overlay_position(self);
|
||||
const bool force_history_render = pixel_scroll_enabled(self) && self->scroll_changed;
|
||||
if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count);
|
||||
self->scroll_changed = false;
|
||||
for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) {
|
||||
lnum = self->scrolled_by - 1 - y;
|
||||
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
|
||||
// we render line graphics even if the line is not dirty as graphics commands received after
|
||||
// the unicode placeholder was first scanned can alter it.
|
||||
screen_render_line_graphics(self, self->historybuf->line, y - self->scrolled_by);
|
||||
if (self->historybuf->line->attrs.has_dirty_text) {
|
||||
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures, self->lc);
|
||||
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line, &self->as_ansi_buf);
|
||||
historybuf_mark_line_clean(self->historybuf, lnum);
|
||||
const unsigned int render_lines = render_lines_for_screen(self);
|
||||
const int render_row_offset = render_row_offset_for_screen(self);
|
||||
Line line = {.text_cache = self->text_cache};
|
||||
for (unsigned int render_row = 0; render_row < render_lines; render_row++) {
|
||||
const int virtual_y = (int)render_row - render_row_offset;
|
||||
bool is_history = false;
|
||||
Line *linep = render_line_for_virtual_y(self, virtual_y, &line, &lnum, &is_history);
|
||||
if (linep == &self->blank_line) {
|
||||
update_line_data(linep, render_row, address);
|
||||
continue;
|
||||
}
|
||||
update_line_data(self->historybuf->line, y, address);
|
||||
}
|
||||
for (index_type y = self->scrolled_by; y < self->lines; y++) {
|
||||
lnum = y - self->scrolled_by;
|
||||
linebuf_init_line(self->linebuf, lnum);
|
||||
if (self->linebuf->line->attrs.has_dirty_text ||
|
||||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor.y == lnum))) {
|
||||
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures, self->lc);
|
||||
screen_render_line_graphics(self, self->linebuf->line, y - self->scrolled_by);
|
||||
if (self->linebuf->line->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(
|
||||
self->marker, self->linebuf->line, &self->as_ansi_buf);
|
||||
if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, self->linebuf->line, fonts_data);
|
||||
linebuf_mark_line_clean(self->linebuf, lnum);
|
||||
if (is_history) {
|
||||
// we render line graphics even if the line is not dirty as graphics commands received after
|
||||
// the unicode placeholder was first scanned can alter it.
|
||||
screen_render_line_graphics(self, linep, virtual_y - (int)self->scrolled_by);
|
||||
if (force_history_render || linep->attrs.has_dirty_text) {
|
||||
render_line(fonts_data, linep, lnum, self->cursor, self->disable_ligatures, self->lc);
|
||||
if (screen_has_marker(self)) mark_text_in_line(self->marker, linep, &self->as_ansi_buf);
|
||||
historybuf_mark_line_clean(self->historybuf, lnum);
|
||||
}
|
||||
} else {
|
||||
if (linep->attrs.has_dirty_text ||
|
||||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered.cursor.y == lnum))) {
|
||||
render_line(fonts_data, linep, lnum, self->cursor, self->disable_ligatures, self->lc);
|
||||
screen_render_line_graphics(self, linep, virtual_y - (int)self->scrolled_by);
|
||||
if (linep->attrs.has_dirty_text && screen_has_marker(self)) mark_text_in_line(
|
||||
self->marker, linep, &self->as_ansi_buf);
|
||||
if (is_overlay_active && lnum == self->overlay_line.ynum) render_overlay_line(self, linep, fonts_data);
|
||||
linebuf_mark_line_clean(self->linebuf, lnum);
|
||||
}
|
||||
}
|
||||
update_line_data(self->linebuf->line, y, address);
|
||||
update_line_data(linep, render_row, address);
|
||||
}
|
||||
if (is_overlay_active && self->overlay_line.ynum + self->scrolled_by < self->lines) {
|
||||
if (self->overlay_line.is_dirty) {
|
||||
@@ -3696,7 +3779,7 @@ iteration_data_is_empty(const Screen *self, const IterationData *idata) {
|
||||
}
|
||||
|
||||
static void
|
||||
apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) {
|
||||
apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask, int render_row_offset) {
|
||||
iteration_data(s, &s->last_rendered, self->columns, -self->historybuf->count, self->scrolled_by);
|
||||
Line *line;
|
||||
const int y_min = MAX(0, s->last_rendered.y), y_limit = MIN(s->last_rendered.y_limit, (int)self->lines);
|
||||
@@ -3705,14 +3788,14 @@ apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) {
|
||||
linebuf_init_line(self->paused_rendering.linebuf, y);
|
||||
line = self->paused_rendering.linebuf->line;
|
||||
} else line = visual_line_(self, y);
|
||||
uint8_t *line_start = data + self->columns * y;
|
||||
uint8_t *line_start = data + self->columns * (y + render_row_offset);
|
||||
XRange xr = xrange_for_iteration_with_multicells(&s->last_rendered, y, line);
|
||||
for (index_type x = xr.x; x < xr.x_limit; x++) {
|
||||
line_start[x] |= set_mask;
|
||||
CPUCell *c = &line->cpu_cells[x];
|
||||
if (c->is_multicell && c->scale > 1) {
|
||||
for (int ym = MAX(0, y - c->y); ym < y; ym++) data[self->columns * ym + x] |= set_mask;
|
||||
for (int ym = y + 1; ym < MIN((int)self->lines, y + c->scale - c->y); ym++) data[self->columns * ym + x] |= set_mask;
|
||||
for (int ym = MAX(0, y - c->y); ym < y; ym++) data[self->columns * (ym + render_row_offset) + x] |= set_mask;
|
||||
for (int ym = y + 1; ym < MIN((int)self->lines, y + c->scale - c->y); ym++) data[self->columns * (ym + render_row_offset) + x] |= set_mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3735,20 +3818,24 @@ screen_has_selection(Screen *self) {
|
||||
void
|
||||
screen_apply_selection(Screen *self, void *address, size_t size) {
|
||||
memset(address, 0, size);
|
||||
const int render_row_offset = render_row_offset_for_screen(self);
|
||||
Selections *sel = self->paused_rendering.expires_at ? &self->paused_rendering.selections : &self->selections;
|
||||
for (size_t i = 0; i < sel->count; i++) apply_selection(self, address, sel->items + i, 1);
|
||||
for (size_t i = 0; i < sel->count; i++) apply_selection(self, address, sel->items + i, 1, render_row_offset);
|
||||
sel->last_rendered_count = sel->count;
|
||||
sel = self->paused_rendering.expires_at ? &self->paused_rendering.url_ranges : &self->url_ranges;
|
||||
for (size_t i = 0; i < sel->count; i++) {
|
||||
Selection *s = sel->items + i;
|
||||
if (OPT(underline_hyperlinks) == UNDERLINE_NEVER && s->is_hyperlink) continue;
|
||||
apply_selection(self, address, s, 2);
|
||||
apply_selection(self, address, s, 2, render_row_offset);
|
||||
}
|
||||
uint8_t *a = address;
|
||||
sel->last_rendered_count = sel->count;
|
||||
ExtraCursors *ec = self->paused_rendering.expires_at ? &self->paused_rendering.extra_cursors : &self->extra_cursors;
|
||||
const size_t render_offset_cells = (size_t)render_row_offset * self->columns;
|
||||
for (unsigned i = 0; i < ec->count; i++) {
|
||||
if (ec->locations[i].cell < size) a[ec->locations[i].cell] |= (ec->locations[i].shape & 7) << 2;
|
||||
if (ec->locations[i].cell + render_offset_cells < size) {
|
||||
a[ec->locations[i].cell + render_offset_cells] |= (ec->locations[i].shape & 7) << 2;
|
||||
}
|
||||
}
|
||||
ec->dirty = false;
|
||||
}
|
||||
@@ -4135,6 +4222,7 @@ screen_update_overlay_text(Screen *self, const char *utf8_text) {
|
||||
// Since we are typing, scroll to the bottom
|
||||
if (self->scrolled_by != 0) {
|
||||
self->scrolled_by = 0;
|
||||
reset_pixel_scroll(self);
|
||||
dirty_scroll(self);
|
||||
}
|
||||
}
|
||||
@@ -4250,7 +4338,8 @@ render_overlay_line(Screen *self, Line *line, FONTS_DATA_HANDLE fonts_data) {
|
||||
|
||||
static void
|
||||
update_overlay_line_data(Screen *self, uint8_t *data) {
|
||||
const size_t base = sizeof(GPUCell) * (self->overlay_line.ynum + self->scrolled_by) * self->columns;
|
||||
const int render_row_offset = render_row_offset_for_screen(self);
|
||||
const size_t base = sizeof(GPUCell) * (self->overlay_line.ynum + self->scrolled_by + render_row_offset) * self->columns;
|
||||
memcpy(data + base, self->overlay_line.gpu_cells, self->columns * sizeof(GPUCell));
|
||||
}
|
||||
|
||||
@@ -4943,10 +5032,38 @@ screen_history_scroll_to_absolute(Screen *self, unsigned int target_scrolled_by)
|
||||
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;
|
||||
reset_pixel_scroll(self);
|
||||
dirty_scroll(self);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
screen_apply_pixel_scroll(Screen *self, double delta_pixels) {
|
||||
if (!pixel_scroll_enabled(self)) return false;
|
||||
if (!self->historybuf->count) return false;
|
||||
const double cell_height = (double)self->cell_size.height;
|
||||
if (cell_height <= 0.0 || delta_pixels == 0.0) return false;
|
||||
|
||||
double total = self->pixel_scroll_offset_y + (double)self->scrolled_by * cell_height + delta_pixels;
|
||||
const double max_total = (double)self->historybuf->count * cell_height;
|
||||
if (total < 0.0) total = 0.0;
|
||||
if (total > max_total) total = max_total;
|
||||
const unsigned int new_scrolled_by = (unsigned int)floor(total / cell_height);
|
||||
const double offset = total - (double)new_scrolled_by * cell_height;
|
||||
bool changed = false;
|
||||
if (new_scrolled_by != self->scrolled_by) {
|
||||
self->scrolled_by = new_scrolled_by;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (offset != self->pixel_scroll_offset_y) {
|
||||
self->pixel_scroll_offset_y = offset;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) dirty_scroll(self);
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool
|
||||
screen_history_scroll(Screen *self, int amt, bool upwards) {
|
||||
switch(amt) {
|
||||
@@ -4971,6 +5088,7 @@ screen_history_scroll(Screen *self, int amt, bool upwards) {
|
||||
unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count);
|
||||
if (new_scroll != self->scrolled_by) {
|
||||
self->scrolled_by = new_scroll;
|
||||
reset_pixel_scroll(self);
|
||||
dirty_scroll(self);
|
||||
return true;
|
||||
}
|
||||
@@ -5613,6 +5731,7 @@ scroll_prompt_to_bottom(Screen *self, PyObject *args UNUSED) {
|
||||
// always scroll to the bottom
|
||||
if (self->scrolled_by != 0) {
|
||||
self->scrolled_by = 0;
|
||||
reset_pixel_scroll(self);
|
||||
dirty_scroll(self);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
|
||||
@@ -106,8 +106,12 @@ typedef struct {
|
||||
|
||||
unsigned int columns, lines, margin_top, margin_bottom, scrolled_by;
|
||||
double pending_scroll_pixels_x, pending_scroll_pixels_y;
|
||||
double pixel_scroll_offset_y;
|
||||
CellPixelSize cell_size;
|
||||
OverlayLine overlay_line;
|
||||
Line blank_line;
|
||||
CPUCell *blank_line_cpu;
|
||||
GPUCell *blank_line_gpu;
|
||||
id_type window_id;
|
||||
Selections selections, url_ranges;
|
||||
struct {
|
||||
@@ -279,6 +283,7 @@ typedef struct 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);
|
||||
bool screen_apply_pixel_scroll(Screen *self, double delta_pixels);
|
||||
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);
|
||||
|
||||
@@ -40,6 +40,28 @@ typedef struct UIRenderData {
|
||||
color_type background_color; // RGB only
|
||||
} UIRenderData;
|
||||
|
||||
static inline bool
|
||||
pixel_scroll_enabled_for_render(const Screen *screen) {
|
||||
return OPT(pixel_scroll) && !screen->paused_rendering.expires_at && screen->linebuf == screen->main_linebuf;
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
render_lines_for_screen(const Screen *screen) {
|
||||
return screen->lines + (pixel_scroll_enabled_for_render(screen) ? 2u : 0u);
|
||||
}
|
||||
|
||||
static inline float
|
||||
row_offset_for_screen(const Screen *screen) {
|
||||
if (!pixel_scroll_enabled_for_render(screen) || !screen->cell_size.height) return 0.f;
|
||||
return -1.f + (float)(screen->pixel_scroll_offset_y / (double)screen->cell_size.height);
|
||||
}
|
||||
|
||||
static inline float
|
||||
scroll_offset_lines_for_screen(const Screen *screen) {
|
||||
if (!pixel_scroll_enabled_for_render(screen) || !screen->cell_size.height) return 0.f;
|
||||
return (float)(screen->pixel_scroll_offset_y / (double)screen->cell_size.height);
|
||||
}
|
||||
|
||||
// Sprites {{{
|
||||
typedef struct {
|
||||
int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
|
||||
@@ -486,6 +508,10 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
|
||||
if (rd->cursor_opacity != 0 && cursor->is_visible) {
|
||||
rd->cursor_x1 = cursor->x, rd->cursor_y1 = cursor->y;
|
||||
rd->cursor_x2 = cursor->x, rd->cursor_y2 = cursor->y;
|
||||
if (pixel_scroll_enabled_for_render(screen)) {
|
||||
rd->cursor_y1 += 1;
|
||||
rd->cursor_y2 += 1;
|
||||
}
|
||||
CursorShape cs = (cursor->is_focused || OPT(cursor_shape_unfocused) == NO_CURSOR_SHAPE) ? cursor->shape : OPT(cursor_shape_unfocused);
|
||||
rd->cursor_shape = cs;
|
||||
color_type cell_fg = rd->default_fg, cell_bg = rd->bg_colors0;
|
||||
@@ -573,7 +599,8 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, FONTS_DATA_HANDLE fonts_
|
||||
bool screen_resized = screen->last_rendered.columns != screen->columns || screen->last_rendered.lines != screen->lines;
|
||||
|
||||
#define update_cell_data { \
|
||||
sz = sizeof(GPUCell) * screen->lines * screen->columns; \
|
||||
const unsigned int render_lines = render_lines_for_screen(screen); \
|
||||
sz = sizeof(GPUCell) * render_lines * screen->columns; \
|
||||
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, true); \
|
||||
screen_update_cell_data(screen, address, fonts_data, disable_ligatures && cursor_pos_changed); \
|
||||
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL; \
|
||||
@@ -585,7 +612,8 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, FONTS_DATA_HANDLE fonts_
|
||||
} else if (screen->reload_all_gpu_data || screen->scroll_changed || screen->is_dirty || screen_resized || (disable_ligatures && cursor_pos_changed)) update_cell_data;
|
||||
|
||||
#define update_selection_data { \
|
||||
sz = (size_t)screen->lines * screen->columns; \
|
||||
const unsigned int render_lines = render_lines_for_screen(screen); \
|
||||
sz = (size_t)render_lines * screen->columns; \
|
||||
address = alloc_and_map_vao_buffer(vao_idx, sz, selection_buffer, true); \
|
||||
screen_apply_selection(screen, address, sz); \
|
||||
unmap_vao_buffer(vao_idx, selection_buffer); address = NULL; \
|
||||
@@ -593,7 +621,7 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, FONTS_DATA_HANDLE fonts_
|
||||
}
|
||||
|
||||
#define update_graphics_data(grman) \
|
||||
grman_update_layers(grman, screen->scrolled_by, -1.f, 1.f, 2.f/screen->columns, 2.f/screen->lines, screen->columns, screen->lines, screen->cell_size)
|
||||
grman_update_layers(grman, screen->scrolled_by, scroll_offset_lines_for_screen(screen), -1.f, 1.f, 2.f/screen->columns, 2.f/screen->lines, screen->columns, screen->lines, screen->cell_size)
|
||||
|
||||
if (screen->paused_rendering.expires_at) {
|
||||
if (!screen->paused_rendering.cell_data_updated) {
|
||||
@@ -1069,8 +1097,9 @@ call_cell_program(int program, const UIRenderData *ui, ssize_t vao_idx, bool for
|
||||
CELL_BUFFERS;
|
||||
bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[program].render_data.index);
|
||||
glUniform1ui(cell_program_layouts[program].uniforms.draw_bg_bitfield, draw_bg_bitfield);
|
||||
glUniform1f(cell_program_layouts[program].uniforms.row_offset, row_offset_for_screen(ui->screen));
|
||||
if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
draw_quad(!for_final_output, ui->screen->lines * ui->screen->columns);
|
||||
draw_quad(!for_final_output, render_lines_for_screen(ui->screen) * ui->screen->columns);
|
||||
if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ typedef struct Options {
|
||||
} mouse_hide;
|
||||
double wheel_scroll_multiplier, touch_scroll_multiplier;
|
||||
int wheel_scroll_min_lines;
|
||||
bool pixel_scroll;
|
||||
bool enable_audio_bell;
|
||||
CursorShape cursor_shape, cursor_shape_unfocused;
|
||||
float cursor_beam_thickness;
|
||||
|
||||
Reference in New Issue
Block a user