Rework StDrawingArea not to use ClutterCairoTexture

Having StDrawingArea use ClutterCairoTexture causes circularity
problems with sizing - StDrawingArea wants to use its allocation for
the size of the texture, but ClutterTexture wants to use the size of
the texture to determine the requited size.

Avoid this by making StDrawingArea directly use Cairo and CoglTexture;
while doing this, the API is changed a bit for simplicity and to
match our use case:

 - Instead of clutter_cairo_texture_create(), we have
   st_drawing_area_get_context() to retrieve an already created
   context. This can only be called in the ::repaint signal.

 - The ::redraw signal is changed to ::repaint so we can have
   st_drawing_area_queue_repaint() that doesn't collide with
   clutter_actor_queue_redraw()

 - ::repaint is now emitted lazily when painting the actor rather
   than synchronously at various different points.

https://bugzilla.gnome.org/show_bug.cgi?id=611750
This commit is contained in:
Owen W. Taylor 2010-03-03 18:00:05 -05:00
parent 58bb0044b2
commit 33dca51650
7 changed files with 220 additions and 90 deletions

View File

@ -695,9 +695,9 @@ AppSwitcher.prototype = {
let n = this._arrows.length; let n = this._arrows.length;
let arrow = new St.DrawingArea(); let arrow = new St.DrawingArea();
arrow.connect('redraw', Lang.bind(this, arrow.connect('repaint', Lang.bind(this,
function (area, texture) { function (area) {
Shell.draw_box_pointer(texture, Shell.PointerDirection.DOWN, Shell.draw_box_pointer(area, Shell.PointerDirection.DOWN,
TRANSPARENT_COLOR, TRANSPARENT_COLOR,
this._curApp == n ? POPUP_ARROW_COLOR : POPUP_UNFOCUSED_ARROW_COLOR); this._curApp == n ? POPUP_ARROW_COLOR : POPUP_UNFOCUSED_ARROW_COLOR);
})); }));

View File

@ -633,8 +633,8 @@ AppIconMenu.prototype = {
this._windowContainerBox.connect('style-changed', Lang.bind(this, this._onStyleChanged)); this._windowContainerBox.connect('style-changed', Lang.bind(this, this._onStyleChanged));
this._arrow = new St.DrawingArea(); this._arrow = new St.DrawingArea();
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) { this._arrow.connect('repaint', Lang.bind(this, function (area) {
Shell.draw_box_pointer(texture, Shell.draw_box_pointer(area,
Shell.PointerDirection.LEFT, Shell.PointerDirection.LEFT,
this._borderColor, this._borderColor,
this._backgroundColor); this._backgroundColor);
@ -884,7 +884,7 @@ AppIconMenu.prototype = {
if (themeNode.get_border_color(St.Side.LEFT, color)) { if (themeNode.get_border_color(St.Side.LEFT, color)) {
this._borderColor = color; this._borderColor = color;
} }
this._arrow.emit_redraw(); this._arrow.queue_repaint();
} }
}; };
Signals.addSignalMethods(AppIconMenu.prototype); Signals.addSignalMethods(AppIconMenu.prototype);

View File

@ -9,6 +9,7 @@ const Lang = imports.lang;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
@ -117,10 +118,9 @@ ClockWidget.prototype = {
// it's the same in both modes // it's the same in both modes
height: COLLAPSED_WIDTH }); height: COLLAPSED_WIDTH });
this.collapsedActor = new Clutter.CairoTexture({ width: COLLAPSED_WIDTH, this.collapsedActor = new St.DrawingArea({ width: COLLAPSED_WIDTH,
height: COLLAPSED_WIDTH, height: COLLAPSED_WIDTH });
surface_width: COLLAPSED_WIDTH, this.collapsedActor.connect('repaint', Lang.bind(this, this._repaintClock));
surface_height: COLLAPSED_WIDTH });
this._update(); this._update();
}, },
@ -139,18 +139,18 @@ ClockWidget.prototype = {
}, },
_update: function() { _update: function() {
let time = new Date(); this.currentTime = new Date();
let msec_remaining = 60000 - (1000 * time.getSeconds() + let msec_remaining = 60000 - (1000 * this.currentTime.getSeconds() +
time.getMilliseconds()); this.currentTime.getMilliseconds());
if (msec_remaining < 500) { if (msec_remaining < 500) {
time.setMinutes(time.getMinutes() + 1); this.currentTime.setMinutes(time.getMinutes() + 1);
msec_remaining += 60000; msec_remaining += 60000;
} }
if (this.state == STATE_COLLAPSED || this.state == STATE_COLLAPSING) if (this.state == STATE_COLLAPSED || this.state == STATE_COLLAPSING)
this._updateCairo(time); this.collapsedActor.queue_repaint();
else else
this._updateText(time); this._updateText();
if (this.timer) if (this.timer)
Mainloop.source_remove(this.timer); Mainloop.source_remove(this.timer);
@ -160,13 +160,13 @@ ClockWidget.prototype = {
_updateText: function(time) { _updateText: function(time) {
// Translators: This is a time format. // Translators: This is a time format.
this.actor.set_text(time.toLocaleFormat(_("%H:%M"))); this.actor.set_text(this.currentTime.toLocaleFormat(_("%H:%M")));
}, },
_updateCairo: function(time) { _repaintClock: function(area) {
Shell.draw_clock(this.collapsedActor, Shell.draw_clock(area,
time.getHours() % 12, this.currentTime.getHours() % 12,
time.getMinutes()); this.currentTime.getMinutes());
} }
}; };

View File

@ -6,7 +6,7 @@
#include <math.h> #include <math.h>
void void
shell_draw_clock (ClutterCairoTexture *texture, shell_draw_clock (StDrawingArea *area,
int hour, int hour,
int minute) int minute)
{ {
@ -15,15 +15,14 @@ shell_draw_clock (ClutterCairoTexture *texture,
double xc, yc, radius, hour_radius, minute_radius; double xc, yc, radius, hour_radius, minute_radius;
double angle; double angle;
clutter_cairo_texture_get_surface_size (texture, &width, &height); st_drawing_area_get_surface_size (area, &width, &height);
xc = (double)width / 2; xc = (double)width / 2;
yc = (double)height / 2; yc = (double)height / 2;
radius = (double)(MIN(width, height)) / 2 - 2; radius = (double)(MIN(width, height)) / 2 - 2;
minute_radius = radius - 3; minute_radius = radius - 3;
hour_radius = radius / 2; hour_radius = radius / 2;
clutter_cairo_texture_clear (texture); cr = st_drawing_area_get_context (area);
cr = clutter_cairo_texture_create (texture);
cairo_set_line_width (cr, 1.0); cairo_set_line_width (cr, 1.0);
/* Outline */ /* Outline */
@ -48,8 +47,6 @@ shell_draw_clock (ClutterCairoTexture *texture,
xc + minute_radius * cos (angle), xc + minute_radius * cos (angle),
yc + minute_radius * sin (angle)); yc + minute_radius * sin (angle));
cairo_stroke (cr); cairo_stroke (cr);
cairo_destroy (cr);
} }
/** /**
@ -117,7 +114,7 @@ shell_fade_app_icon (ClutterTexture *source)
} }
void void
shell_draw_box_pointer (ClutterCairoTexture *texture, shell_draw_box_pointer (StDrawingArea *area,
ShellPointerDirection direction, ShellPointerDirection direction,
ClutterColor *border_color, ClutterColor *border_color,
ClutterColor *background_color) ClutterColor *background_color)
@ -125,10 +122,9 @@ shell_draw_box_pointer (ClutterCairoTexture *texture,
guint width, height; guint width, height;
cairo_t *cr; cairo_t *cr;
clutter_cairo_texture_get_surface_size (texture, &width, &height); st_drawing_area_get_surface_size (area, &width, &height);
clutter_cairo_texture_clear (texture); cr = st_drawing_area_get_context (area);
cr = clutter_cairo_texture_create (texture);
cairo_set_line_width (cr, 1.0); cairo_set_line_width (cr, 1.0);
@ -169,8 +165,6 @@ shell_draw_box_pointer (ClutterCairoTexture *texture,
clutter_cairo_set_source_color (cr, background_color); clutter_cairo_set_source_color (cr, background_color);
cairo_fill (cr); cairo_fill (cr);
cairo_destroy (cr);
} }
static void static void

View File

@ -4,6 +4,7 @@
#define __SHELL_DRAWING_H__ #define __SHELL_DRAWING_H__
#include <clutter/clutter.h> #include <clutter/clutter.h>
#include "st.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -14,12 +15,12 @@ typedef enum {
SHELL_POINTER_RIGHT SHELL_POINTER_RIGHT
} ShellPointerDirection; } ShellPointerDirection;
void shell_draw_box_pointer (ClutterCairoTexture *texture, void shell_draw_box_pointer (StDrawingArea *area,
ShellPointerDirection direction, ShellPointerDirection direction,
ClutterColor *border_color, ClutterColor *border_color,
ClutterColor *background_color); ClutterColor *background_color);
void shell_draw_clock (ClutterCairoTexture *texture, void shell_draw_clock (StDrawingArea *area,
int hour, int hour,
int minute); int minute);

View File

@ -6,66 +6,154 @@
* *
* #StDrawingArea is similar to #ClutterCairoTexture in that * #StDrawingArea is similar to #ClutterCairoTexture in that
* it allows drawing via Cairo; the primary difference is that * it allows drawing via Cairo; the primary difference is that
* it is dynamically sized. To use, connect to the #StDrawingArea::redraw * it is dynamically sized. To use, connect to the #StDrawingArea::repaint
* signal, and inside the signal handler, call * signal, and inside the signal handler, call
* clutter_cairo_texture_create() to begin drawing. The * st_drawing_area_get_context() to get the Cairo context to draw to. The
* #StDrawingArea::redraw signal will be emitted by default when the area is * #StDrawingArea::repaint signal will be emitted by default when the area is
* resized or the CSS style changes; you can use the * resized or the CSS style changes; you can use the
* st_drawing_area_emit_redraw() as well. * st_drawing_area_queue_repaint() as well.
*/ */
#include "st-drawing-area.h" #include "st-drawing-area.h"
#include <cairo.h> #include <cairo.h>
G_DEFINE_TYPE(StDrawingArea, st_drawing_area, ST_TYPE_BIN); /* Cairo stores the data in native byte order as ARGB but Cogl's pixel
formats specify the actual byte order. Therefore we need to use a
different format depending on the architecture */
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define PIXEL_FORMAT COGL_PIXEL_FORMAT_BGRA_8888_PRE
#else
#define PIXEL_FORMAT COGL_PIXEL_FORMAT_ARGB_8888_PRE
#endif
G_DEFINE_TYPE(StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
struct _StDrawingAreaPrivate { struct _StDrawingAreaPrivate {
ClutterCairoTexture *texture; CoglHandle *texture;
CoglHandle *material;
cairo_t *context;
guint needs_repaint : 1;
guint in_repaint : 1;
}; };
/* Signals */ /* Signals */
enum enum
{ {
REDRAW, REPAINT,
LAST_SIGNAL LAST_SIGNAL
}; };
static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 }; static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
static void static void
st_drawing_area_allocate (ClutterActor *self, st_drawing_area_dispose (GObject *object)
const ClutterActorBox *box, {
ClutterAllocationFlags flags) StDrawingArea *area = ST_DRAWING_AREA (object);
StDrawingAreaPrivate *priv = area->priv;
if (priv->material != COGL_INVALID_HANDLE)
{
cogl_handle_unref (priv->material);
priv->material = COGL_INVALID_HANDLE;
}
if (priv->texture != COGL_INVALID_HANDLE)
{
cogl_handle_unref (priv->texture);
priv->texture = COGL_INVALID_HANDLE;
}
}
static void
st_drawing_area_paint (ClutterActor *self)
{ {
StThemeNode *theme_node;
ClutterActorBox content_box;
StDrawingArea *area = ST_DRAWING_AREA (self); StDrawingArea *area = ST_DRAWING_AREA (self);
int width = box->x2 - box->x1; StDrawingAreaPrivate *priv = area->priv;
int height = box->y2 - box->y1; StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
ClutterActorBox allocation_box;
ClutterActorBox content_box;
int width, height;
CoglColor color;
guint8 paint_opacity;
(CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class))->allocate (self, box, flags); (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class))->paint (self);
theme_node = st_widget_get_theme_node (ST_WIDGET (self)); clutter_actor_get_allocation_box (self, &allocation_box);
st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
st_theme_node_get_content_box (theme_node, box, &content_box); width = (int)(0.5 + content_box.x2 - content_box.x1);
height = (int)(0.5 + content_box.y2 - content_box.y1);
if (priv->material == COGL_INVALID_HANDLE)
priv->material = cogl_material_new ();
if (priv->texture != COGL_INVALID_HANDLE &&
(width != cogl_texture_get_width (priv->texture) ||
height != cogl_texture_get_height (priv->texture)))
{
cogl_handle_unref (priv->texture);
priv->texture = COGL_INVALID_HANDLE;
}
if (width > 0 && height > 0) if (width > 0 && height > 0)
{ {
clutter_cairo_texture_set_surface_size (area->priv->texture, if (priv->texture == COGL_INVALID_HANDLE)
content_box.x2 - content_box.x1, {
content_box.y2 - content_box.y1); priv->texture = cogl_texture_new_with_size (width, height,
g_signal_emit (G_OBJECT (self), st_drawing_area_signals[REDRAW], 0, COGL_TEXTURE_NONE,
area->priv->texture); PIXEL_FORMAT);
priv->needs_repaint = TRUE;
}
if (priv->needs_repaint)
{
cairo_surface_t *surface;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
priv->context = cairo_create (surface);
priv->in_repaint = TRUE;
g_signal_emit ((GObject*)area, st_drawing_area_signals[REPAINT], 0);
priv->in_repaint = FALSE;
cairo_destroy (priv->context);
priv->context = NULL;
cogl_texture_set_region (priv->texture, 0, 0, 0, 0, width, height, width, height,
PIXEL_FORMAT,
cairo_image_surface_get_stride (surface),
cairo_image_surface_get_data (surface));
cairo_surface_destroy (surface);
}
}
cogl_material_set_layer (priv->material, 0, priv->texture);
if (priv->texture)
{
paint_opacity = clutter_actor_get_paint_opacity (self);
cogl_color_set_from_4ub (&color,
paint_opacity, paint_opacity, paint_opacity, paint_opacity);
cogl_material_set_color (priv->material, &color);
cogl_set_source (priv->material);
cogl_rectangle_with_texture_coords (content_box.x1, content_box.y1,
width, height,
0.0f, 0.0f, 1.0f, 1.0f);
} }
} }
static void static void
st_drawing_area_style_changed (StWidget *self) st_drawing_area_style_changed (StWidget *self)
{ {
StDrawingArea *area = ST_DRAWING_AREA (self);
StDrawingAreaPrivate *priv = area->priv;
(ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self); (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
st_drawing_area_emit_redraw (ST_DRAWING_AREA (self)); priv->needs_repaint = TRUE;
} }
static void static void
@ -75,17 +163,18 @@ st_drawing_area_class_init (StDrawingAreaClass *klass)
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
actor_class->allocate = st_drawing_area_allocate; gobject_class->dispose = st_drawing_area_dispose;
actor_class->paint = st_drawing_area_paint;
widget_class->style_changed = st_drawing_area_style_changed; widget_class->style_changed = st_drawing_area_style_changed;
st_drawing_area_signals[REDRAW] = st_drawing_area_signals[REPAINT] =
g_signal_new ("redraw", g_signal_new ("repaint",
G_TYPE_FROM_CLASS (klass), G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StDrawingAreaClass, redraw), G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
NULL, NULL, NULL, NULL,
g_cclosure_marshal_VOID__OBJECT, g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, CLUTTER_TYPE_CAIRO_TEXTURE); G_TYPE_NONE, 0);
g_type_class_add_private (gobject_class, sizeof (StDrawingAreaPrivate)); g_type_class_add_private (gobject_class, sizeof (StDrawingAreaPrivate));
} }
@ -95,31 +184,74 @@ st_drawing_area_init (StDrawingArea *area)
{ {
area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, ST_TYPE_DRAWING_AREA, area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, ST_TYPE_DRAWING_AREA,
StDrawingAreaPrivate); StDrawingAreaPrivate);
area->priv->texture = CLUTTER_CAIRO_TEXTURE (clutter_cairo_texture_new (1, 1)); area->priv->texture = COGL_INVALID_HANDLE;
clutter_container_add_actor (CLUTTER_CONTAINER (area), CLUTTER_ACTOR (area->priv->texture));
} }
/** /**
* st_drawing_area_get_texture: * st_drawing_area_queue_repaint:
* @area: the #StDrawingArea
* *
* Return Value: (transfer none): * Will cause the actor to emit a ::repaint signal before it is next
*/ * drawn to the scene. Useful if some parameters for the area being
ClutterCairoTexture * * drawn other than the size or style have changed. Note that
st_drawing_area_get_texture (StDrawingArea *area) * clutter_actor_queue_redraw() will simply result in the same
{ * contents being drawn to the scene again.
return area->priv->texture;
}
/**
* st_drawing_area_emit_redraw:
* @area: A #StDrawingArea
*
* Immediately emit a redraw signal. Useful if
* some parameters for the area being drawn other
* than the size or style have changed.
*/ */
void void
st_drawing_area_emit_redraw (StDrawingArea *area) st_drawing_area_queue_repaint (StDrawingArea *area)
{ {
g_signal_emit ((GObject*)area, st_drawing_area_signals[REDRAW], 0, area->priv->texture); StDrawingAreaPrivate *priv;
g_return_if_fail (ST_IS_DRAWING_AREA (area));
priv = area->priv;
priv->needs_repaint = TRUE;
clutter_actor_queue_redraw (CLUTTER_ACTOR (area));
}
/**
* st_drawing_area_get_context:
* @area: the #StDrawingArea
*
* Gets the Cairo context to paint to. This function must only be called
* from a signal hander for the ::repaint signal.
*
* Return Value: (transfer none): the Cairo context for the paint operation
*/
cairo_t *
st_drawing_area_get_context (StDrawingArea *area)
{
g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
g_return_val_if_fail (area->priv->in_repaint, NULL);
return area->priv->context;
}
/**
* st_drawing_area_get_surface_size:
* @area: the #StDrawingArea
* @width: (out): location to store the width of the painted area
* @height: (out): location to store the height of the painted area
*
* Gets the size of the cairo surface being painted to, which is equal
* to the size of the content area of the widget. This function must
* only be called from a signal hander for the ::repaint signal.
*/
void
st_drawing_area_get_surface_size (StDrawingArea *area,
guint *width,
guint *height)
{
StDrawingAreaPrivate *priv;
g_return_if_fail (ST_IS_DRAWING_AREA (area));
g_return_if_fail (area->priv->in_repaint);
priv = area->priv;
if (width)
*width = cogl_texture_get_width (priv->texture);
if (height)
*height = cogl_texture_get_height (priv->texture);
} }

