Implement control code for deleting images
This commit is contained in:
parent
3f9f5ae076
commit
747f7b076d
@ -256,6 +256,40 @@ they are semi-transparent. You can specify z-index values using the `z` key.
|
||||
Negative z-index values mean that the images will be drawn under the text. This
|
||||
allows rendering of text on top of images.
|
||||
|
||||
== Deleting images
|
||||
|
||||
Images can be deleted by using the delete action `a=d`. If specified without any
|
||||
other keys, it will delete all images visible on screen. To delete specific images,
|
||||
use the `d` key as described in the table below. Note that each value of d has
|
||||
both a lowercase and an uppercase variant. The lowercase variant only deletes the
|
||||
images without necessarily freeing up the stored image data, so that the images can be
|
||||
re-displayed without needing to resend the data. The uppercase variants will delete
|
||||
the image data as well, provided that the image is not referenced elsewhere, such as in the
|
||||
scrollback buffer. The values of the `x` and `y` keys are the same as cursor positions (i.e.
|
||||
x=1, y=1 is the top left cell).
|
||||
|
||||
|===
|
||||
| Value of `d` | Meaning
|
||||
|
||||
| `a` or `A` | Delete all images visible on screen
|
||||
| `i` or `I` | Delete all images with the specified id, specified using the `i` key.
|
||||
| `c` or `C` | Delete all images that intersect with the current cursor position.
|
||||
| `p` or `P` | Delete all images that intersect a specific cell, the cell is specified using the `x` and `y` keys
|
||||
| `q` or `Q` | Delete all images that intersect a specific cell having a specific z-index. The cell and z-index is specified using the `x`, `y` and `z` keys.
|
||||
| `x` or `X` | Delete all images that intersect the specified column, specified using the `x` key.
|
||||
| `y` or `Y` | Delete all images that intersect the specified row, specified using the `y` key.
|
||||
| `z` or `Z` | Delete all images that have the specified z-index, specified using the `z` key.
|
||||
|
||||
Some examples:
|
||||
|
||||
```
|
||||
<ESC>_Ga=d<ESC>\ # delete all visible images
|
||||
<ESC>_Ga=d,i=10<ESC>\ # delete the image with id=10
|
||||
<ESC>_Ga=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
|
||||
<ESC>_Ga=P,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4)
|
||||
```
|
||||
|===
|
||||
|
||||
== Control data reference
|
||||
|
||||
The table below shows all the control data keys as well as what values they can
|
||||
@ -265,7 +299,7 @@ take, and the default value they take when missing. All integers are 32-bit.
|
||||
|===
|
||||
|Key | Value | Default | Description
|
||||
|
||||
| `a` | Single character. `(t, T, q, p)` | `t` | The overall action this graphics command is performing.
|
||||
| `a` | Single character. `(t, T, q, p, d)` | `t` | The overall action this graphics command is performing.
|
||||
|
||||
4+^.^h| Keys for image transmission
|
||||
|
||||
@ -290,6 +324,11 @@ take, and the default value they take when missing. All integers are 32-bit.
|
||||
| `c` | Positive integer | `0` | The number of columns to display the image over
|
||||
| `r` | Positive integer | `0` | The number of rows to display the image over
|
||||
| `z` | Integer | `0` | The *z-index* vertical stacking order of the image
|
||||
|
||||
4+^.^h| Keys for deleting images
|
||||
|
||||
| `d` | Single character. `(a, A, c, C, p, P, q, Q, x, X, y, Y, z, Z)`. | `a` | What to delete.
|
||||
|
||||
|===
|
||||
|
||||
|
||||
@ -298,7 +337,7 @@ take, and the default value they take when missing. All integers are 32-bit.
|
||||
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 clear command (usually `<ESC>[H<ESC>[2J`) should also
|
||||
all text is cleared. The clear screen escape code (usually `<ESC>[2J`) should also
|
||||
clear all images. This is so that the clear command works.
|
||||
|
||||
The other commands to erase text must have no effect on graphics.
|
||||
|
||||
@ -561,6 +561,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
|
||||
}
|
||||
c->x += num_cols; c->y += num_rows - 1;
|
||||
ref->effective_num_rows = num_rows;
|
||||
ref->effective_num_cols = num_cols;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -630,7 +631,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*, Image*, const void*)) {
|
||||
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*)) {
|
||||
Image *img; ImageRef *ref;
|
||||
size_t i, j;
|
||||
|
||||
@ -643,7 +644,7 @@ filter_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe
|
||||
remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--);
|
||||
}
|
||||
}
|
||||
if (img->refcnt == 0) {
|
||||
if (img->refcnt == 0 && (free_images || img->client_id == 0)) {
|
||||
free_image(img);
|
||||
remove_from_array(self->images, sizeof(Image), i, self->image_count--);
|
||||
}
|
||||
@ -702,7 +703,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) {
|
||||
|
||||
void
|
||||
grman_scroll_images(GraphicsManager *self, const ScrollData *data) {
|
||||
filter_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
|
||||
filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@ -712,7 +713,71 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) {
|
||||
|
||||
void
|
||||
grman_clear(GraphicsManager *self) {
|
||||
filter_refs(self, NULL, clear_filter_func);
|
||||
filter_refs(self, NULL, true, clear_filter_func);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
id_filter_func(ImageRef UNUSED *ref, Image *img, const void *data) {
|
||||
uint32_t iid = *(uint32_t*)data;
|
||||
return img->client_id == iid;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
x_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
|
||||
const GraphicsCommand *g = data;
|
||||
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
y_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
|
||||
const GraphicsCommand *g = data;
|
||||
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
z_filter_func(ImageRef *ref, Image UNUSED *img, const void *data) {
|
||||
const GraphicsCommand *g = data;
|
||||
return ref->z_index == g->z_index;
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
point_filter_func(ImageRef *ref, Image *img, const void *data) {
|
||||
return x_filter_func(ref, img, data) && y_filter_func(ref, img, data);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
point3d_filter_func(ImageRef *ref, Image *img, const void *data) {
|
||||
return z_filter_func(ref, img, data) && point_filter_func(ref, img, data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty) {
|
||||
static GraphicsCommand d;
|
||||
switch (g->delete_action) {
|
||||
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func); *is_dirty = true; break
|
||||
#define D(l, u, data, func) case l: case u: I(u, data, func)
|
||||
#define G(l, u, func) D(l, u, g, func)
|
||||
case 0:
|
||||
D('a', 'A', NULL, clear_filter_func);
|
||||
D('i', 'I', &g->id, id_filter_func);
|
||||
G('p', 'P', point_filter_func);
|
||||
G('q', 'Q', point3d_filter_func);
|
||||
G('x', 'X', x_filter_func);
|
||||
G('y', 'Y', y_filter_func);
|
||||
G('z', 'Z', z_filter_func);
|
||||
case 'c':
|
||||
case 'C':
|
||||
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
|
||||
I('C', &d, point_filter_func);
|
||||
default:
|
||||
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
|
||||
break;
|
||||
#undef G
|
||||
#undef D
|
||||
#undef I
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
@ -737,12 +802,15 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
break;
|
||||
case 'p':
|
||||
if (!g->id) {
|
||||
REPORT_ERROR("%s", "Put graphics command without image id");
|
||||
REPORT_ERROR("Put graphics command without image id");
|
||||
break;
|
||||
}
|
||||
handle_put_command(self, g, c, is_dirty, NULL);
|
||||
ret = create_add_response(self, true, g->id);
|
||||
break;
|
||||
case 'd':
|
||||
handle_delete_command(self, g, c, is_dirty);
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown graphics command action: %c", g->action);
|
||||
break;
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include "data-types.h"
|
||||
|
||||
typedef struct {
|
||||
unsigned char action, transmission_type, compressed;
|
||||
unsigned char action, transmission_type, compressed, delete_action;
|
||||
uint32_t format, more, id, data_sz, data_offset;
|
||||
uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset;
|
||||
int32_t z_index;
|
||||
@ -34,7 +34,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
uint32_t src_width, src_height, src_x, src_y;
|
||||
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows;
|
||||
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols;
|
||||
int32_t z_index;
|
||||
int32_t start_row, start_column;
|
||||
ImageRect src_rect;
|
||||
|
||||
@ -540,6 +540,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
enum GR_STATES state = KEY, value_state = FLAG;
|
||||
enum KEYS {
|
||||
action='a',
|
||||
delete_action='d',
|
||||
transmission_type='t',
|
||||
compressed='o',
|
||||
format = 'f',
|
||||
@ -577,7 +578,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
switch(key) {
|
||||
#define KS(n, vs) case n: state = EQUAL; value_state = vs; break
|
||||
#define U(x) KS(x, UINT)
|
||||
KS(action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT);
|
||||
KS(action, FLAG); KS(delete_action, FLAG); KS(transmission_type, FLAG); KS(compressed, FLAG); KS(z_index, INT);
|
||||
U(format); U(more); U(id); U(data_sz); U(data_offset); U(width); U(height); U(x_offset); U(y_offset); U(data_height); U(data_width); U(num_cells); U(num_lines); U(cell_x_offset); U(cell_y_offset);
|
||||
#undef U
|
||||
#undef KS
|
||||
@ -598,7 +599,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
case FLAG:
|
||||
switch(key) {
|
||||
#define F(a) case a: g.a = screen->parser_buf[pos++] & 0xff; break
|
||||
F(action); F(transmission_type); F(compressed);
|
||||
F(action); F(delete_action); F(transmission_type); F(compressed);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -675,8 +676,8 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
|
||||
#define A(x) #x, g.x
|
||||
#define U(x) #x, (unsigned int)(g.x)
|
||||
#define I(x) #x, (int)(g.x)
|
||||
REPORT_VA_COMMAND("s {sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command",
|
||||
A(action), A(transmission_type), A(compressed),
|
||||
REPORT_VA_COMMAND("s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si} y#", "graphics_command",
|
||||
A(action), A(delete_action), A(transmission_type), A(compressed),
|
||||
U(format), U(more), U(id), U(data_sz), U(data_offset),
|
||||
U(width), U(height), U(x_offset), U(y_offset), U(data_height), U(data_width), U(num_cells), U(num_lines), U(cell_x_offset), U(cell_y_offset),
|
||||
U(payload_sz), I(z_index),
|
||||
|
||||
@ -292,3 +292,37 @@ class TestGraphics(BaseTest):
|
||||
s.index()
|
||||
s.reset()
|
||||
self.ae(s.grman.image_count, 1)
|
||||
|
||||
def test_gr_delete(self):
|
||||
cw, ch = 10, 20
|
||||
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
|
||||
|
||||
def delete(ac=None, **kw):
|
||||
cmd = 'a=d'
|
||||
if ac:
|
||||
cmd += ',d={}'.format(ac)
|
||||
if kw:
|
||||
cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items())
|
||||
send_command(s, cmd)
|
||||
|
||||
put_image(s, cw, ch)
|
||||
delete()
|
||||
self.ae(len(layers(s)), 0), self.ae(s.grman.image_count, 1)
|
||||
delete('A')
|
||||
self.ae(s.grman.image_count, 0)
|
||||
iid = put_image(s, cw, ch)[0]
|
||||
delete('I', i=iid)
|
||||
self.ae(s.grman.image_count, 0)
|
||||
s.reset()
|
||||
put_image(s, cw, ch)
|
||||
put_image(s, cw, ch)
|
||||
delete('C')
|
||||
self.ae(s.grman.image_count, 2)
|
||||
s.cursor_position(1, 1)
|
||||
delete('C')
|
||||
self.ae(s.grman.image_count, 1)
|
||||
delete('P', x=2, y=1)
|
||||
self.ae(s.grman.image_count, 0)
|
||||
put_image(s, cw, ch, z=9)
|
||||
delete('Z', z=9)
|
||||
self.ae(s.grman.image_count, 0)
|
||||
|
||||
@ -202,7 +202,7 @@ class TestParser(BaseTest):
|
||||
for p, v in tuple(k.items()):
|
||||
if isinstance(v, str) and p != 'payload':
|
||||
k[p] = v.encode('ascii')
|
||||
for f in 'action transmission_type compressed'.split():
|
||||
for f in 'action delete_action transmission_type compressed'.split():
|
||||
k.setdefault(f, b'\0')
|
||||
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width'
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index').split():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user