Graphics protocol: Support for positioning images relative to other images
Fixes #6617
This commit is contained in:
parent
aee14a49f0
commit
52d5a4679f
8 changed files with 398 additions and 43 deletions
|
|
@ -52,6 +52,8 @@ Detailed list of changes
|
|||
|
||||
- A new :doc:`escape code <pointer-shapes>` that can be used by programs running in the terminal to change the shape of the mouse pointer (:iss:`6711`)
|
||||
|
||||
- Graphics protocol: Support for positioning :ref:`images relative to other images <relative_image_placement>` (:iss:`6400`)
|
||||
|
||||
- A new option :opt:`single_window_padding_width` to use a different padding when only a single window is visible (:iss:`6734`)
|
||||
|
||||
- A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`)
|
||||
|
|
|
|||
|
|
@ -640,6 +640,58 @@ placements from the protocol perspective. They cannot be manipulated using
|
|||
graphics commands, instead they should be moved, deleted, or modified by
|
||||
manipulating the underlying Unicode placeholder as normal text.
|
||||
|
||||
.. _relative_image_placement:
|
||||
|
||||
Relative placements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 0.31.0
|
||||
Support for positioning images relative to other images
|
||||
|
||||
You can specify that a placement is positioned relative to another placement.
|
||||
This is particularly useful in combination with
|
||||
:ref:`graphics_unicode_placeholders` above. It can be used to specify a single
|
||||
transparent pixel image using a Unicode placeholder, which moves around
|
||||
naturally with the text, the real image(s) can base their position relative to
|
||||
the placeholder.
|
||||
|
||||
To specify that a placement should be relative to another, use the
|
||||
``P=<image_id>,Q=<placement_id>`` keys, when creating the relative placement.
|
||||
For example::
|
||||
|
||||
<ESC>_Ga=p,i=<image_id>,p=<placement_id>,P=<parent_img_id>,Q=<parent_placement_id><ESC>\
|
||||
|
||||
This will create a *relative placement* that refers to the *parent placement* specified
|
||||
by the ``P`` and ``Q`` keys. When the parent placement moves, the relative
|
||||
placement moves along with it. The relative placement can be offset from the
|
||||
parent's location by a specified number of cells, using the ``H`` and ``V``
|
||||
keys for horizontal and vertical displacement. Positive values move right and
|
||||
down. Negative values move left and up.
|
||||
|
||||
The lifetime of a relative placement is tied to the lifetime of its parent. If
|
||||
its parent is deleted, it is deleted as well. If the image that the relative
|
||||
placement is a placement of, has no more placements, the image is deleted as
|
||||
well. Thus, a parent and its relative placements form a *group* that is managed
|
||||
together.
|
||||
|
||||
A relative placement can refer to another relative placement as its parent.
|
||||
Thus the relative placements can form a chain. It is implementation dependent
|
||||
how long a chain of such placements is allowed, but implementation must allow
|
||||
a chain of length at least 8. If the implementation max depth is exceeded, the
|
||||
terminal must respond with the ``ETOODEEP`` error code.
|
||||
|
||||
Virtual placements created for Unicode placeholder based images cannot also be
|
||||
relative placements. However, a relative placement can refer to a virtual
|
||||
placement as its parent. If a client attempts to make a virtual placement
|
||||
relative the terminal must respond with the ``EINVAL`` error code.
|
||||
|
||||
Terminals are required to reject the creation of a relative placement
|
||||
that would create a cycle, such as when A is relative to B and B is relative to
|
||||
C and C is relative to A. In such cases, the terminal must respond with the
|
||||
``ECYCLE`` error code.
|
||||
|
||||
If a client attempts to create a reference to a placement that does not exist
|
||||
the terminal must respond with the ``ENOPARENT`` error code.
|
||||
|
||||
Deleting images
|
||||
---------------------
|
||||
|
|
@ -962,6 +1014,10 @@ Key Value Default Description
|
|||
``U`` Positive integer ``0`` Set to ``1`` to create a virtual placement for a Unicode placeholder.
|
||||
``1`` is to not move the cursor at all when placing the image.
|
||||
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
||||
``P`` Positive integer ``0`` The id of a parent image for relative placement
|
||||
``Q`` Positive integer ``0`` The id of a placement in the parent image for relative placement
|
||||
``H`` 32-bit integer ``0`` The offset in cells in the horizontal direction for relative placement
|
||||
``V`` 32-bit integer ``0`` The offset in cells in the vertical direction for relative placement
|
||||
|
||||
**Keys for animation frame loading**
|
||||
-----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -282,6 +282,10 @@ def graphics_parser() -> None:
|
|||
'z': ('z_index', 'int'),
|
||||
'C': ('cursor_movement', 'uint'),
|
||||
'U': ('unicode_placement', 'uint'),
|
||||
'P': ('parent_id', 'uint'),
|
||||
'Q': ('parent_placement_id', 'uint'),
|
||||
'H': ('offset_from_parent_x', 'int'),
|
||||
'V': ('offset_from_parent_y', 'int'),
|
||||
}
|
||||
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
||||
write_header(text, 'kitty/parse-graphics-command.h')
|
||||
|
|
|
|||
216
kitty/graphics.c
216
kitty/graphics.c
|
|
@ -65,6 +65,7 @@ next_id(id_type *counter) {
|
|||
if (UNLIKELY(ans == 0)) ans = ++(*counter);
|
||||
return ans;
|
||||
}
|
||||
static const unsigned PARENT_DEPTH_LIMIT = 8;
|
||||
|
||||
GraphicsManager*
|
||||
grman_alloc(void) {
|
||||
|
|
@ -140,14 +141,14 @@ dealloc(GraphicsManager* self) {
|
|||
}
|
||||
|
||||
static Image*
|
||||
img_by_internal_id(GraphicsManager *self, id_type id) {
|
||||
img_by_internal_id(const GraphicsManager *self, id_type id) {
|
||||
Image *ans;
|
||||
HASH_FIND(hh, self->images, &id, sizeof(id), ans);
|
||||
return ans;
|
||||
}
|
||||
|
||||
static Image*
|
||||
img_by_client_id(GraphicsManager *self, uint32_t id) {
|
||||
img_by_client_id(const GraphicsManager *self, uint32_t id) {
|
||||
for (Image *img = self->images; img != NULL; img = img->hh.next) {
|
||||
if (img->client_id == id) return img;
|
||||
}
|
||||
|
|
@ -155,7 +156,7 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
|
|||
}
|
||||
|
||||
static Image*
|
||||
img_by_client_number(GraphicsManager *self, uint32_t number) {
|
||||
img_by_client_number(const GraphicsManager *self, uint32_t number) {
|
||||
// get the newest image with the specified number
|
||||
Image *ans = NULL;
|
||||
for (Image *img = self->images; img != NULL; img = img->hh.next) {
|
||||
|
|
@ -164,6 +165,24 @@ img_by_client_number(GraphicsManager *self, uint32_t number) {
|
|||
return ans;
|
||||
}
|
||||
|
||||
static ImageRef*
|
||||
ref_by_internal_id(const Image *img, id_type id) {
|
||||
ImageRef *ans;
|
||||
HASH_FIND(hh, img->refs, &id, sizeof(id), ans);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static ImageRef*
|
||||
ref_by_client_id(const Image *img, uint32_t id) {
|
||||
for (ImageRef *ref = img->refs; ref != NULL; ref = ref->hh.next) {
|
||||
if (ref->client_id == id) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_image(GraphicsManager *self, Image *img) {
|
||||
free_image(self, img);
|
||||
|
|
@ -709,6 +728,9 @@ create_ref(Image *img, ImageRef *clone_from) {
|
|||
return ans;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_cell_image(const ImageRef *self) { return self->virtual_ref_id != 0; }
|
||||
|
||||
// Create a real image ref for a virtual image ref (placement) positioned in the
|
||||
// given cells. This is used for images positioned using Unicode placeholders.
|
||||
//
|
||||
|
|
@ -761,7 +783,7 @@ Image *grman_put_cell_image(GraphicsManager *self, uint32_t screen_row,
|
|||
// Create the ref structure on stack first. We will not create a real
|
||||
// reference if the image is completely out of bounds.
|
||||
ImageRef ref = {0};
|
||||
ref.is_cell_image = true;
|
||||
ref.virtual_ref_id = virt_img_ref->internal_id;
|
||||
|
||||
uint32_t img_rows = virt_img_ref->num_rows;
|
||||
uint32_t img_columns = virt_img_ref->num_cols;
|
||||
|
|
@ -877,26 +899,93 @@ Image *grman_put_cell_image(GraphicsManager *self, uint32_t screen_row,
|
|||
return img;
|
||||
}
|
||||
|
||||
static void remove_ref(Image *img, ImageRef *ref);
|
||||
|
||||
static bool
|
||||
has_good_ancestry(GraphicsManager *self, ImageRef *ref) {
|
||||
ImageRef *r = ref;
|
||||
unsigned depth = 0;
|
||||
while (r->parent.img) {
|
||||
if (r == ref && depth) {
|
||||
set_command_failed_response("ECYCLE", "This parent reference creates a cycle");
|
||||
return false;
|
||||
}
|
||||
if (depth++ >= PARENT_DEPTH_LIMIT) {
|
||||
set_command_failed_response("ETOODEEP", "Too many levels of parent references");
|
||||
return false;
|
||||
}
|
||||
Image *parent = img_by_internal_id(self, r->parent.img);
|
||||
if (!parent) {
|
||||
set_command_failed_response("ENOENT", "One of the ancestors of this ref with image id: %u not found", r->parent.img);
|
||||
return false;
|
||||
}
|
||||
ImageRef *parent_ref = ref_by_internal_id(parent, r->parent.ref);
|
||||
if (!parent_ref) {
|
||||
set_command_failed_response("ENOENT", "One of the ancestors of this ref with image id: %u and ref id: %u not found", r->parent.img, r->parent.ref);
|
||||
return false;
|
||||
}
|
||||
r = parent_ref;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
|
||||
if (g->unicode_placement && g->parent_id) {
|
||||
set_command_failed_response("EINVAL", "Put command creating a virtual placement cannot refer to a parent"); return g->id;
|
||||
}
|
||||
if (img == NULL) {
|
||||
if (g->id) img = img_by_client_id(self, g->id);
|
||||
else if (g->image_number) img = img_by_client_number(self, g->image_number);
|
||||
if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return g->id; }
|
||||
}
|
||||
if (!img->root_frame_data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return img->client_id; }
|
||||
*is_dirty = true;
|
||||
self->layers_dirty = true;
|
||||
id_type parent_id = 0, parent_placement_id = 0;
|
||||
if (g->parent_id) {
|
||||
Image *parent = img_by_client_id(self, g->parent_id);
|
||||
if (!parent) {
|
||||
set_command_failed_response("ENOPARENT", "Put command refers to a parent image with id: %u that does not exist", g->parent_id);
|
||||
return g->id;
|
||||
}
|
||||
if (!parent->refs) {
|
||||
set_command_failed_response("ENOPARENT", "Put command refers to a parent image with id: %u that has no placements", g->parent_id);
|
||||
return g->id;
|
||||
}
|
||||
ImageRef *parent_ref = parent->refs;
|
||||
if (g->parent_placement_id) {
|
||||
parent_ref = ref_by_client_id(parent, g->parent_placement_id);
|
||||
if (!parent_ref) {
|
||||
set_command_failed_response("ENOPARENT", "Put command refers to a parent image placement with id: %u and placement id: %u that does not exist", g->parent_id, g->parent_placement_id);
|
||||
return g->id;
|
||||
}
|
||||
}
|
||||
parent_id = parent->internal_id;
|
||||
parent_placement_id = parent_ref->internal_id;
|
||||
}
|
||||
ImageRef *ref = NULL;
|
||||
if (g->placement_id && img->client_id) {
|
||||
for (ImageRef *r = img->refs; r != NULL; r = r->hh.next) {
|
||||
if (r->client_id == g->placement_id) {
|
||||
ref = r;
|
||||
if (parent_id && parent_id == img->internal_id && parent_placement_id && parent_placement_id == r->internal_id) {
|
||||
set_command_failed_response("EINVAL", "Put command refers to itself as its own parent");
|
||||
return g->id;
|
||||
}
|
||||
if (parent_id && parent_placement_id) {
|
||||
id_type rp = ref->parent.img, rpp = ref->parent.ref;
|
||||
ref->parent.img = parent_id; ref->parent.ref = parent_placement_id;
|
||||
bool ok = has_good_ancestry(self, ref);
|
||||
ref->parent.img = rp; ref->parent.ref = rpp;
|
||||
if (!ok) return g->id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ref == NULL) ref = create_ref(img, NULL);
|
||||
|
||||
*is_dirty = true;
|
||||
self->layers_dirty = true;
|
||||
img->atime = monotonic();
|
||||
ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height;
|
||||
ref->src_width = MIN(ref->src_width, img->width - ((float)img->width > ref->src_x ? ref->src_x : (float)img->width));
|
||||
|
|
@ -909,13 +998,25 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
|
|||
if (img->client_id) ref->client_id = g->placement_id;
|
||||
update_src_rect(ref, img);
|
||||
update_dest_rect(ref, g->num_cells, g->num_lines, cell);
|
||||
ref->parent.img = parent_id;
|
||||
ref->parent.ref = parent_placement_id;
|
||||
ref->parent.offset.x = g->offset_from_parent_x;
|
||||
ref->parent.offset.y = g->offset_from_parent_y;
|
||||
ref->is_virtual_ref = false;
|
||||
if (g->unicode_placement) {
|
||||
ref->is_virtual_ref = true;
|
||||
ref->start_row = ref->start_column = 0;
|
||||
}
|
||||
// Move the cursor, the screen will take care of ensuring it is in bounds
|
||||
if (g->cursor_movement != 1 && !g->unicode_placement) {
|
||||
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
||||
if (ref->parent.img) {
|
||||
if (!has_good_ancestry(self, ref)) {
|
||||
remove_ref(img, ref);
|
||||
return g->id;
|
||||
}
|
||||
} else {
|
||||
// Move the cursor, the screen will take care of ensuring it is in bounds
|
||||
if (g->cursor_movement != 1 && !g->unicode_placement) {
|
||||
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
||||
}
|
||||
}
|
||||
return img->client_id;
|
||||
}
|
||||
|
|
@ -940,6 +1041,48 @@ gpu_data_for_image(ImageRenderData *ans, float left, float top, float right, flo
|
|||
ans->group_count = 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
resolve_cell_ref(const Image *img, id_type virt_ref_id, int32_t *start_row, int32_t *start_column) {
|
||||
*start_row = 0; *start_column = 0;
|
||||
bool found = false;
|
||||
for (ImageRef *ref = img->refs; ref != NULL; ref = ref->hh.next) {
|
||||
if (ref->virtual_ref_id == virt_ref_id) {
|
||||
if (!found || ref->start_row < *start_row) *start_row = ref->start_row;
|
||||
if (!found || ref->start_column < *start_column) *start_column = ref->start_column;
|
||||
found = true;
|
||||
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
resolve_parent_offset(const GraphicsManager *self, const ImageRef *ref, int32_t *start_row, int32_t *start_column, bool *is_virtual_ref) {
|
||||
*start_row = 0; *start_column = 0;
|
||||
int32_t x = 0, y = 0;
|
||||
unsigned depth = 0;
|
||||
ImageRef cell_ref = {0};
|
||||
while (ref->parent.img) {
|
||||
if (depth++ >= PARENT_DEPTH_LIMIT) return false; // either a cycle or too many ancestors
|
||||
Image *img = img_by_internal_id(self, ref->parent.img);
|
||||
if (!img) return false;
|
||||
ImageRef *parent = ref_by_internal_id(img, ref->parent.ref);
|
||||
if (!parent) return false;
|
||||
if (parent->is_virtual_ref) {
|
||||
*is_virtual_ref = true;
|
||||
if (!resolve_cell_ref(img, parent->internal_id, &cell_ref.start_row, &cell_ref.start_column)) return false;
|
||||
parent = &cell_ref;
|
||||
}
|
||||
x += ref->parent.offset.x;
|
||||
y += ref->parent.offset.y;
|
||||
ref = parent;
|
||||
}
|
||||
*start_row = ref->start_row + y;
|
||||
*start_column = ref->start_column + x;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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) self->layers_dirty = true;
|
||||
|
|
@ -950,7 +1093,7 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
|||
self->num_of_below_refs = 0;
|
||||
self->num_of_negative_refs = 0;
|
||||
self->num_of_positive_refs = 0;
|
||||
Image *img; ImageRef *ref;
|
||||
Image *img, *tmpimg; ImageRef *ref, *tmpref;
|
||||
ImageRect r;
|
||||
float screen_width = dx * num_cols, screen_height = dy * num_rows;
|
||||
float screen_bottom = screen_top - screen_height;
|
||||
|
|
@ -960,19 +1103,32 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
|||
|
||||
// Iterate over all visible refs and create render data
|
||||
self->render_data.count = 0;
|
||||
for (img = self->images; img != NULL; img = img->hh.next) {
|
||||
|
||||
HASH_ITER(hh, self->images, img, tmpimg) {
|
||||
bool was_drawn = img->is_drawn;
|
||||
bool ref_removed = false;
|
||||
img->is_drawn = false;
|
||||
|
||||
for (ref = img->refs; ref != NULL; ref = ref->hh.next) {
|
||||
HASH_ITER(hh, img->refs, ref, tmpref) {
|
||||
if (ref->is_virtual_ref) continue;
|
||||
r.top = y0 - ref->start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height;
|
||||
if (ref->num_rows > 0) r.bottom = y0 - (ref->start_row + (int32_t)ref->num_rows) * dy;
|
||||
int32_t start_row = ref->start_row, start_column = ref->start_column;
|
||||
if (ref->parent.img) {
|
||||
bool is_virtual_ref;
|
||||
if (!resolve_parent_offset(self, ref, &start_row, &start_column, &is_virtual_ref)) {
|
||||
if (!is_virtual_ref) {
|
||||
remove_ref(img, ref);
|
||||
ref_removed = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
r.top = y0 - start_row * dy - dy * (float)ref->cell_y_offset / (float)cell.height;
|
||||
if (ref->num_rows > 0) r.bottom = y0 - (start_row + (int32_t)ref->num_rows) * dy;
|
||||
else r.bottom = r.top - screen_height * (float)ref->src_height / screen_height_px;
|
||||
if (r.top <= screen_bottom || r.bottom >= screen_top) continue; // not visible
|
||||
|
||||
r.left = screen_left + ref->start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width;
|
||||
if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx;
|
||||
r.left = screen_left + start_column * dx + dx * (float)ref->cell_x_offset / (float) cell.width;
|
||||
if (ref->num_cols > 0) r.right = screen_left + (start_column + (int32_t)ref->num_cols) * dx;
|
||||
else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px;
|
||||
|
||||
if (ref->z_index < ((int32_t)INT32_MIN/2))
|
||||
|
|
@ -986,10 +1142,14 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
|||
zero_at_ptr(rd);
|
||||
rd->dest_rect = r; rd->src_rect = ref->src_rect;
|
||||
self->render_data.count++;
|
||||
rd->z_index = ref->z_index; rd->image_id = img->internal_id;
|
||||
rd->z_index = ref->z_index; rd->image_id = img->internal_id; rd->ref_id = ref->internal_id;
|
||||
rd->texture_id = img->texture_id;
|
||||
img->is_drawn = true;
|
||||
}
|
||||
if (ref_removed && !img->refs) {
|
||||
remove_image(self, img);
|
||||
continue;
|
||||
}
|
||||
if (img->is_drawn && !was_drawn && img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->animation_duration) {
|
||||
self->has_images_needing_animation = true;
|
||||
global_state.check_for_active_animated_images = true;
|
||||
|
|
@ -1691,7 +1851,7 @@ grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize
|
|||
|
||||
static bool
|
||||
cell_image_row_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
if (ref->is_virtual_ref || !ref->is_cell_image)
|
||||
if (ref->is_virtual_ref || !is_cell_image(ref))
|
||||
return false;
|
||||
int32_t top = *(int32_t *)data;
|
||||
int32_t bottom = *((int32_t *)data + 1);
|
||||
|
|
@ -1700,7 +1860,7 @@ cell_image_row_filter_func(const ImageRef *ref, Image UNUSED *img, const void *d
|
|||
|
||||
static bool
|
||||
cell_image_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data UNUSED, CellPixelSize cell UNUSED) {
|
||||
return !ref->is_virtual_ref && ref->is_cell_image;
|
||||
return !ref->is_virtual_ref && is_cell_image(ref);
|
||||
}
|
||||
|
||||
// Remove cell images within the given region.
|
||||
|
|
@ -1726,7 +1886,7 @@ clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *dat
|
|||
|
||||
static bool
|
||||
clear_filter_func_noncell(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
|
||||
}
|
||||
|
||||
|
|
@ -1758,21 +1918,21 @@ number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelS
|
|||
|
||||
static bool
|
||||
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
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 bool
|
||||
y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
const GraphicsCommand *g = data;
|
||||
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)g->y_offset - 1) < ((int32_t)(ref->start_row + ref->effective_num_rows));
|
||||
}
|
||||
|
||||
static bool
|
||||
z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
const GraphicsCommand *g = data;
|
||||
return ref->z_index == g->z_index;
|
||||
}
|
||||
|
|
@ -1780,13 +1940,13 @@ z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixe
|
|||
|
||||
static bool
|
||||
point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
|
||||
}
|
||||
|
||||
static bool
|
||||
point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) return false;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) return false;
|
||||
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
|
||||
}
|
||||
|
||||
|
|
@ -1842,7 +2002,7 @@ grman_resize(GraphicsManager *self, index_type old_lines UNUSED, index_type line
|
|||
const unsigned int vertical_shrink_size = num_content_lines_before - num_content_lines_after;
|
||||
for (img = self->images; img != NULL; img = img->hh.next) {
|
||||
for (ref = img->refs; ref != NULL; ref = ref->hh.next) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) continue;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) continue;
|
||||
ref->start_row -= vertical_shrink_size;
|
||||
}
|
||||
}
|
||||
|
|
@ -1855,7 +2015,7 @@ grman_rescale(GraphicsManager *self, CellPixelSize cell) {
|
|||
self->layers_dirty = true;
|
||||
for (img = self->images; img != NULL; img = img->hh.next) {
|
||||
for (ref = img->refs; ref != NULL; ref = ref->hh.next) {
|
||||
if (ref->is_virtual_ref || ref->is_cell_image) continue;
|
||||
if (ref->is_virtual_ref || is_cell_image(ref)) continue;
|
||||
ref->cell_x_offset = MIN(ref->cell_x_offset, cell.width - 1);
|
||||
ref->cell_y_offset = MIN(ref->cell_y_offset, cell.height - 1);
|
||||
update_dest_rect(ref, ref->num_cols, ref->num_rows, cell);
|
||||
|
|
@ -2050,7 +2210,7 @@ W(update_layers) {
|
|||
ImageRenderData *r = self->render_data.item + i;
|
||||
#define R(which) Py_BuildValue("{sf sf sf sf}", "left", r->which.left, "top", r->which.top, "right", r->which.right, "bottom", r->which.bottom)
|
||||
PyTuple_SET_ITEM(ans, i,
|
||||
Py_BuildValue("{sN sN sI si sK}", "src_rect", R(src_rect), "dest_rect", R(dest_rect), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id)
|
||||
Py_BuildValue("{sN sN sI si sK sK}", "src_rect", R(src_rect), "dest_rect", R(dest_rect), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id, "ref_id", r->ref_id)
|
||||
);
|
||||
#undef R
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
typedef struct {
|
||||
unsigned char action, transmission_type, compressed, delete_action;
|
||||
uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet;
|
||||
uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet, parent_id, parent_placement_id;
|
||||
uint32_t width, height, x_offset, y_offset;
|
||||
union { uint32_t cursor_movement, compose_mode; };
|
||||
union { uint32_t cell_x_offset, blend_mode; };
|
||||
|
|
@ -23,6 +23,7 @@ typedef struct {
|
|||
union { int32_t z_index, gap; };
|
||||
size_t payload_sz;
|
||||
bool unicode_placement;
|
||||
int32_t offset_from_parent_x, offset_from_parent_y;
|
||||
} GraphicsCommand;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -36,13 +37,19 @@ typedef struct {
|
|||
int32_t start_row, start_column;
|
||||
uint32_t client_id;
|
||||
ImageRect src_rect;
|
||||
// Indicates whether this reference represents a cell image that should be
|
||||
// Indicates whether this reference represents a cell ref that should be
|
||||
// removed when the corresponding cells are modified.
|
||||
bool is_cell_image;
|
||||
// The internal id of the virtual ref this cell image was created from. Is a cell ref if this is non-zero.
|
||||
id_type virtual_ref_id;
|
||||
// Virtual refs are not displayed but they can be used as prototypes for
|
||||
// refs placed using unicode placeholders.
|
||||
bool is_virtual_ref;
|
||||
|
||||
struct {
|
||||
id_type img, ref;
|
||||
struct { int32_t x, y; } offset;
|
||||
} parent;
|
||||
|
||||
id_type internal_id;
|
||||
hash_handle_type hh;
|
||||
} ImageRef;
|
||||
|
|
@ -86,7 +93,7 @@ typedef struct {
|
|||
ImageRect src_rect, dest_rect;
|
||||
uint32_t texture_id, group_count;
|
||||
int z_index;
|
||||
id_type image_id;
|
||||
id_type image_id, ref_id;
|
||||
} ImageRenderData;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
31
kitty/parse-graphics-command.h
generated
31
kitty/parse-graphics-command.h
generated
|
|
@ -40,7 +40,11 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
cell_y_offset = 'Y',
|
||||
z_index = 'z',
|
||||
cursor_movement = 'C',
|
||||
unicode_placement = 'U'
|
||||
unicode_placement = 'U',
|
||||
parent_id = 'P',
|
||||
parent_placement_id = 'Q',
|
||||
offset_from_parent_x = 'H',
|
||||
offset_from_parent_y = 'V'
|
||||
};
|
||||
|
||||
enum KEYS key = 'a';
|
||||
|
|
@ -128,6 +132,18 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
case unicode_placement:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case parent_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case parent_placement_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case offset_from_parent_x:
|
||||
value_state = INT;
|
||||
break;
|
||||
case offset_from_parent_y:
|
||||
value_state = INT;
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, invalid key "
|
||||
"character: 0x%x",
|
||||
|
|
@ -240,6 +256,8 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
READ_UINT;
|
||||
switch (key) {
|
||||
I(z_index);
|
||||
I(offset_from_parent_x);
|
||||
I(offset_from_parent_y);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -273,6 +291,8 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
U(cell_y_offset);
|
||||
U(cursor_movement);
|
||||
U(unicode_placement);
|
||||
U(parent_id);
|
||||
U(parent_placement_id);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -332,7 +352,7 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
|
||||
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 sI sI "
|
||||
"sI si sI} y#",
|
||||
"sI sI sI si si si sI} y#",
|
||||
"graphics_command", "action", g.action, "delete_action", g.delete_action,
|
||||
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
||||
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
|
||||
|
|
@ -347,8 +367,11 @@ static inline void parse_graphics_code(Screen *screen,
|
|||
(unsigned int)g.num_lines, "cell_x_offset", (unsigned int)g.cell_x_offset,
|
||||
"cell_y_offset", (unsigned int)g.cell_y_offset, "cursor_movement",
|
||||
(unsigned int)g.cursor_movement, "unicode_placement",
|
||||
(unsigned int)g.unicode_placement, "z_index", (int)g.z_index,
|
||||
"payload_sz", g.payload_sz, payload, g.payload_sz);
|
||||
(unsigned int)g.unicode_placement, "parent_id", (unsigned int)g.parent_id,
|
||||
"parent_placement_id", (unsigned int)g.parent_placement_id, "z_index",
|
||||
(int)g.z_index, "offset_from_parent_x", (int)g.offset_from_parent_x,
|
||||
"offset_from_parent_y", (int)g.offset_from_parent_y, "payload_sz",
|
||||
g.payload_sz, payload, g.payload_sz);
|
||||
|
||||
screen_handle_graphics_command(screen, &g, payload);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,13 +123,15 @@ def create_screen():
|
|||
return s, 2 / s.columns, 2 / s.lines
|
||||
|
||||
def put_cmd(
|
||||
z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0,
|
||||
height=0, cell_x_off=0, cell_y_off=0, placement_id=0,
|
||||
cursor_movement=0, unicode_placeholder=0
|
||||
z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0,
|
||||
cell_y_off=0, placement_id=0, cursor_movement=0, unicode_placeholder=0, parent_id=0,
|
||||
parent_placement_id=0, offset_from_parent_x=0, offset_from_parent_y=0,
|
||||
):
|
||||
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d,C=%d,U=%d' % (
|
||||
z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off,
|
||||
cell_y_off, placement_id, cursor_movement, unicode_placeholder
|
||||
return (
|
||||
f'z={z},c={num_cols},r={num_lines},x={x_off},y={y_off},w={width},h={height},'
|
||||
f'X={cell_x_off},Y={cell_y_off},p={placement_id},C={cursor_movement},'
|
||||
f'U={unicode_placeholder},P={parent_id},Q={parent_placement_id},'
|
||||
f'H={offset_from_parent_x},V={offset_from_parent_y}'
|
||||
)
|
||||
|
||||
def put_image(screen, w, h, **kw):
|
||||
|
|
@ -540,6 +542,105 @@ def group_counts():
|
|||
self.ae(put_image(s, 8, 16, id=2, z=-1)[1], 'OK')
|
||||
self.ae(group_counts(), (2, 1, 1, 2, 1))
|
||||
|
||||
def test_image_parents(self):
|
||||
cw, ch = 10, 20
|
||||
iw, ih = 10, 20
|
||||
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
|
||||
|
||||
def positions():
|
||||
ans = {}
|
||||
def x(x):
|
||||
return round(((x + 1)/2) * s.columns)
|
||||
def y(y):
|
||||
return int(((-y + 1)/2) * s.lines)
|
||||
|
||||
for i in layers(s):
|
||||
d = i['dest_rect']
|
||||
ans[(i['image_id'], i['ref_id'])] = {'x': x(d['left']), 'y': y(d['top'])}
|
||||
return ans
|
||||
|
||||
def p(x, y=0):
|
||||
return {'x':x, 'y': y}
|
||||
|
||||
self.ae(put_image(s, iw, ih, id=1)[1], 'OK')
|
||||
self.ae(put_ref(s, id=1, placement_id=1), (1, ('OK', 'i=1,p=1')))
|
||||
pos = {(1, 1): p(0), (1, 2): p(1)}
|
||||
self.ae(positions(), pos)
|
||||
# check that adding a reference to a non-existent parent fails
|
||||
self.ae(put_ref(s, id=1, placement_id=33, parent_id=1, parent_placement_id=2), (1, ('ENOPARENT', 'i=1,p=33')))
|
||||
self.ae(put_ref(s, id=1, placement_id=33, parent_id=33), (1, ('ENOPARENT', 'i=1,p=33')))
|
||||
# check that we cannot add a reference that is its own parent
|
||||
self.ae(put_ref(s, id=1, placement_id=1, parent_id=1, parent_placement_id=1), (1, ('EINVAL', 'i=1,p=1')))
|
||||
|
||||
self.ae(put_image(s, iw, ih, id=2)[1], 'OK')
|
||||
pos[(2,1)] = p(2)
|
||||
self.ae(positions(), pos)
|
||||
# Add two children to the first placement of img2
|
||||
before = s.cursor.x, s.cursor.y
|
||||
self.ae(put_ref(s, id=1, placement_id=2, parent_id=2, offset_from_parent_y=3), (1, ('OK', 'i=1,p=2')))
|
||||
self.ae(before, (s.cursor.x, s.cursor.y), 'Cursor must not move for child image')
|
||||
pos[(1,3)] = p(2, 3)
|
||||
self.ae(positions(), pos)
|
||||
self.ae(put_ref(s, id=2, placement_id=3, parent_id=2, offset_from_parent_y=4), (2, ('OK', 'i=2,p=3')))
|
||||
pos[(2,2)] = p(2, 4)
|
||||
self.ae(positions(), pos)
|
||||
# Add a grand child to the second child of img2
|
||||
self.ae(put_ref(s, id=2, placement_id=4, parent_id=2, parent_placement_id=3, offset_from_parent_x=-1), (2, ('OK', 'i=2,p=4')))
|
||||
pos[(2,3)] = p(pos[(2,2)]['x']-1, pos[(2,2)]['y'])
|
||||
self.ae(positions(), pos)
|
||||
# Check that creating a cycle is prevented
|
||||
self.ae(put_ref(s, id=2, placement_id=3, parent_id=2, parent_placement_id=4), (2, ('ECYCLE', 'i=2,p=3')))
|
||||
self.ae(positions(), pos)
|
||||
# Check that depth is limited
|
||||
for i in range(5, 12):
|
||||
q = put_ref(s, id=2, placement_id=i, parent_id=2, parent_placement_id=i-1, offset_from_parent_x=-1)[1][0]
|
||||
if q == 'ETOODEEP':
|
||||
break
|
||||
self.ae(q, 'OK')
|
||||
else:
|
||||
self.assertTrue(False, 'Failed to limit reference chain depth')
|
||||
# Check that deleting a parent removes all descendants
|
||||
send_command(s, 'a=d,d=i,i=2,p=3')
|
||||
pos.pop((2,3)), pos.pop((2,2))
|
||||
self.ae(positions(), pos)
|
||||
# Check that deleting a parent deletes all descendants and also removes
|
||||
# images with no remaining placements
|
||||
self.ae(put_ref(s, id=2, placement_id=3, parent_id=2, offset_from_parent_y=4), (2, ('OK', 'i=2,p=3')))
|
||||
pos[(2,11)] = p(2, 4)
|
||||
self.ae(positions(), pos)
|
||||
self.ae(put_image(s, iw, ih, id=3, placement_id=97, parent_id=2, parent_placement_id=3)[1], 'OK')
|
||||
pos[(3,1)] = p(2, 4)
|
||||
self.ae(positions(), pos)
|
||||
send_command(s, 'a=d,d=i,i=2')
|
||||
pos.pop((3,1)), pos.pop((2,11)), pos.pop((2,1)), pos.pop((1,3))
|
||||
self.ae(positions(), pos)
|
||||
# Check that virtual placements that try to be relative are rejected
|
||||
self.ae(put_ref(s, id=1, placement_id=11, parent_id=1, unicode_placeholder=1), (1, ('EINVAL', 'i=1,p=11')))
|
||||
# Check creation of children of a unicode placeholder based image
|
||||
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
|
||||
put_image(s, 20, 20, num_cols=4, num_lines=2, unicode_placeholder=1, id=42)
|
||||
s.update_only_line_graphics_data()
|
||||
self.assertFalse(positions()) # the reference is virtual
|
||||
self.ae(put_ref(s, id=42, placement_id=11, parent_id=42, offset_from_parent_y=2, offset_from_parent_x=1), (42, ('OK', 'i=42,p=11')))
|
||||
self.assertFalse(positions()) # the reference is virtual without any cell images so the child is invisible
|
||||
s.apply_sgr("38;5;42")
|
||||
# These two characters will become one 2x1 ref.
|
||||
s.cursor.x = s.cursor.y = 1
|
||||
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\u030D")
|
||||
s.cursor.x = s.cursor.y = 0
|
||||
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\u030D")
|
||||
s.update_only_line_graphics_data()
|
||||
pos = {(1, 2): p(1, 2), (1, 3): p(0), (1, 4): p(1)}
|
||||
self.ae(positions(), pos)
|
||||
s.cursor.x = s.cursor.y = 0
|
||||
s.erase_in_display(0, False)
|
||||
s.update_only_line_graphics_data()
|
||||
self.assertFalse(positions()) # the reference is virtual without any cell images so the child is invisible
|
||||
s.cursor.x = s.cursor.y = 2
|
||||
s.draw("\U0010EEEE\u0305\u0305\U0010EEEE\u0305\u030D")
|
||||
s.update_only_line_graphics_data()
|
||||
self.ae(positions(), {(1, 5): {'x': 2, 'y': 2}, (1, 2): {'x': 3, 'y': 4}})
|
||||
|
||||
def test_unicode_placeholders(self):
|
||||
# This test tests basic image placement using using unicode placeholders
|
||||
cw, ch = 10, 20
|
||||
|
|
|
|||
|
|
@ -420,7 +420,9 @@ def c(**k):
|
|||
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 cursor_movement'
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement').split():
|
||||
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet unicode_placement'
|
||||
' parent_id parent_placement_id offset_from_parent_x offset_from_parent_y'
|
||||
).split():
|
||||
k.setdefault(f, 0)
|
||||
p = k.pop('payload', '').encode('utf-8')
|
||||
k['payload_sz'] = len(p)
|
||||
|
|
|
|||
Loading…
Reference in a new issue