Implement clipped scrolling of images when in page area mode

This commit is contained in:
Kovid Goyal 2017-10-06 21:08:10 +05:30
parent 480645877a
commit a9d31541c1
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 129 additions and 26 deletions

View File

@ -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.

View File

@ -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;
}

View File

@ -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*);

View File

@ -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

View File

@ -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()