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
This commit is contained in:
Daniel Hirt 2015-07-01 16:24:31 +01:00 committed by Tom Hacohen
parent 87a88b5685
commit e8afd5241c
6 changed files with 755 additions and 20 deletions

View File

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

View File

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

View File

@ -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 <Ecore.h>
#include <Ecore_Evas.h>
#include <stdio.h>
#include <errno.h>
#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 <item size=72x16></item>can register itself as an obstacle to the textblock"
" object. Upon reg<color=#0ff>stering, it aff</color>ects 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;
}

View File

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

View File

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

View File

@ -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 <item size=72x16></item>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, "<wrap=word>");
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, "<wrap=mixed>");
_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, "<wrap=char>");
_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);
}