Evas textblock: Started splitting the layouting to two stages, logical item creating and visual ordering of the items. This will let us to cache the logical items and just relayout the visual items when needed thus making everything a lot faster.

SVN revision: 56490
This commit is contained in:
Tom Hacohen 2011-01-30 10:40:14 +00:00
parent 50ed367970
commit 91456d3198
2 changed files with 158 additions and 298 deletions

View File

@ -149,6 +149,7 @@ _evas_object_text_items_clear(Evas_Object_Text *o)
}
}
#ifdef BIDI_SUPPORT
static int
_evas_object_text_it_compare_logical(const void *_it1, const void *_it2)
{
@ -161,6 +162,7 @@ _evas_object_text_it_compare_logical(const void *_it1, const void *_it2)
return 1;
}
#endif
/* FIXME: doc */
static int
_evas_object_text_last_up_to_pos(const Evas_Object *obj,

View File

@ -243,6 +243,7 @@ struct _Evas_Object_Textblock_Node_Text
char *utf8;
Evas_Object_Textblock_Node_Format *format_node;
Evas_BiDi_Paragraph_Props *bidi_props;
Eina_Bool dirty : 1;
};
struct _Evas_Object_Textblock_Node_Format
@ -290,6 +291,7 @@ struct _Evas_Object_Textblock_Paragraph
EINA_INLIST;
Evas_Object_Textblock_Line *lines;
Evas_Object_Textblock_Node_Text *text_node;
Eina_List *logical_items;
int x, y, w, h;
int line_no;
};
@ -671,13 +673,9 @@ _item_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln, Evas_Object_T
* @param ln the layout line to be freed, must not be NULL.
*/
static void
_line_free(const Evas_Object *obj, Evas_Object_Textblock_Line *ln)
_line_free(const Evas_Object *obj __UNUSED__, Evas_Object_Textblock_Line *ln)
{
while (ln->items)
{
Evas_Object_Textblock_Item *it = ln->items;
_item_free(obj, ln, it);
}
/* Items are freed from the logical list */
if (ln) free(ln);
}
@ -1746,6 +1744,7 @@ struct _Ctxt
Evas_Object_Textblock_Line *ln;
Eina_List *format_stack;
Evas_Object_Textblock_Format *fmt;
int x, y;
int w, h;
@ -1842,13 +1841,6 @@ static void
_layout_paragraph_new(Ctxt *c, Evas_Object_Textblock_Node_Text *n)
{
c->par = calloc(1, sizeof(Evas_Object_Textblock_Paragraph));
if (c->paragraphs)
{
Evas_Object_Textblock_Paragraph *last_par;
last_par = (Evas_Object_Textblock_Paragraph *)
EINA_INLIST_GET(c->paragraphs)->last;
c->par->y = last_par->y + last_par->h;
}
c->paragraphs = (Evas_Object_Textblock_Paragraph *)eina_inlist_append(EINA_INLIST_GET(c->paragraphs), EINA_INLIST_GET(c->par));
c->ln = NULL;
c->par->text_node = n;
@ -1870,6 +1862,16 @@ _paragraph_free(const Evas_Object *obj, Evas_Object_Textblock_Paragraph *par)
par->lines = (Evas_Object_Textblock_Line *)eina_inlist_remove(EINA_INLIST_GET(par->lines), EINA_INLIST_GET(par->lines));
_line_free(obj, ln);
}
{
Eina_List *i, *i_prev;
Evas_Object_Textblock_Item *it;
EINA_LIST_FOREACH_SAFE(par->logical_items, i, i_prev, it)
{
_item_free(obj, NULL, it);
}
eina_list_free(par->logical_items);
}
free(par);
}
@ -2012,7 +2014,6 @@ _layout_line_align_get(Ctxt *c)
return c->align;
}
#ifdef BIDI_SUPPORT
/**
* @internal
* Reorder the items in a line.
@ -2020,11 +2021,11 @@ _layout_line_align_get(Ctxt *c)
* @param line the line to reorder
*/
static void
_layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line)
_layout_line_order(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line)
{
/*FIXME: do it a bit more efficient - not very efficient ATM. */
#ifdef BIDI_SUPPORT
Evas_Object_Textblock_Item *it;
Evas_BiDi_Paragraph_Props *props;
EvasBiDiStrIndex *v_to_l = NULL;
size_t start, end;
size_t len;
@ -2032,73 +2033,71 @@ _layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line)
if (line->items && line->items->text_node &&
line->items->text_node->bidi_props)
{
Evas_BiDi_Paragraph_Props *props;
props = line->items->text_node->bidi_props;
start = end = line->items->text_pos;
}
else
{
return;
}
/* Find the first and last positions in the line */
/* Find the first and last positions in the line */
EINA_INLIST_FOREACH(line->items, it)
{
if (it->text_pos < start)
EINA_INLIST_FOREACH(line->items, it)
{
start = it->text_pos;
}
else
{
int tlen;
tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
eina_unicode_strlen(_ITEM_TEXT(it)->text) : 1;
if (it->text_pos + tlen > end)
if (it->text_pos < start)
{
end = it->text_pos + tlen;
start = it->text_pos;
}
}
}
len = end - start;
evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l);
/* Update visual pos */
{
Evas_Object_Textblock_Item *i;
i = line->items;
while (i)
{
i->visual_pos = evas_bidi_position_logical_to_visual(
v_to_l, len, i->text_pos - start);
i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next;
}
}
/*FIXME: not very efficient, sort the items arrays. Anyhow, should only
* reorder if it's a bidi paragraph */
{
Evas_Object_Textblock_Item *i, *j, *min;
i = line->items;
while (i)
{
min = i;
EINA_INLIST_FOREACH(i, j)
else
{
if (j->visual_pos < min->visual_pos)
int tlen;
tlen = (it->type == EVAS_TEXTBLOCK_ITEM_TEXT) ?
eina_unicode_strlen(_ITEM_TEXT(it)->text) : 1;
if (it->text_pos + tlen > end)
{
min = j;
end = it->text_pos + tlen;
}
}
if (min != i)
{
line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min));
line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i));
}
}
i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next;
len = end - start;
evas_bidi_props_reorder_line(NULL, start, len, props, &v_to_l);
/* Update visual pos */
{
Evas_Object_Textblock_Item *i;
i = line->items;
while (i)
{
i->visual_pos = evas_bidi_position_logical_to_visual(
v_to_l, len, i->text_pos - start);
i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(i)->next;
}
}
/*FIXME: not very efficient, sort the items arrays. Anyhow, should only
* reorder if it's a bidi paragraph */
{
Evas_Object_Textblock_Item *i, *j, *min;
i = line->items;
while (i)
{
min = i;
EINA_INLIST_FOREACH(i, j)
{
if (j->visual_pos < min->visual_pos)
{
min = j;
}
}
if (min != i)
{
line->items = (Evas_Object_Textblock_Item *) eina_inlist_remove(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min));
line->items = (Evas_Object_Textblock_Item *) eina_inlist_prepend_relative(EINA_INLIST_GET(line->items), EINA_INLIST_GET(min), EINA_INLIST_GET(i));
}
i = (Evas_Object_Textblock_Item *) EINA_INLIST_GET(min)->next;
}
}
}
#endif
/* Recalculate the positions according to the new order. */
{
@ -2113,10 +2112,10 @@ _layout_line_reorder(Ctxt *c __UNUSED__, Evas_Object_Textblock_Line *line)
}
}
/* Fix the order in each line correctly */
#ifdef BIDI_SUPPORT
if (v_to_l) free(v_to_l);
}
#endif
}
/**
* @internal
@ -2133,9 +2132,7 @@ _layout_line_advance(Ctxt *c, Evas_Object_Textblock_Format *fmt,
Evas_Object_Textblock_Item *it;
Eina_Bool no_text = EINA_TRUE;
#ifdef BIDI_SUPPORT
_layout_line_reorder(c, c->ln);
#endif
_layout_line_order(c, c->ln);
c->maxascent = c->maxdescent = 0;
EINA_INLIST_FOREACH(c->ln->items, it)
@ -2491,7 +2488,7 @@ _layout_word_next(Eina_Unicode *str, int p)
* @param it the item itself.
*/
static void
_layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt,
_layout_text_add_logical_item(Ctxt *c, Evas_Object_Textblock_Format *fmt,
Evas_Object_Textblock_Text_Item *ti)
{
int tw, th, adv, inset;
@ -2507,16 +2504,13 @@ _layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt,
inset = c->ENFN->font_inset_get(c->ENDT, fmt->font.font,
ti->text);
ti->inset = inset;
ti->parent.x = c->x;
adv = 0;
if (fmt->font.font)
adv = c->ENFN->font_h_advance_get(c->ENDT, fmt->font.font,
ti->text, &ti->parent.text_props);
ti->parent.adv = adv;
c->x += adv;
c->ln->items = (Evas_Object_Textblock_Item *)
eina_inlist_append(EINA_INLIST_GET(c->ln->items),
EINA_INLIST_GET(_ITEM(ti)));
ti->parent.x = 0;
c->par->logical_items = eina_list_append(c->par->logical_items, ti);
}
/**
@ -2534,8 +2528,7 @@ _layout_text_add_item(Ctxt *c, Evas_Object_Textblock_Format *fmt,
static void
_layout_text_append(Ctxt *c, Evas_Object_Textblock_Format *fmt, Evas_Object_Textblock_Node_Text *n, int start, int off, const char *repch)
{
int tw, new_line, empty_item;
int wrap, twrap, ch, index, white_stripped;
int new_line, empty_item;
Eina_Unicode *alloc_str = NULL;
const Eina_Unicode *str = EINA_UNICODE_EMPTY_STRING;
const Eina_Unicode *tbase;
@ -2610,8 +2603,6 @@ skip:
{
/* if this is the first line item and it starts with spaces - remove them */
int tmp_len;
wrap = 0;
white_stripped = 0;
ti = _layout_text_item_new(c, fmt, str);
ti->parent.text_node = n;
@ -2637,186 +2628,9 @@ skip:
&ti->parent.text_props, ti->parent.text_node->bidi_props,
ti->parent.text_pos, tmp_len);
}
tw = 0;
if (fmt->font.font)
tw = c->ENFN->font_h_advance_get(c->ENDT, ti->format->font.font,
ti->text, &ti->parent.text_props);
/* Check if we need to wrap, i.e the text is bigger than the width
* Only calculate wrapping if the width of the object is > 0 */
if ((c->w >= 0) &&
((fmt->wrap_word) || (fmt->wrap_char)) &&
((c->x + tw) >
(c->w - c->o->style_pad.l - c->o->style_pad.r -
c->marginl - c->marginr)))
{
wrap = _layout_text_cutoff_get(c, fmt, ti);
/* Avoiding too small textblocks to even contain one char */
if (wrap == 0)
GET_NEXT(str, wrap);
/* We need to wrap and found the position that overflows */
if (wrap > 0)
{
if (fmt->wrap_word)
{
index = wrap;
ch = GET_NEXT(str, index);
if (!_is_white(ch))
wrap = _layout_word_start(str, wrap);
/* If we found where to cut the text at, i.e the start
* of the word we were pointing at */
if (wrap > 0)
{
twrap = wrap;
ch = GET_PREV(str, twrap);
/* the text intersects the wrap point on a whitespace char */
if (_is_white(ch))
{
_layout_item_text_cutoff(c, ti, wrap);
twrap = wrap;
str += twrap;
}
/* intersects a word */
else
{
/* walk back to start of word */
twrap = _layout_word_start(str, wrap);
if (twrap != 0)
{
wrap = twrap;
ch = GET_PREV(str, twrap);
_layout_item_text_cutoff(c, ti, twrap);
str += wrap;
}
else
{
empty_item = 1;
_item_free(c->obj, NULL, _ITEM(ti));
if (c->ln->items)
{
ti = _ITEM_TEXT((EINA_INLIST_GET(c->ln->items))->last);
_layout_strip_trailing_whitespace(c, fmt, _ITEM(ti));
twrap = _layout_word_end(str, wrap);
if (twrap >= 0)
{
ch = GET_NEXT(str, twrap);
str += twrap;
}
else
str = NULL;
}
}
}
}
/* If we weren't able to find the start of the word we
* are currently pointing at, or we were able but it's
* the first word */
else
{
/* wrap now is the index of the word START */
index = wrap;
ch = GET_NEXT(str, index);
str += tmp_len;
/* If there are already items in this line, we
* should just try creating a new line for it */
if (c->ln->items)
{
white_stripped = _layout_item_abort(c, fmt, _ITEM(ti));
empty_item = 1;
}
/* If there were no items in this line, try to do
* our best wrapping possible since it's the middle
* of the word */
else
{
wrap = 0;
twrap = _layout_word_end(ti->text, wrap);
wrap = twrap;
if (twrap >= 0)
{
ch = GET_NEXT(str, wrap);
_layout_item_text_cutoff(c, ti, twrap);
}
if (wrap > 0)
str += wrap;
else
str = NULL;
}
}
}
else if (fmt->wrap_char)
{
_layout_item_text_cutoff(c, ti, wrap);
str += wrap;
}
/* Marked we wrapped and we want to start a new line */
new_line = 1;
}
/* We need to wrap, but for some reason we failed obatining the
* overflow position. */
else
{
/*FIXME: sanitize this error handling - should probably
* never get here anyway unless something really bad
* has happend */
/* wrap now is the index of the word START */
if (wrap < 0) wrap = 0;
index = wrap;
ch = GET_NEXT(str, index);
if (c->ln->items)
{
white_stripped = _layout_item_abort(c, fmt, _ITEM(ti));
empty_item = 1;
new_line = 1;
}
else
{
if (wrap <= 0)
{
wrap = 0;
twrap = _layout_word_end(ti->text, wrap);
wrap = _layout_word_next(ti->text, wrap);
if (twrap >= 0)
_layout_item_text_cutoff(c, ti, twrap);
if (wrap >= 0)
str += wrap;
else
str += tmp_len;
}
else
str += tmp_len;
new_line = 1;
}
}
}
else
str += tmp_len;
/* Set item properties */
if (empty_item)
{
empty_item = 0;
}
else
{
_layout_text_add_item(c, fmt, ti);
}
if (new_line)
{
if (str)
{
if (!white_stripped)
{
index = 0;
ch = GET_NEXT(str, index);
if (_is_white(ch)) str += index;
}
}
new_line = 0;
_layout_line_advance(c, fmt, EINA_TRUE);
}
_layout_text_add_logical_item(c, fmt, ti);
}
if (alloc_str) free(alloc_str);
@ -2841,9 +2655,7 @@ _layout_format_item_add(Ctxt *c, Evas_Object_Textblock_Node_Format *n, const cha
fi->item = eina_stringshare_add(item);
fi->source_node = n;
fi->parent.type = EVAS_TEXTBLOCK_ITEM_FORMAT;
c->ln->items = _ITEM(eina_inlist_append(
EINA_INLIST_GET(c->ln->items),
EINA_INLIST_GET(_ITEM(fi))));
c->par->logical_items = eina_list_append(c->par->logical_items, fi);
if (n)
{
fi->parent.text_node = n->text_node;
@ -2980,16 +2792,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c,
x2 = c->x + w;
if (x2 >
(c->w - c->o->style_pad.l -
c->o->style_pad.r -
c->marginl - c->marginr))
{
_layout_line_advance(c, fmt, EINA_TRUE);
x2 = w;
}
fi = _layout_format_item_add(c, n, NULL);
fi->parent.x = c->x;
fi->vsize = vsize;
fi->size = size;
fi->formatme = 1;
@ -2999,7 +2802,6 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c,
fi->parent.h = h;
fi->ascent = c->maxascent;
fi->descent = c->maxdescent;
c->x = x2;
handled = 1;
}
if (!handled)
@ -3031,9 +2833,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c,
Evas_Object_Textblock_Format_Item *fi;
fi = _layout_format_item_add(c, n, item);
fi->parent.x = c->x;
fi->parent.w = fi->parent.adv = 0;
_layout_line_advance(c, fmt, EINA_TRUE);
}
else if ((!strcmp(item, "\t")) || (!strcmp(item, "\\t")))
{
@ -3041,24 +2841,7 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c,
int x2;
x2 = c->x + fmt->tabstops;
/* Wrap lines if there's a size */
if ((c->w > 0) && (x2 >
(c->w - c->o->style_pad.l -
c->o->style_pad.r -
c->marginl - c->marginr)))
{
_layout_line_advance(c, fmt, EINA_TRUE);
x2 = c->x + fmt->tabstops;
}
if (c->ln->items)
{
Evas_Object_Textblock_Item *it;
it = (Evas_Object_Textblock_Item *)(EINA_INLIST_GET(c->ln->items))->last;
_layout_strip_trailing_whitespace(c, fmt, it);
}
fi = _layout_format_item_add(c, n, item);
fi->parent.x = c->x;
fi->parent.w = fi->parent.adv = x2 - c->x;
fi->formatme = 1;
c->x = x2;
@ -3076,6 +2859,51 @@ _layout_do_format(const Evas_Object *obj, Ctxt *c,
*_fmt = fmt;
}
static void
_layout_update_par(Ctxt *c)
{
Evas_Object_Textblock_Paragraph *last_par;
last_par = (Evas_Object_Textblock_Paragraph *)
EINA_INLIST_GET(c->par)->prev;
if (last_par)
{
c->par->y = last_par->y + last_par->h;
}
}
static void
_layout_visualize_par(Ctxt *c)
{
Evas_Object_Textblock_Item *it;
Eina_List *i;
if (!c->par->logical_items)
return;
_layout_line_new(c, c->fmt);
EINA_LIST_FOREACH(c->par->logical_items, i, it)
{
c->ln->items = (Evas_Object_Textblock_Item *)
eina_inlist_append(EINA_INLIST_GET(c->ln->items),
EINA_INLIST_GET(it));
/* FIXME: Add word wrappnig here */
if (it->type == EVAS_TEXTBLOCK_ITEM_FORMAT)
{
Evas_Object_Textblock_Format_Item *fi;
fi = _ITEM_FORMAT(it);
fi->y = c->y;
if (_IS_LINE_SEPARATOR(
eina_strbuf_string_get(fi->source_node->format)))
{
_layout_line_advance(c, c->fmt, EINA_TRUE);
}
}
else
{
c->fmt = _ITEM_TEXT(it)->format;
}
}
_layout_line_advance(c, c->fmt, EINA_FALSE);
}
/**
* @internal
* Create the layout from the nodes.
@ -3152,7 +2980,6 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_
int off;
_layout_paragraph_new(c, n); /* Each node is a paragraph */
_layout_line_new(c, fmt);
/* For each text node to thorugh all of it's format nodes
* append text from the start to the offset of the next format
@ -3191,9 +3018,22 @@ _layout(const Evas_Object *obj, int calc_only, int w, int h, int *w_ret, int *h_
_layout_text_append(c, fmt, n, start, -1, o->repch);
}
/* FIXME: move away? */
{
/* FIXME: is this the right format? or maybe it can change with pops? */
c->fmt = c->format_stack->data;
Evas_Object_Textblock_Paragraph *par;
EINA_INLIST_FOREACH(c->paragraphs, par)
{
c->par = par;
_layout_update_par(c);
_layout_visualize_par(c);
}
}
/* Advance the line so it'll calculate the size */
if ((c->ln) && (c->ln->items) && (fmt))
_layout_line_advance(c, fmt, EINA_TRUE);
//_layout_line_advance(c, fmt, EINA_TRUE);
/* Clean the rest of the format stack */
while (c->format_stack)
@ -5061,7 +4901,7 @@ evas_textblock_cursor_line_char_last(Evas_Textblock_Cursor *cur)
GET_NEXT(_ITEM_TEXT(it)->text, index);
cur->pos += index;
}
else if (!EINA_INLIST_GET(ln)->next)
else if (!EINA_INLIST_GET(ln)->next && !EINA_INLIST_GET(ln->par)->next)
{
cur->pos++;
}
@ -5732,6 +5572,8 @@ _evas_textblock_node_text_new(void)
n = calloc(1, sizeof(Evas_Object_Textblock_Node_Text));
n->unicode = eina_ustrbuf_new();
/* We want to layout each paragraph at least once. */
n->dirty = EINA_TRUE;
#ifdef BIDI_SUPPORT
n->bidi_props = evas_bidi_paragraph_props_new();
n->bidi_props->direction = EVAS_BIDI_PARAGRAPH_NATURAL;
@ -5927,6 +5769,22 @@ _evas_textblock_changed(Evas_Object_Textblock *o, Evas_Object *obj)
evas_object_change(obj);
}
/**
* @internal
* Mark and notifiy that the textblock, and specifically a node has changed.
*
* @param o the textblock object.
* @param obj the evas object.
* @param n the paragraph that changed.
*/
static void
_evas_textblock_content_changed(Evas_Object_Textblock *o, Evas_Object *obj,
Evas_Object_Textblock_Node_Text *n)
{
n->dirty = EINA_TRUE;
_evas_textblock_changed(o, obj);
}
/**
* Adds text to the current cursor position and set the cursor to *before*
* the start of the text just added.