View File

@ -2,7 +2,8 @@
#ifndef __ST_DRAWING_AREA_H__ #ifndef __ST_DRAWING_AREA_H__
#define __ST_DRAWING_AREA_H__ #define __ST_DRAWING_AREA_H__
#include "st-bin.h" #include "st-widget.h"
#include <cairo.h>
#define ST_TYPE_DRAWING_AREA (st_drawing_area_get_type ()) #define ST_TYPE_DRAWING_AREA (st_drawing_area_get_type ())
#define ST_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_DRAWING_AREA, StDrawingArea)) #define ST_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ST_TYPE_DRAWING_AREA, StDrawingArea))
@ -18,22 +19,24 @@ typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate;
struct _StDrawingArea struct _StDrawingArea
{ {
StBin parent; StWidget parent;
StDrawingAreaPrivate *priv; StDrawingAreaPrivate *priv;
}; };
struct _StDrawingAreaClass struct _StDrawingAreaClass
{ {
StBinClass parent_class; StWidgetClass parent_class;
void (*redraw) (StDrawingArea *area, ClutterCairoTexture *texture); void (*repaint) (StDrawingArea *area);
}; };
GType st_drawing_area_get_type (void) G_GNUC_CONST; GType st_drawing_area_get_type (void) G_GNUC_CONST;
ClutterCairoTexture *st_drawing_area_get_texture (StDrawingArea *area); void st_drawing_area_queue_repaint (StDrawingArea *area);
cairo_t *st_drawing_area_get_context (StDrawingArea *area);
void st_drawing_area_emit_redraw (StDrawingArea *area); void st_drawing_area_get_surface_size (StDrawingArea *area,
guint *width,
guint *height);
#endif /* __ST_DRAWING_AREA_H__ */ #endif /* __ST_DRAWING_AREA_H__ */