From a9d31541c1b19351a5102227f1505d7c3ae27fd7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 6 Oct 2017 21:08:10 +0530 Subject: [PATCH] Implement clipped scrolling of images when in page area mode --- graphics-protocol.asciidoc | 11 ++++- kitty/graphics.c | 82 ++++++++++++++++++++++++++++---------- kitty/graphics.h | 8 +++- kitty/screen.c | 12 +++++- kitty_tests/graphics.py | 42 ++++++++++++++++++- 5 files changed, 129 insertions(+), 26 deletions(-) diff --git a/graphics-protocol.asciidoc b/graphics-protocol.asciidoc index 331dc1380..d3dd8e57d 100644 --- a/graphics-protocol.asciidoc +++ b/graphics-protocol.asciidoc @@ -268,12 +268,19 @@ take, and the default value they take when missing. All integers are 32-bit. |=== -== Interaction with screen changes +== Interaction with other terminal actions -When resetting the screen, all images that are visible on the screen must be +When resetting the terminal, all images that are visible on the screen must be cleared. When switching from the main screen to the alternate screen buffer (1049 private mode) all images in the alternate screen must be cleared, just as all text is cleared. The commands to clear the screen and erase text must have no effect on graphics. The dedicated delete graphics commands must be used for those. + +When scrolling the screen (such as when using index cursor movement commands, +or scrolling through the history buffer), images must be scrolled along with +text. When page margins are defined and the index commands are used, only +images that are entirely within the page area (between the margins) must be +scrolled. When scrolling them would cause them to extend outside the page area, +they must be clipped. diff --git a/kitty/graphics.c b/kitty/graphics.c index 94b675a3a..665835a50 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -510,6 +510,14 @@ create_add_response(GraphicsManager UNUSED *self, const GraphicsCommand *g, bool if (base->array == NULL) fatal("Out of memory while ensuring space in array"); \ } +static inline void +update_src_rect(ImageRef *ref, Image *img) { + // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image + ref->src_rect.left = (float)ref->src_x / (float)img->width; + ref->src_rect.right = (float)(ref->src_x + ref->src_width) / (float)img->width; + ref->src_rect.top = (float)ref->src_y / (float)img->height; + ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height; +} static void handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) { @@ -535,12 +543,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b ref->cell_x_offset = MIN(g->cell_x_offset, global_state.cell_width - 1); ref->cell_y_offset = MIN(g->cell_y_offset, global_state.cell_height - 1); ref->num_cols = g->num_cells; ref->num_rows = g->num_lines; - - // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image - ref->src_rect.left = (float)ref->src_x / (float)img->width; - ref->src_rect.right = (float)(ref->src_x + ref->src_width) / (float)img->width; - ref->src_rect.top = (float)ref->src_y / (float)img->height; - ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height; + update_src_rect(ref, img); // Move the cursor, the screen will take care of ensuring it is in bounds uint32_t num_cols = g->num_cells, num_rows = g->num_lines, t; @@ -625,7 +628,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree // Image lifetime/scrolling {{{ static inline void -filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRef*, const void*)) { +filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRef*, Image*, const void*)) { Image *img; ImageRef *ref; size_t i, j; @@ -634,7 +637,7 @@ filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe img = self->images + i; for (j = img->refcnt; j-- > 0;) { ref = img->refs + j; - if (filter_func(ref, data)) { + if (filter_func(ref, img, data)) { remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--); } } @@ -648,21 +651,60 @@ filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe } static inline bool -scroll_filter_func(ImageRef *ref, const void* data) { - int32_t *d = (int32_t*)data, amt = d[0], limit = d[1]; - ref->start_row += amt; - return ref->start_row + (int32_t)ref->effective_num_rows <= limit; -} - -void -grman_scroll_images(GraphicsManager *self, int32_t amt, int32_t limit) { - int32_t data[2]; - data[0] = amt; data[1] = limit; - filter_refs(self, data, scroll_filter_func); +scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) { + ScrollData *d = (ScrollData*)data; + ref->start_row += d->amt; + return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit; } static inline bool -clear_filter_func(ImageRef *ref, const void UNUSED *data) { +ref_within_region(ImageRef *ref, index_type margin_top, index_type margin_bottom) { + return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom; +} + +static inline bool +ref_outside_region(ImageRef *ref, index_type margin_top, index_type margin_bottom) { + return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom; +} + +static inline bool +scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) { + ScrollData *d = (ScrollData*)data; + if (ref_within_region(ref, d->margin_top, d->margin_bottom)) { + ref->start_row += d->amt; + if (ref_outside_region(ref, d->margin_top, d->margin_bottom)) return true; + // Clip the image if scrolling has resulted in part of it being outside the page area + uint32_t clip_amt, clipped_rows; + if (ref->start_row < (int32_t)d->margin_top) { + // image moved up + clipped_rows = d->margin_top - ref->start_row; + clip_amt = global_state.cell_height * clipped_rows; + if (ref->src_height <= clip_amt) return true; + ref->src_y += clip_amt; ref->src_height -= clip_amt; + ref->effective_num_rows -= clipped_rows; + update_src_rect(ref, img); + ref->start_row += clipped_rows; + } else if (ref->start_row + ref->effective_num_rows > d->margin_bottom) { + // image moved down + clipped_rows = ref->start_row + ref->effective_num_rows - d->margin_bottom; + clip_amt = global_state.cell_height * clipped_rows; + if (ref->src_height <= clip_amt) return true; + ref->src_height -= clip_amt; + ref->effective_num_rows -= clipped_rows; + update_src_rect(ref, img); + } + return ref_outside_region(ref, d->margin_top, d->margin_bottom); + } + return false; +} + +void +grman_scroll_images(GraphicsManager *self, const ScrollData *data) { + filter_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func); +} + +static inline bool +clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) { return ref->start_row + (int32_t)ref->effective_num_rows > 0; } diff --git a/kitty/graphics.h b/kitty/graphics.h index f64184b04..5717abc38 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -75,8 +75,14 @@ typedef struct { PyTypeObject GraphicsManager_Type; +typedef struct { + int32_t amt, limit; + index_type margin_top, margin_bottom; + bool has_margins; +} ScrollData; + GraphicsManager* grman_realloc(GraphicsManager *, index_type lines, index_type columns); void grman_clear(GraphicsManager*); const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty); 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); -void grman_scroll_images(GraphicsManager *self, int32_t amt, int32_t limit); +void grman_scroll_images(GraphicsManager *self, const ScrollData*); diff --git a/kitty/screen.c b/kitty/screen.c index f6a42e3ad..9e17f6aa1 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -653,9 +653,18 @@ screen_cursor_to_column(Screen *self, unsigned int column) { } } +#define INDEX_GRAPHICS(amtv) { \ + bool is_main = self->linebuf == self->main_linebuf; \ + static ScrollData s; \ + s.amt = amtv; s.limit = is_main ? -self->historybuf->ynum : 0; \ + s.has_margins = self->margin_top != 0 || self->margin_bottom != self->lines - 1; \ + s.margin_top = top; s.margin_bottom = bottom; \ + grman_scroll_images(self->grman, &s); \ +} + #define INDEX_UP \ linebuf_index(self->linebuf, top, bottom); \ - grman_scroll_images(self->grman, -1, self->linebuf == self->main_linebuf ? -self->historybuf->ynum : 0); \ + INDEX_GRAPHICS(-1) \ if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \ /* Only add to history when no page margins have been set */ \ linebuf_init_line(self->linebuf, bottom); \ @@ -688,6 +697,7 @@ screen_scroll(Screen *self, unsigned int count) { #define INDEX_DOWN \ linebuf_reverse_index(self->linebuf, top, bottom); \ linebuf_clear_line(self->linebuf, top); \ + INDEX_GRAPHICS(1) \ self->is_dirty = true; void diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index aa51cc6d7..e6802c108 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -88,6 +88,7 @@ def put_helpers(self, cw, ch): cmd = 'a=T,f=24,i=%d,s=%d,v=%d,%s' % (iid, w, h, put_cmd(**kw)) data = b'x' * w * h * 3 send_command(screen, cmd, data) + return iid def put_ref(screen, **kw): cmd = 'a=p,i=%d,%s' % (iid, put_cmd(**kw)) @@ -223,14 +224,51 @@ class TestGraphics(BaseTest): s.index() self.ae(s.grman.image_count, 0) + # Now test with margins + s.reset() + # Test images outside page area untouched + put_image(s, cw, ch) # a one cell image at (0, 0) + for i in range(s.lines - 1): + s.index() + put_image(s, cw, ch) # a one cell image at (0, bottom) + s.set_margins(2, 4) # 1-based indexing + self.ae(s.grman.image_count, 2) + for i in range(s.lines + s.historybuf.ynum): + s.index() + self.ae(s.grman.image_count, 2) + for i in range(s.lines): # ensure cursor is at top margin + s.reverse_index() + # Test clipped scrolling during index + put_image(s, cw, 2*ch, z=-1) # 1x2 cell image + self.ae(s.grman.image_count, 3) + self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0}) + s.index(), s.index() + l = layers(s) + self.ae(len(l), 3) + self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.5, 'right': 1.0, 'bottom': 1.0}) + s.index() + self.ae(s.grman.image_count, 2) + # Test clipped scrolling during reverse_index + for i in range(s.lines): + s.reverse_index() + put_image(s, cw, 2*ch, z=-1) # 1x2 cell image + self.ae(s.grman.image_count, 3) + self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 1.0}) + while s.cursor.y != 1: + s.reverse_index() + s.reverse_index() + self.ae(layers(s)[0]['src_rect'], {'left': 0.0, 'top': 0.0, 'right': 1.0, 'bottom': 0.5}) + s.reverse_index() + self.ae(s.grman.image_count, 2) + def test_gr_reset(self): cw, ch = 10, 20 s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch) - put_image(s, 10, 20) # a one cell image at (0, 0) + put_image(s, cw, ch) # a one cell image at (0, 0) self.ae(len(layers(s)), 1) s.reset() self.ae(s.grman.image_count, 0) - put_image(s, 10, 20) # a one cell image at (0, 0) + put_image(s, cw, ch) # a one cell image at (0, 0) self.ae(s.grman.image_count, 1) for i in range(s.lines): s.index()