From e8afd5241cb0fc99a6f6d2ba0e83d8987507a03a Mon Sep 17 00:00:00 2001 From: Daniel Hirt Date: Wed, 1 Jul 2015 16:24:31 +0100 Subject: [PATCH] Evas Textblock: Add obstacle objects feature Summary: Introducing a new feature for Evas Textblock. This allows the layout to wrap around other evas objects. The following API is added: - obstacle_add - obstacle_del - obstacle_update Evas objects can now serve as textblock obstacles, if positioned and visible on the text area. The text will wrap around the obstacles according to the wrapping mode set to it. This also modifies the current wrapping code to handle obstacle wrap points as well. The wrap index query function is modified so that forward-scanning (specific cases) may be disabled when treating obstacle wrap point. RTL text is currently unsupported by this feature. Consult added docs and example for usage. @feature Test Plan: Evas example and test in evas_suite are provided with this. Reviewers: tasn Subscribers: raster, JackDanielZ, cedric Differential Revision: https://phab.enlightenment.org/D2405 --- src/examples/evas/Makefile.am | 5 + src/examples/evas/Makefile.examples | 3 +- src/examples/evas/evas-textblock-obstacles.c | 311 ++++++++++++++++++ src/lib/evas/canvas/evas_object_textblock.c | 328 +++++++++++++++++-- src/lib/evas/canvas/evas_textblock.eo | 40 +++ src/tests/evas/evas_test_textblock.c | 88 +++++ 6 files changed, 755 insertions(+), 20 deletions(-) create mode 100644 src/examples/evas/evas-textblock-obstacles.c diff --git a/src/examples/evas/Makefile.am b/src/examples/evas/Makefile.am index 5c39cbd724..d2716522bf 100644 --- a/src/examples/evas/Makefile.am +++ b/src/examples/evas/Makefile.am @@ -159,6 +159,11 @@ evas_text_SOURCES = evas-text.c evas_text_LDADD = $(ECORE_EVAS_COMMON_LDADD) evas_text_CPPFLAGS = $(ECORE_EVAS_COMMON_CPPFLAGS) +EXTRA_PROGRAMS += evas_textblock_obstacles +evas_textblock_obstacles_SOURCES = evas-textblock-obstacles.c +evas_textblock_obstacles_LDADD = $(ECORE_EVAS_COMMON_LDADD) +evas_textblock_obstacles_CPPFLAGS = $(ECORE_EVAS_COMMON_CPPFLAGS) + EXTRA_PROGRAMS += evas_smart_object evas_smart_object_SOURCES = evas-smart-object.c evas_smart_object_LDADD = $(ECORE_EVAS_COMMON_LDADD) diff --git a/src/examples/evas/Makefile.examples b/src/examples/evas/Makefile.examples index 313be1b3b0..0d2b33a28b 100644 --- a/src/examples/evas/Makefile.examples +++ b/src/examples/evas/Makefile.examples @@ -23,7 +23,8 @@ EXAMPLES= evas-aspect-hints \ evas-smart-object \ evas-stacking \ evas-table \ - evas-text + evas-text \ + evas-textblock-obstacles all: edje examples edje: $(EDJE_OBJS) diff --git a/src/examples/evas/evas-textblock-obstacles.c b/src/examples/evas/evas-textblock-obstacles.c new file mode 100644 index 0000000000..babcfaee84 --- /dev/null +++ b/src/examples/evas/evas-textblock-obstacles.c @@ -0,0 +1,311 @@ +/** + * Evas textblock example for obstacles feature + * + * You'll need at least one engine built for it (excluding the buffer + * one). See stdout/stderr for output. + * + * You start with two registered obstacle objects. They are not visible + * at first, so the textblock simply shows the text that has been set to it. + * Once the obstacle is visible (show/hide keys in the example), the text will + * wrap around it. + * This example allows you to test two obstacles registered to the same + * textblock object. Also, you can play with size and position for each. + * Use the 'h' key to show the provided options for this test. + * + * @verbatim + * gcc -o evas-textblock-obstacles evas-textblock-obstacles.c `pkg-config --libs --cflags evas ecore ecore-evas` + * @endverbatim + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define PACKAGE_EXAMPLES_DIR "." +#endif + +#include +#include +#include +#include +#include "evas-common.h" + +#define WIDTH (320) +#define HEIGHT (240) + +#define POINTER_CYCLE(_ptr, _array) \ + do \ + { \ + if ((unsigned int)(((unsigned char *)(_ptr)) - ((unsigned char *)(_array))) >= \ + sizeof(_array)) \ + _ptr = _array; \ + } \ + while(0) + +static const char *commands = \ + "commands are:\n" + "\tt - change currently controlled obstacle\n" + "\tv - show/hide current obstacle\n" + "\ts - cycle current obstacle's size\n" + "\tp - change current obstacle's position (random)\n" + "\tw - cycle text wrapping modes (none/word/char/mixed)\n" + "\th - print help\n"; + +struct text_preset_data +{ + const char **font_ptr; + const char *font[3]; + + const char **wrap_ptr; + const char *wrap[4]; + + Evas_Coord *obs_size_ptr; + Evas_Coord obs_size[3]; + + Evas_Object **obs_ptr; /* pointer to the currently controlled obstacle object */ + Evas_Object *obs[2]; +}; + +struct test_data +{ + Ecore_Evas *ee; + Evas *evas; + struct text_preset_data t_data; + Evas_Object *text, *bg; + Evas_Coord w, h; + Evas_Textblock_Style *st; +}; + +static struct test_data d = {0}; + +static void +_on_destroy(Ecore_Evas *ee EINA_UNUSED) +{ + ecore_main_loop_quit(); +} + +static void +_canvas_resize_cb(Ecore_Evas *ee) +{ + int w, h; + + ecore_evas_geometry_get(ee, NULL, NULL, &w, &h); + evas_object_resize(d.bg, w, h); + evas_object_resize(d.text, w, h); + d.w = w; + d.h = h; +} + +static unsigned int +_getrand(unsigned int low, unsigned int high) +{ + return (rand() % (high - low)) + low; +} + +static void +_style_set(const char *wrap) +{ + char buf[2000]; + snprintf(buf, + 2000, + "DEFAULT='font=Sans font_size=16 color=#000 wrap=%s text_class=entry'" + "br='\n'" + "ps='ps'" + "tab='\t'", + wrap); + evas_textblock_style_set(d.st, buf); +} + +static void +_on_keydown(void *data EINA_UNUSED, + Evas *evas EINA_UNUSED, + Evas_Object *o EINA_UNUSED, + void *einfo) +{ + Evas_Event_Key_Down *ev = einfo; + + if (strcmp(ev->key, "h") == 0) /* print help */ + { + fprintf(stdout, commands); + return; + } + + if (strcmp(ev->key, "t") == 0) /* change obstacle type */ + { + (d.t_data.obs_ptr)++; + POINTER_CYCLE(d.t_data.obs_ptr, d.t_data.obs); + + fprintf(stdout, "Now controlling obstacle: %p\n", *d.t_data.obs_ptr); + + return; + } + + if (strcmp(ev->key, "v") == 0) /* change obstacle visibility */ + { + Eo *obj = *d.t_data.obs_ptr; + if (evas_object_visible_get(obj)) + evas_object_hide(obj); + else + evas_object_show(obj); + fprintf(stdout, "Show/hide toggle for obstacle %p\n", + *d.t_data.obs_ptr); + evas_object_textblock_obstacles_update(d.text); + + return; + } + + if (strcmp(ev->key, "s") == 0) /* change obstacle size */ + { + (d.t_data.obs_size_ptr)++; + POINTER_CYCLE(d.t_data.obs_size_ptr, d.t_data.obs_size); + + evas_object_resize(*d.t_data.obs_ptr, + *d.t_data.obs_size_ptr, + *d.t_data.obs_size_ptr); + + evas_object_textblock_obstacles_update(d.text); + + fprintf(stdout, "Changing obstacle size to: %d,%d\n", *d.t_data.obs_size_ptr, *d.t_data.obs_size_ptr); + + return; + } + if (strcmp(ev->key, "p") == 0) /* change obstacle position */ + { + Evas_Coord x, y; + Evas_Coord rx, ry, gx, gy; + x = _getrand(0, d.w); + y = _getrand(0, d.h); + evas_object_move(*d.t_data.obs_ptr, x, y); + evas_object_textblock_obstacles_update(d.text); + + fprintf(stdout, "Changing obstacles position\n"); + evas_object_move(*d.t_data.obs_ptr, x, y); + evas_object_geometry_get(d.t_data.obs[0], &rx, &ry, NULL, NULL); + evas_object_geometry_get(d.t_data.obs[1], &gx, &gy, NULL, NULL); + fprintf(stdout, "Obstacle #1 (red) : [%d,%d]\n", rx, ry); + fprintf(stdout, "Obstacle #2 (green): [%d,%d]\n", gx, gy); + + return; + } + if (strcmp(ev->key, "w") == 0) /* change obstacle position */ + { + (d.t_data.wrap_ptr)++; + POINTER_CYCLE(d.t_data.wrap_ptr, d.t_data.wrap); + fprintf(stdout, "Changing wrap mode to: %s\n", *d.t_data.wrap_ptr); + _style_set(*d.t_data.wrap_ptr); + evas_object_textblock_obstacles_update(d.text); + + return; + } +} + +static void +_obs_init(Evas_Object *obj) +{ + evas_object_resize(obj, 50, 50); +} + +static void +_text_init() +{ + d.st = evas_textblock_style_new(); + evas_object_textblock_style_set(d.text, d.st); + _style_set("word"); + + evas_object_textblock_text_markup_set(d.text, + "This is an example text to demonstrate the textblock object" + " with obstacle objects support." + " Any evas object can register itself as an obstacle to the textblock" + " object. Upon regstering, it affects the layout of the text in" + " certain situations. Usually, when the obstacle shows above the text" + " area, it will cause the layout of the text to split and move" + " parts of it, so that all text area is apparent." + ); +} + +int +main(void) +{ + if (!ecore_evas_init()) + return EXIT_FAILURE; + + /* example obstacles types */ + Evas_Object *rect, *rect2; + + /* init values one is going to cycle through while running this + * example */ + struct text_preset_data init_data = + { + .font = {"DejaVu", "Courier", "Utopia"}, + .wrap = {"word", "char", "mixed", "none"}, + .obs_size = {50, 70, 100}, + .obs = {NULL, NULL}, + }; + + d.t_data = init_data; + d.t_data.font_ptr = d.t_data.font; + d.t_data.obs_size_ptr = d.t_data.obs_size; + d.t_data.obs_ptr = d.t_data.obs; + + /* this will give you a window with an Evas canvas under the first + * engine available */ + d.ee = ecore_evas_new(NULL, 0, 0, WIDTH, HEIGHT, NULL); + if (!d.ee) + goto error; + printf("Window size set to [%d,%d]\n", WIDTH, HEIGHT); + + ecore_evas_callback_delete_request_set(d.ee, _on_destroy); + ecore_evas_callback_resize_set(d.ee, _canvas_resize_cb); + ecore_evas_show(d.ee); + + d.evas = ecore_evas_get(d.ee); + + d.bg = evas_object_rectangle_add(d.evas); + evas_object_color_set(d.bg, 255, 255, 255, 255); /* white bg */ + evas_object_move(d.bg, 0, 0); /* at canvas' origin */ + evas_object_resize(d.bg, WIDTH, HEIGHT); /* covers full canvas */ + evas_object_show(d.bg); + + evas_object_focus_set(d.bg, EINA_TRUE); + evas_object_event_callback_add( + d.bg, EVAS_CALLBACK_KEY_DOWN, _on_keydown, NULL); + + d.text = evas_object_textblock_add(d.evas); + _text_init(); + evas_object_resize(d.text, WIDTH, HEIGHT); + evas_object_move(d.text, 0, 0); + evas_object_show(d.text); + d.w = WIDTH; + d.h = HEIGHT; + + /* init obstacles */ + rect = evas_object_rectangle_add(d.evas); + d.t_data.obs[0] = rect; + evas_object_color_set(rect, 255, 0, 0, 255); + _obs_init(rect); + rect2 = evas_object_rectangle_add(d.evas); + d.t_data.obs[1] = rect2; + evas_object_color_set(rect2, 0, 255, 0, 255); + _obs_init(rect2); + + evas_object_textblock_obstacle_add(d.text, rect); + evas_object_textblock_obstacle_add(d.text, rect2); + + evas_object_show(d.t_data.obs[0]); + evas_object_show(d.t_data.obs[1]); + + fprintf(stdout, commands); + ecore_main_loop_begin(); + + evas_textblock_style_free(d.st); + ecore_evas_free(d.ee); + ecore_evas_shutdown(); + + return 0; + +error: + fprintf(stderr, "you got to have at least one evas engine built and linked" + " up to ecore-evas for this example to run properly.\n"); + ecore_evas_shutdown(); + return -1; +} + diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index 6351b1f73c..4e8ffaa4b6 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -497,6 +497,7 @@ struct _Evas_Object_Textblock Eina_List *ellip_prev_it; /* item that is placed before ellipsis item (0.0 <= ellipsis < 1.0), if required */ Eina_List *anchors_a; Eina_List *anchors_item; + Eina_List *obstacles; int last_w, last_h; struct { int l, r, t, b; @@ -512,6 +513,7 @@ struct _Evas_Object_Textblock } formatted, native; Eina_Bool redraw : 1; Eina_Bool changed : 1; + Eina_Bool obstacle_changed : 1; Eina_Bool content_changed : 1; Eina_Bool format_changed : 1; Eina_Bool have_ellipsis : 1; @@ -2560,6 +2562,8 @@ struct _Ctxt Eina_List *format_stack; Evas_Object_Textblock_Format *fmt; + Eina_List *obs_infos; /**< Extra information for items in current line. */ + int x, y; int w, h; int wmax, hmax; @@ -3405,6 +3409,12 @@ _layout_last_line_max_descent_adjust_calc(Ctxt *c, const Evas_Object_Textblock_P return 0; } +typedef struct _Evas_Textblock_Obstacle_Info +{ + Evas_Object_Textblock_Item *it; /**< the corresponding item node. */ + Evas_Coord obs_adv; + Evas_Coord obs_preadv; +} Evas_Textblock_Obstacle_Info; /** * @internal @@ -3419,6 +3429,9 @@ static void _layout_line_finalize(Ctxt *c, Evas_Object_Textblock_Format *fmt) { Evas_Object_Textblock_Item *it; + Evas_Coord obs_preadv = 0, obs_adv = 0; + Eina_List *i; + Evas_Textblock_Obstacle_Info *obs_info = NULL; Evas_Coord x = 0; /* If there are no text items yet, calc ascent/descent @@ -3463,12 +3476,28 @@ _layout_line_finalize(Ctxt *c, Evas_Object_Textblock_Format *fmt) } loop_advance: + obs_preadv = 0; + obs_adv = 0; + EINA_LIST_FOREACH(c->obs_infos, i, obs_info) + { + if (obs_info->it == it) + { + obs_preadv += obs_info->obs_preadv; + obs_adv += obs_info->obs_adv; + } + } + x += obs_preadv; it->x = x; - x += it->adv; + x += it->adv + obs_adv; if ((it->w > 0) && ((it->x + it->w) > c->ln->w)) c->ln->w = it->x + it->w; } + /* clear obstacle info for this line */ + EINA_LIST_FREE(c->obs_infos, obs_info) + { + free(obs_info); + } c->ln->y = c->y - c->par->y; c->ln->h = c->ascent + c->descent; @@ -4315,7 +4344,7 @@ _layout_get_charwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, static int _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, const Evas_Object_Textblock_Item *it, Eina_Bool mixed_wrap, - size_t line_start, const char *breaks) + size_t line_start, const char *breaks, Eina_Bool scan_fwd) { Eina_Bool wrap_after = EINA_FALSE; size_t wrap; @@ -4371,7 +4400,7 @@ _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, return ((orig_wrap >= line_start) && (orig_wrap < len)) ? ((int) orig_wrap) : -1; } - else + else if (scan_fwd) { /* Scan forward to find the next wrapping point */ wrap = orig_wrap; @@ -4383,6 +4412,7 @@ _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, /* If we need to find the position after the cutting point */ if ((wrap == line_start) || (wrap_after)) { + if (!scan_fwd) return wrap; if (mixed_wrap) { return _layout_get_charwrap(c, fmt, it, @@ -4417,20 +4447,20 @@ _layout_get_word_mixwrap_common(Ctxt *c, Evas_Object_Textblock_Format *fmt, static int _layout_get_wordwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, const Evas_Object_Textblock_Item *it, size_t line_start, - const char *breaks) + const char *breaks, Eina_Bool allow_scan_fwd) { return _layout_get_word_mixwrap_common(c, fmt, it, EINA_FALSE, line_start, - breaks); + breaks, allow_scan_fwd); } /* -1 means no wrap */ static int _layout_get_mixedwrap(Ctxt *c, Evas_Object_Textblock_Format *fmt, const Evas_Object_Textblock_Item *it, size_t line_start, - const char *breaks) + const char *breaks, Eina_Bool allow_scan_fwd) { return _layout_get_word_mixwrap_common(c, fmt, it, EINA_TRUE, line_start, - breaks); + breaks, allow_scan_fwd); } static Evas_Object_Textblock_Text_Item * @@ -4737,6 +4767,20 @@ _layout_par_append_ellipsis(Ctxt *c) ellip_ti->parent.ln = c->ln; c->x += ellip_ti->parent.adv; } +/* obstacles */ +static inline void +_layout_obstacles_update(Ctxt *c); + +typedef struct _Evas_Textblock_Obstacle +{ + Eo *eo_obs; /**< Pointer to evas object which serves as an obstacle. */ + Evas_Coord x, y, w, h; /**< Geometry of the obstacle object. x,y are + the offset position of the obstacle relative to the textblock object. */ + Eina_Bool visible : 1; +} Evas_Textblock_Obstacle; + +static Evas_Textblock_Obstacle * +_layout_item_obstacle_get(Ctxt *c, Evas_Object_Textblock_Item *it); /* 0 means go ahead, 1 means break without an error, 2 means * break with an error, should probably clean this a bit (enum/macro) @@ -4750,6 +4794,9 @@ _layout_par(Ctxt *c) int wrap = -1; char *line_breaks = NULL; + /* Obstacles logic */ + Eina_Bool handle_obstacles = EINA_FALSE; + if (!c->par->logical_items) return 2; @@ -4766,7 +4813,7 @@ _layout_par(Ctxt *c) * and we aren't just calculating. */ if (!c->par->text_node->is_new && !c->par->text_node->dirty && !c->width_changed && c->par->lines && - !c->o->have_ellipsis) + !c->o->have_ellipsis && !c->o->obstacle_changed) { Evas_Object_Textblock_Line *ln; /* Update c->line_no */ @@ -4782,6 +4829,14 @@ _layout_par(Ctxt *c) return 0; } + + /* Update all obstacles */ + if (c->o->obstacle_changed || c->width_changed) + { + _layout_obstacles_update(c); + handle_obstacles = EINA_TRUE; + } + c->par->text_node->dirty = EINA_FALSE; c->par->text_node->is_new = EINA_FALSE; c->par->rendered = EINA_FALSE; @@ -4837,11 +4892,15 @@ _layout_par(Ctxt *c) _layout_par_ellipsis_items(c, ellip); } + Eina_Bool item_preadv = EINA_FALSE; + Evas_Textblock_Obstacle *obs = NULL; for (i = c->par->logical_items ; i ; ) { Evas_Coord prevdescent = 0, prevascent = 0; int adv_line = 0; int redo_item = 0; + Evas_Textblock_Obstacle_Info *obs_info = NULL; + it = _ITEM(eina_list_data_get(i)); /* Skip visually deleted items */ if (it->visually_deleted) @@ -4879,13 +4938,17 @@ _layout_par(Ctxt *c) } } - + if (handle_obstacles && !obs) + { + obs = _layout_item_obstacle_get(c, it); + } /* Check if we need to wrap, i.e the text is bigger than the width, or we already found a wrap point. */ if ((c->w >= 0) && - (((c->x + it->w) > - (c->w - c->o->style_pad.l - c->o->style_pad.r - - c->marginl - c->marginr)) || (wrap > 0))) + (obs || + (((c->x + it->w) > + (c->w - c->o->style_pad.l - c->o->style_pad.r - + c->marginl - c->marginr)) || (wrap > 0)))) { /* Handle ellipsis here. If we don't have more width left * and no height left, or no more width left and no wrapping. @@ -4940,21 +5003,42 @@ _layout_par(Ctxt *c) else line_start = it->text_pos; - adv_line = 1; + /* Only when doing non-obstacle handling */ + if (!obs) + adv_line = 1; /* If we don't already have a wrap point from before */ if (wrap < 0) { + /* Originally with wrapping, we may have ended up + * wrapping on the item next to the current one, + * if the current one was the first item in a line. + * This is different with obstacles: we allow the + * wrapping point algorithm to consider the first + * item in a line as well, and "push" it forward + * after the obstacle. + * There is one specific case with obstacles, where + * we DON'T allow to scan forward on the textblock's + * edges, and that's if the first item in a line + * was pushed forward by an obstacle once, as there + * is a chance it will fit in the next lines. */ + Eina_Bool allow_scan_fwd = (!obs && !item_preadv); + Evas_Coord save_cw = c->w; + if (obs) + { + c->w = obs->x; + } if (it->format->wrap_word) wrap = _layout_get_wordwrap(c, it->format, it, - line_start, line_breaks); + line_start, line_breaks, allow_scan_fwd); else if (it->format->wrap_char) wrap = _layout_get_charwrap(c, it->format, it, line_start, line_breaks); else if (it->format->wrap_mixed) wrap = _layout_get_mixedwrap(c, it->format, it, - line_start, line_breaks); + line_start, line_breaks, allow_scan_fwd); else wrap = -1; + c->w = save_cw; } /* If it's before the item, rollback and apply. @@ -5013,6 +5097,13 @@ _layout_par(Ctxt *c) wrap -= it->text_pos; /* Cut here */ } } + /* Specific case for obstacles */ + if (obs && (wrap >= 0)) + { + obs_info = calloc(1, sizeof(Evas_Textblock_Obstacle_Info)); + obs_info->it = it; + c->obs_infos = eina_list_append(c->obs_infos, obs_info); + } if ((wrap >= 0) && ((size_t) wrap == it_len)) { /* Can happen if this is the last word in the paragraph */ @@ -5025,6 +5116,11 @@ _layout_par(Ctxt *c) _layout_item_text_split_strip_white(c, _ITEM_TEXT(it), i, wrap); } + if (obs) + { + obs_info->obs_adv = obs->x + obs->w - c->x - it->adv; + c->x = obs->x + obs->w; + } } else if (wrap == 0) { @@ -5037,7 +5133,17 @@ _layout_par(Ctxt *c) adv_line = 0; redo_item = 1; - _layout_line_advance(c, it->format); + if (obs) + { + obs_info->obs_preadv = obs->x + obs->w - c->x; + c->x = obs->x + obs->w; + item_preadv = EINA_TRUE; + } + else + { + _layout_line_advance(c, it->format); + item_preadv = EINA_FALSE; + } } else // (wrap < 0) { @@ -5045,6 +5151,7 @@ _layout_par(Ctxt *c) adv_line = 0; } /* Reset wrap */ + obs = NULL; wrap = -1; } } @@ -5070,10 +5177,11 @@ _layout_par(Ctxt *c) adv_line = 1; } } - c->x += it->adv; + if (!obs_info) c->x += it->adv; if (c->o->ellip_prev_it == i) _layout_par_append_ellipsis(c); i = eina_list_next(i); + item_preadv = EINA_FALSE; } if (adv_line) { @@ -5509,6 +5617,7 @@ _layout(const Evas_Object *eo_obj, int w, int h, int *w_ret, int *h_ret) c->align_auto = EINA_TRUE; c->ln = NULL; c->width_changed = (obj->cur->geometry.w != o->last_w); + c->obs_infos = NULL; /* Start of logical layout creation */ /* setup default base style */ @@ -5655,6 +5764,8 @@ _layout(const Evas_Object *eo_obj, int w, int h, int *w_ret, int *h_ret) LYDBG("ZZ: ... layout #2\n"); _layout(eo_obj, w, h, w_ret, h_ret); } + + c->o->obstacle_changed = EINA_FALSE; } /* @@ -6879,6 +6990,182 @@ evas_textblock_text_utf8_to_markup(const Evas_Object *eo_obj, const char *text) } +static void +_obstacle_update(Evas_Textblock_Obstacle *obs, Eo *eo_obj) +{ + Evas_Coord x, y; + Evas_Coord ox, oy, ow, oh; + Eo *eo_obs = obs->eo_obs; + + eo_do(eo_obs, efl_gfx_position_get(&ox, &oy), efl_gfx_size_get(&ow, &oh)); + eo_do(eo_obj, efl_gfx_position_get(&x, &y)); + + obs->x = ox - x; + obs->y = oy - y; + obs->w = ow; + obs->h = oh; +} + +static void +_layout_obstacles_update(Ctxt *c) +{ + Eina_List *i; + Eina_Bool obstacle_changed = c->o->obstacle_changed; + Evas_Textblock_Obstacle *obs; + + EINA_LIST_FOREACH(c->o->obstacles, i, obs) + { + if (obstacle_changed) + _obstacle_update(obs, c->obj); + } +} + +static Evas_Textblock_Obstacle * +_obstacle_find(Evas_Textblock_Data *obj, Eo *eo_obs) +{ + Evas_Textblock_Obstacle *obs; + Eina_List *i; + + EINA_LIST_FOREACH(obj->obstacles, i, obs) + { + if (eo_obs == obs->eo_obs) + return obs; + } + return NULL; +} + +Eina_Bool +_obstacle_del_cb(void *data, Eo *eo_obs, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + Eo *eo_obj = data; + Evas_Textblock_Data *obj = eo_data_scope_get(eo_obj, MY_CLASS); + Eina_List *i; + Evas_Textblock_Obstacle *obs; + + EINA_LIST_FOREACH(obj->obstacles, i, obs) + { + if (eo_obs == obs->eo_obs) + break; + } + obj->obstacles = eina_list_remove_list(obj->obstacles, i); + free(obs); + _evas_textblock_changed(obj, data); + obj->obstacle_changed = EINA_TRUE; + + return EINA_TRUE; +} + +static void +_obstacle_clear(Eo *eo_obj, Evas_Textblock_Obstacle *obs) +{ + eo_do(obs->eo_obs, eo_event_callback_del(EVAS_OBJECT_EVENT_DEL, + _obstacle_del_cb, eo_obj)); +} + +static void +_obstacle_free(Eo *eo_obj, Evas_Textblock_Obstacle *obs) +{ + _obstacle_clear(eo_obj, obs); + free(obs); +} + +static void +_obstacles_free(Eo *eo_obj, Evas_Textblock_Data *obj) +{ + Evas_Textblock_Obstacle *obs; + + EINA_LIST_FREE(obj->obstacles, obs) + { + _obstacle_free(eo_obj, obs); + } +} + +EOLIAN static Eina_Bool +_evas_textblock_obstacle_add(Eo *eo_obj, + Evas_Textblock_Data *obj, Eo *eo_obs) +{ + Evas_Textblock_Obstacle *obs; + + if (!eo_isa(eo_obs, EVAS_OBJECT_CLASS)) + return EINA_FALSE; + obs = _obstacle_find(obj, eo_obs); + if (obs) return EINA_FALSE; + + obs = calloc(1, sizeof(Evas_Textblock_Obstacle)); + if (!obs) return EINA_FALSE; + + obs->eo_obs = eo_obs; + eo_do(eo_obs, eo_event_callback_add(EVAS_OBJECT_EVENT_DEL,_obstacle_del_cb, + eo_obj)); + + obj->obstacles = eina_list_append(obj->obstacles, obs); + _obstacle_update(obs, eo_obj); + _evas_textblock_changed(obj, eo_obj); + obj->obstacle_changed = EINA_TRUE; + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_evas_textblock_obstacle_del(Eo *eo_obj, Evas_Textblock_Data *obj, + Eo *eo_obs EINA_UNUSED) +{ + Evas_Textblock_Obstacle *obs; + Eina_List *i; + + if (!eo_isa(eo_obs, EVAS_OBJECT_CLASS)) + return EINA_FALSE; + + EINA_LIST_FOREACH(obj->obstacles, i, obs) + { + if (eo_obs == obs->eo_obs) + { + break; + } + } + if (!i) return EINA_FALSE; + obj->obstacles = eina_list_remove_list(obj->obstacles, i); + _obstacle_free(eo_obj, obs); + _evas_textblock_changed(obj, eo_obj); + obj->obstacle_changed = EINA_TRUE; + return EINA_TRUE; +} + +EOLIAN static void +_evas_textblock_obstacles_update(Eo *eo_obj, Evas_Textblock_Data *obj) +{ + _evas_textblock_changed(obj, eo_obj); + obj->obstacle_changed = EINA_TRUE; +} + +static Evas_Textblock_Obstacle * +_layout_item_obstacle_get(Ctxt *c, Evas_Object_Textblock_Item *it) +{ + Evas_Textblock_Obstacle *obs, *min_obs = NULL; + Eina_List *i; + + EINA_LIST_FOREACH(c->o->obstacles, i, obs) + { + Eina_Bool is_visible; + eo_do(obs->eo_obs, is_visible = efl_gfx_visible_get()); + if (!is_visible) + continue; + if ((obs->y < c->y + it->h) && + (obs->x < c->x + it->w) && + (obs->x + obs->w > c->x) && + (obs->y + obs->h > c->y)) + { + if ((obs->x < c->w) && + (!min_obs || (obs->x < min_obs->x))) + { + min_obs = obs; + } + } + } + return min_obs; +} + /* cursors */ /** @@ -11242,8 +11529,10 @@ evas_object_textblock_free(Evas_Object *eo_obj) if (o->repch) eina_stringshare_del(o->repch); if (o->ellip_ti) _item_free(eo_obj, NULL, _ITEM(o->ellip_ti)); _format_command_shutdown(); -} + /* remove obstacles */ + _obstacles_free(eo_obj, o); +} static void evas_object_textblock_render(Evas_Object *eo_obj EINA_UNUSED, @@ -11764,7 +12053,8 @@ evas_object_textblock_coords_recalc(Evas_Object *eo_obj EINA_UNUSED, // obviously if content text changed we need to reformat it (o->content_changed) || // if format changed (eg styles) we need to re-format/match tags etc. - (o->format_changed) + (o->format_changed) || + (o->obstacle_changed) ) { LYDBG("ZZ: invalidate 2 %p ## %i != %i || %3.3f || %i && %i != %i | %i %i\n", eo_obj, obj->cur->geometry.w, o->last_w, o->valign, o->have_ellipsis, obj->cur->geometry.h, o->last_h, o->content_changed, o->format_changed); diff --git a/src/lib/evas/canvas/evas_textblock.eo b/src/lib/evas/canvas/evas_textblock.eo index 15b74c6b07..3832e50dd4 100644 --- a/src/lib/evas/canvas/evas_textblock.eo +++ b/src/lib/evas/canvas/evas_textblock.eo @@ -297,6 +297,46 @@ class Evas.Textblock (Evas.Object) @in ts: Evas.Textblock.Style *; /*@ the style to set. */ } } + obstacle_add { + /*@ + Add obstacle evas object @p eo_obs to be observed during layout of text. + The textblock does the layout of the text according to the position + of the obstacle. + + @return Returns true on success, false on failure. + + @since 1.15 */ + params { + @in eo_obs: Evas.Object *; + } + return: bool; + } + obstacle_del { + /*@ + Removes @p eo_obs from observation during text layout + + @return Returns true on success, false on failure. + + @since 1.15 */ + params { + @in eo_obs: Evas.Object *; + } + return: bool; + } + obstacles_update { + /*@ + Triggers for relayout due to obstacles' state change. The obstacles + alone don't affect the layout, until this is called. Use this after + doing changes (moving, positioning etc.) in the obstacles that you + would like to be considered in the layout. + For example: if you have just repositioned the obstacles to differrent + coordinates relative to the textblock, you need to call this so + it will consider this new state and will relayout the text. + + @return Returns no value. + + @since 1.15 */ + } } implements { Eo.Base.constructor; diff --git a/src/tests/evas/evas_test_textblock.c b/src/tests/evas/evas_test_textblock.c index 1c1d5965c4..fe4dfe1895 100644 --- a/src/tests/evas/evas_test_textblock.c +++ b/src/tests/evas/evas_test_textblock.c @@ -3304,6 +3304,93 @@ START_TEST(evas_textblock_delete) } END_TEST; +/* Runs x,y in [from,to] range */ +static void +_obstacle_run(Evas_Object *tb, Evas_Object *obj, + Evas_Coord from_x, Evas_Coord to_x, + Evas_Coord from_y, Evas_Coord to_y, + Evas_Coord bh) +{ + Evas_Coord fw, fh; + Evas_Coord x, y; + for (y = from_y; y <= to_y; y += 5) + { + for (x = from_x; x <= to_x; x += 5) + { + evas_object_move(obj, x, y); + evas_object_textblock_obstacles_update(tb); + evas_object_textblock_size_formatted_get(tb, &fw, &fh); + /* the obstacle size is large enough to assume that adding it + * will at least make the formatted height value bigger */ + ck_assert_int_ge(fh, bh); + } + } +} + +START_TEST(evas_textblock_obstacle) +{ + START_TB_TEST(); + Evas_Coord fw, fh; + Evas_Object *rect, *rect2, *rect3; + const char *buf = + "This is an example text to demonstrate the textblock object" + " with obstacle objects support." + " Any evas object can register itself as an obstacle to the textblock" + " object. Upon registring, it affects the layout of the text in" + " certain situations. Usually, when the obstacle shows above the text" + " area, it will cause the layout of the text to split and move" + " parts of it, so that all text area is apparent."; + + rect = evas_object_rectangle_add(evas); + rect2 = evas_object_rectangle_add(evas); + rect3 = evas_object_rectangle_add(evas); + evas_object_resize(rect, 50, 50); + evas_object_resize(rect2, 50, 50); + evas_object_resize(rect3, 50, 50); + evas_object_textblock_text_markup_set(tb, buf); + evas_textblock_cursor_format_prepend(cur, ""); + evas_object_textblock_size_formatted_get(tb, &fw, &fh); + + ck_assert(!evas_object_textblock_obstacle_del(tb, rect)); + + ck_assert(evas_object_textblock_obstacle_add(tb, rect)); + ck_assert(!evas_object_textblock_obstacle_add(tb, rect)); + + ck_assert(evas_object_textblock_obstacle_add(tb, rect2)); + ck_assert(evas_object_textblock_obstacle_add(tb, rect3)); + + evas_object_show(rect); + evas_object_show(rect2); + evas_object_show(rect3); + + /* Compare formatted size with and without obstacle */ + _obstacle_run(tb, rect, 0, fw, fh / 2, fh / 2, fh); + /* Now, with bigger obstacles */ + evas_object_resize(rect, 150, 150); + evas_object_resize(rect3, 300, 300); + evas_object_hide(rect); + evas_object_textblock_obstacles_update(tb); + _obstacle_run(tb, rect, 0, fw, fh / 2, fh / 2, fh); + + evas_object_textblock_obstacle_del(tb, rect); + /* running with rect, now that it's not observed */ + evas_textblock_cursor_format_prepend(cur, ""); + _obstacle_run(tb, rect, 0, fw, fh / 2, fh / 2, fh); + + evas_object_del(rect2); + /* running with rect again, since rect2 is deleted */ + evas_textblock_cursor_format_prepend(cur, ""); + _obstacle_run(tb, rect, 0, fw, fh / 2, fh / 2, fh); + + evas_object_del(rect); + _obstacle_run(tb, rect3, 0, fw, 0, 0, fh); + END_TB_TEST(); + /* Deleting rect3 later, so it will be first removed from observation, + * during freeing of the textblock */ + evas_object_del(rect3); +} +END_TEST; + void evas_test_textblock(TCase *tc) { tcase_add_test(tc, evas_textblock_simple); @@ -3325,5 +3412,6 @@ void evas_test_textblock(TCase *tc) tcase_add_test(tc, evas_textblock_wrapping); tcase_add_test(tc, evas_textblock_items); tcase_add_test(tc, evas_textblock_delete); + tcase_add_test(tc, evas_textblock_obstacle); }