clutter-text: Use the ink rect in the paint volume

Previously ClutterText was just reporting the allocation as the paint
volume. The preferred size of a ClutterText is just the logical
rectangle of the layout. A pango layout can sometimes draw outside of
its logical rectangle for example with an italicised font with large
serifs. Additionally, ClutterText doesn't make any attempt to clip the
text if the actor gets allocated a size too small for the text so it
would also end up drawing outside of the paint volume in that case. To
fix this, the paint volume is now reported as the ink rect of the
Pango layout. The rectangle for the cursor and selection is also
unioned into that because it won't necessarily be within the ink
rectangle.

The function for drawing the selection rectangles has been split up
into a generic function that calculates the rectangles that need to be
drawn and a function that draws them. That way the get_paint_volume
virtual can share the code to calculate the rectangles.

http://bugzilla.clutter-project.org/show_bug.cgi?id=2599
This commit is contained in:
Neil Roberts 2011-03-07 18:53:07 +00:00
parent a20a8071ed
commit d151d789f6

View File

@ -59,6 +59,7 @@
#include "clutter-private.h" /* includes <cogl-pango/cogl-pango.h> */
#include "clutter-profile.h"
#include "clutter-units.h"
#include "clutter-paint-volume-private.h"
/* cursor width in pixels */
#define DEFAULT_CURSOR_SIZE 2
@ -178,6 +179,11 @@ struct _ClutterTextPrivate
ClutterColor cursor_color;
guint cursor_size;
/* Box representing the paint volume. The box is lazily calculated
and cached */
ClutterPaintVolume paint_volume;
gboolean paint_volume_valid;
guint preedit_cursor_pos;
gint preedit_n_chars;
@ -248,6 +254,37 @@ static guint text_signals[LAST_SIGNAL] = { 0, };
static void clutter_text_font_changed_cb (ClutterText *text);
static void
clutter_text_dirty_paint_volume (ClutterText *text)
{
ClutterTextPrivate *priv = text->priv;
if (priv->paint_volume_valid)
{
clutter_paint_volume_free (&priv->paint_volume);
priv->paint_volume_valid = FALSE;
}
}
static void
clutter_text_queue_redraw (ClutterActor *self)
{
/* This is a wrapper for clutter_actor_queue_redraw that also
dirties the cached paint volume. It would be nice if we could
just override the default implementation of the queue redraw
signal to do this instead but that doesn't work because the
signal isn't immediately emitted when queue_redraw is called.
Clutter will however immediately call get_paint_volume when
queue_redraw is called so we do need to dirty it immediately. */
clutter_text_dirty_paint_volume (CLUTTER_TEXT (self));
clutter_actor_queue_redraw (self);
}
#define clutter_actor_queue_redraw \
Please_use_clutter_text_queue_redraw_instead
#define offset_real(t,p) ((p) == -1 ? g_utf8_strlen ((t), -1) : (p))
static gint
@ -277,7 +314,7 @@ clutter_text_clear_selection (ClutterText *self)
{
priv->selection_bound = priv->position;
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
}
}
@ -457,6 +494,8 @@ clutter_text_dirty_cache (ClutterText *text)
g_object_unref (priv->cached_layouts[i].layout);
priv->cached_layouts[i].layout = NULL;
}
clutter_text_dirty_paint_volume (text);
}
/*
@ -1370,12 +1409,113 @@ clutter_text_finalize (GObject *gobject)
if (priv->preedit_attrs)
pango_attr_list_unref (priv->preedit_attrs);
clutter_text_dirty_paint_volume (self);
g_free (priv->text);
g_free (priv->font_name);
G_OBJECT_CLASS (clutter_text_parent_class)->finalize (gobject);
}
typedef void (* ClutterTextSelectionFunc) (ClutterText *text,
const ClutterActorBox *box,
gpointer user_data);
static void
clutter_text_foreach_selection_rectangle (ClutterText *self,
ClutterTextSelectionFunc func,
gpointer user_data)
{
ClutterTextPrivate *priv = self->priv;
PangoLayout *layout = clutter_text_get_layout (self);
gchar *utf8 = clutter_text_get_display_text (self);
gint lines;
gint start_index;
gint end_index;
gint line_no;
if (priv->position == 0)
start_index = 0;
else
start_index = offset_to_bytes (utf8, priv->position);
if (priv->selection_bound == 0)
end_index = 0;
else
end_index = offset_to_bytes (utf8, priv->selection_bound);
if (start_index > end_index)
{
gint temp = start_index;
start_index = end_index;
end_index = temp;
}
lines = pango_layout_get_line_count (layout);
for (line_no = 0; line_no < lines; line_no++)
{
PangoLayoutLine *line;
gint n_ranges;
gint *ranges;
gint i;
gint index_;
gint maxindex;
ClutterActorBox box;
gfloat y, height;
line = pango_layout_get_line_readonly (layout, line_no);
pango_layout_line_x_to_index (line, G_MAXINT, &maxindex, NULL);
if (maxindex < start_index)
continue;
pango_layout_line_get_x_ranges (line, start_index, end_index,
&ranges,
&n_ranges);
pango_layout_line_x_to_index (line, 0, &index_, NULL);
clutter_text_position_to_coords (self,
bytes_to_offset (utf8, index_),
NULL, &y, &height);
box.y1 = y;
box.y2 = y + height;
for (i = 0; i < n_ranges; i++)
{
gint range_x;
gint range_width;
range_x = ranges[i * 2] / PANGO_SCALE;
/* Account for any scrolling in single line mode */
if (priv->single_line_mode)
range_x += priv->text_x;
range_width = (ranges[i * 2 + 1] - ranges[i * 2])
/ PANGO_SCALE;
box.x1 = range_x;
box.x2 = range_x + range_width;
func (self, &box, user_data);
}
g_free (ranges);
}
g_free (utf8);
}
static void
clutter_text_add_selection_rectangle_to_path (ClutterText *text,
const ClutterActorBox *box,
gpointer user_data)
{
cogl_path_rectangle (user_data, box->x1, box->y1, box->x2, box->y2);
}
/* Draws the selected text, its background, and the cursor */
static void
selection_paint (ClutterText *self)
@ -1417,82 +1557,9 @@ selection_paint (ClutterText *self)
else
{
/* Paint selection background first */
PangoLayout *layout = clutter_text_get_layout (self);
CoglPath *selection_path = cogl_path_new ();
CoglColor cogl_color = { 0, };
PangoLayout *layout = clutter_text_get_layout (self);
gchar *utf8 = clutter_text_get_display_text (self);
gint lines;
gint start_index;
gint end_index;
gint line_no;
if (position == 0)
start_index = 0;
else
start_index = offset_to_bytes (utf8, position);
if (priv->selection_bound == 0)
end_index = 0;
else
end_index = offset_to_bytes (utf8, priv->selection_bound);
if (start_index > end_index)
{
gint temp = start_index;
start_index = end_index;
end_index = temp;
}
lines = pango_layout_get_line_count (layout);
for (line_no = 0; line_no < lines; line_no++)
{
PangoLayoutLine *line;
gint n_ranges;
gint *ranges;
gint i;
gint index_;
gint maxindex;
gfloat y, height;
line = pango_layout_get_line_readonly (layout, line_no);
pango_layout_line_x_to_index (line, G_MAXINT, &maxindex, NULL);
if (maxindex < start_index)
continue;
pango_layout_line_get_x_ranges (line, start_index, end_index,
&ranges,
&n_ranges);
pango_layout_line_x_to_index (line, 0, &index_, NULL);
clutter_text_position_to_coords (self,
bytes_to_offset (utf8, index_),
NULL, &y, &height);
for (i = 0; i < n_ranges; i++)
{
gint range_x;
gint range_width;
range_x = ranges[i * 2] / PANGO_SCALE;
/* Account for any scrolling in single line mode */
if (priv->single_line_mode)
range_x += priv->text_x;
range_width = (ranges[i * 2 + 1] - ranges[i * 2])
/ PANGO_SCALE;
cogl_path_rectangle (selection_path,
range_x,
y,
range_x + range_width,
y + height);
}
g_free (ranges);
}
/* Paint selection background */
if (priv->selection_color_set)
@ -1507,6 +1574,11 @@ selection_paint (ClutterText *self)
color->blue,
paint_opacity * color->alpha / 255);
clutter_text_foreach_selection_rectangle
(self,
clutter_text_add_selection_rectangle_to_path,
selection_path);
cogl_path_fill (selection_path);
/* Paint selected text */
@ -1531,8 +1603,6 @@ selection_paint (ClutterText *self)
cogl_pango_render_layout (layout, priv->text_x, 0, &cogl_color, 0);
cogl_clip_pop ();
g_free (utf8);
}
}
}
@ -1878,6 +1948,10 @@ clutter_text_paint (ClutterActor *self)
gint text_x = priv->text_x;
gboolean clip_set = FALSE;
/* Note that if anything in this paint method changes it needs to be
reflected in the get_paint_volume implementation which is tightly
tied to the workings of this function */
if (G_UNLIKELY (priv->font_desc == NULL || priv->text == NULL))
{
CLUTTER_NOTE (ACTOR, "desc: %p, text %p",
@ -1972,13 +2046,122 @@ clutter_text_paint (ClutterActor *self)
}
static void
clutter_text_add_selection_to_paint_volume_cb (ClutterText *text,
const ClutterActorBox *box,
gpointer user_data)
{
ClutterPaintVolume *total_volume = user_data;
ClutterPaintVolume rect_volume;
ClutterVertex vertex;
_clutter_paint_volume_init_static (&rect_volume, CLUTTER_ACTOR (text));
vertex.x = box->x1;
vertex.y = box->y1;
vertex.z = 0.0f;
clutter_paint_volume_set_origin (&rect_volume, &vertex);
clutter_paint_volume_set_width (&rect_volume, box->x2 - box->x1);
clutter_paint_volume_set_height (&rect_volume, box->y2 - box->y1);
clutter_paint_volume_union (total_volume, &rect_volume);
clutter_paint_volume_free (&rect_volume);
}
static void
clutter_text_get_paint_volume_for_cursor (ClutterText *text,
ClutterPaintVolume *volume)
{
ClutterTextPrivate *priv = text->priv;
ClutterVertex origin;
clutter_text_ensure_cursor_position (text);
if (priv->position == priv->selection_bound)
{
origin.x = priv->cursor_pos.x;
origin.y = priv->cursor_pos.y;
origin.z = 0;
clutter_paint_volume_set_origin (volume, &origin);
clutter_paint_volume_set_width (volume, priv->cursor_pos.width);
clutter_paint_volume_set_height (volume, priv->cursor_pos.height);
}
else
{
clutter_text_foreach_selection_rectangle
(text, clutter_text_add_selection_to_paint_volume_cb, volume);
}
}
static gboolean
clutter_text_get_paint_volume (ClutterActor *self,
ClutterPaintVolume *volume)
{
return _clutter_actor_set_default_paint_volume (self,
CLUTTER_TYPE_TEXT,
volume);
ClutterText *text = CLUTTER_TEXT (self);
ClutterTextPrivate *priv = text->priv;
/* ClutterText uses the logical layout as the natural size of the
actor. This means that it can sometimes paint outside of its
allocation for example with italic fonts with serifs. Therefore
we should use the ink rectangle of the layout instead */
if (!priv->paint_volume_valid)
{
PangoLayout *layout;
PangoRectangle ink_rect;
ClutterVertex origin;
/* If the text is single line editable then it gets clipped to
the allocation anyway so we can just use that */
if (priv->editable && priv->single_line_mode)
return _clutter_actor_set_default_paint_volume (self,
CLUTTER_TYPE_TEXT,
volume);
if (G_OBJECT_TYPE (self) != CLUTTER_TYPE_TEXT)
return FALSE;
if (!clutter_actor_has_allocation (self))
return FALSE;
_clutter_paint_volume_init_static (&priv->paint_volume, self);
layout = clutter_text_get_layout (text);
pango_layout_get_extents (layout, &ink_rect, NULL);
origin.x = ink_rect.x / (float) PANGO_SCALE;
origin.y = ink_rect.y / (float) PANGO_SCALE;
origin.z = 0;
clutter_paint_volume_set_origin (&priv->paint_volume, &origin);
clutter_paint_volume_set_width (&priv->paint_volume,
ink_rect.width / (float) PANGO_SCALE);
clutter_paint_volume_set_height (&priv->paint_volume,
ink_rect.height / (float) PANGO_SCALE);
/* If the cursor is visible then that will likely be drawn
outside of the ink rectangle so we should merge that in */
if (priv->editable && priv->cursor_visible && priv->has_focus)
{
ClutterPaintVolume cursor_paint_volume;
_clutter_paint_volume_init_static (&cursor_paint_volume,
self);
clutter_text_get_paint_volume_for_cursor (text, &cursor_paint_volume);
clutter_paint_volume_union (&priv->paint_volume,
&cursor_paint_volume);
clutter_paint_volume_free (&cursor_paint_volume);
}
priv->paint_volume_valid = TRUE;
}
_clutter_paint_volume_copy_static (&priv->paint_volume, volume);
return TRUE;
}
static void
@ -2133,7 +2316,7 @@ clutter_text_key_focus_in (ClutterActor *actor)
priv->has_focus = TRUE;
clutter_actor_queue_redraw (actor);
clutter_text_queue_redraw (actor);
}
static void
@ -2143,7 +2326,7 @@ clutter_text_key_focus_out (ClutterActor *actor)
priv->has_focus = FALSE;
clutter_actor_queue_redraw (actor);
clutter_text_queue_redraw (actor);
}
static gboolean
@ -3416,7 +3599,7 @@ clutter_text_set_editable (ClutterText *self,
{
priv->editable = editable;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]);
}
@ -3466,7 +3649,7 @@ clutter_text_set_selectable (ClutterText *self,
{
priv->selectable = selectable;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTABLE]);
}
@ -3520,7 +3703,7 @@ clutter_text_set_activatable (ClutterText *self,
{
priv->activatable = activatable;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_ACTIVATABLE]);
}
@ -3611,7 +3794,7 @@ clutter_text_set_cursor_visible (ClutterText *self,
{
priv->cursor_visible = cursor_visible;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_VISIBLE]);
}
@ -3665,7 +3848,7 @@ clutter_text_set_cursor_color (ClutterText *self,
else
priv->cursor_color_set = FALSE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_COLOR]);
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_COLOR_SET]);
@ -3807,7 +3990,7 @@ clutter_text_set_selection_bound (ClutterText *self,
else
priv->selection_bound = selection_bound;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_BOUND]);
}
@ -3863,7 +4046,7 @@ clutter_text_set_selection_color (ClutterText *self,
else
priv->selection_color_set = FALSE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_COLOR]);
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTION_COLOR_SET]);
@ -3922,7 +4105,7 @@ clutter_text_set_selected_text_color (ClutterText *self,
else
priv->selected_text_color_set = FALSE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTED_TEXT_COLOR]);
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTED_TEXT_COLOR_SET]);
@ -4267,7 +4450,7 @@ clutter_text_set_color (ClutterText *self,
priv->text_color = *color;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_COLOR]);
}
@ -4741,7 +4924,7 @@ clutter_text_set_cursor_position (ClutterText *self,
time the cursor is moved up or down */
priv->x_pos = -1;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_POSITION]);
}
@ -4775,7 +4958,7 @@ clutter_text_set_cursor_size (ClutterText *self,
priv->cursor_size = size;
clutter_actor_queue_redraw (CLUTTER_ACTOR (self));
clutter_text_queue_redraw (CLUTTER_ACTOR (self));
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_CURSOR_SIZE]);
}