gnome-shell-3.6.3.1/src/st/st-drawing-area.c

No issues found

  1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
  2 /*
  3  * st-drawing-area.c: A dynamically-sized Cairo drawing area
  4  *
  5  * Copyright 2009, 2010 Red Hat, Inc.
  6  *
  7  * This program is free software; you can redistribute it and/or modify
  8  * it under the terms of the GNU Lesser General Public License as
  9  * published by the Free Software Foundation, either version 2.1 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * This program is distributed in the hope it will be useful, but WITHOUT ANY
 13  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 14  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 15  * more details.
 16  *
 17  * You should have received a copy of the GNU Lesser General Public License
 18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
 19  */
 20 
 21 /**
 22  * SECTION:st-drawing-area
 23  * @short_description: A dynamically-sized Cairo drawing area
 24  *
 25  * #StDrawingArea is similar to #ClutterCairoTexture in that
 26  * it allows drawing via Cairo; the primary difference is that
 27  * it is dynamically sized.  To use, connect to the #StDrawingArea::repaint
 28  * signal, and inside the signal handler, call
 29  * st_drawing_area_get_context() to get the Cairo context to draw to.  The
 30  * #StDrawingArea::repaint signal will be emitted by default when the area is
 31  * resized or the CSS style changes; you can use the
 32  * st_drawing_area_queue_repaint() as well.
 33  */
 34 
 35 #include "st-drawing-area.h"
 36 
 37 #include <cairo.h>
 38 
 39 G_DEFINE_TYPE(StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
 40 
 41 struct _StDrawingAreaPrivate {
 42   CoglHandle texture;
 43   CoglHandle material;
 44   cairo_t *context;
 45   guint needs_repaint : 1;
 46   guint in_repaint : 1;
 47 };
 48 
 49 /* Signals */
 50 enum
 51 {
 52   REPAINT,
 53   LAST_SIGNAL
 54 };
 55 
 56 static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
 57 
 58 static void
 59 st_drawing_area_dispose (GObject *object)
 60 {
 61   StDrawingArea *area = ST_DRAWING_AREA (object);
 62   StDrawingAreaPrivate *priv = area->priv;
 63 
 64   if (priv->material != COGL_INVALID_HANDLE)
 65     {
 66       cogl_handle_unref (priv->material);
 67       priv->material = COGL_INVALID_HANDLE;
 68     }
 69 
 70   if (priv->texture != COGL_INVALID_HANDLE)
 71     {
 72       cogl_handle_unref (priv->texture);
 73       priv->texture = COGL_INVALID_HANDLE;
 74     }
 75 
 76   G_OBJECT_CLASS (st_drawing_area_parent_class)->dispose (object);
 77 }
 78 
 79 static void
 80 st_drawing_area_paint (ClutterActor *self)
 81 {
 82   StDrawingArea *area = ST_DRAWING_AREA (self);
 83   StDrawingAreaPrivate *priv = area->priv;
 84   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
 85   ClutterActorBox allocation_box;
 86   ClutterActorBox content_box;
 87   int width, height;
 88   CoglColor color;
 89   guint8 paint_opacity;
 90 
 91   (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class))->paint (self);
 92 
 93   clutter_actor_get_allocation_box (self, &allocation_box);
 94   st_theme_node_get_content_box (theme_node, &allocation_box, &content_box);
 95 
 96   width = (int)(0.5 + content_box.x2 - content_box.x1);
 97   height = (int)(0.5 + content_box.y2 - content_box.y1);
 98 
 99   if (priv->material == COGL_INVALID_HANDLE)
100     priv->material = cogl_material_new ();
101 
102   if (priv->texture != COGL_INVALID_HANDLE &&
103       (width != cogl_texture_get_width (priv->texture) ||
104        height != cogl_texture_get_height (priv->texture)))
105     {
106       cogl_handle_unref (priv->texture);
107       priv->texture = COGL_INVALID_HANDLE;
108     }
109 
110   if (width > 0 && height > 0)
111     {
112       if (priv->texture == COGL_INVALID_HANDLE)
113         {
114           priv->texture = cogl_texture_new_with_size (width, height,
115                                                       COGL_TEXTURE_NONE,
116                                                       CLUTTER_CAIRO_FORMAT_ARGB32);
117           priv->needs_repaint = TRUE;
118         }
119 
120       if (priv->needs_repaint)
121         {
122           cairo_surface_t *surface;
123 
124           surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
125           priv->context = cairo_create (surface);
126           priv->in_repaint = TRUE;
127           priv->needs_repaint = FALSE;
128 
129           g_signal_emit ((GObject*)area, st_drawing_area_signals[REPAINT], 0);
130 
131           priv->in_repaint = FALSE;
132           cairo_destroy (priv->context);
133           priv->context = NULL;
134 
135           cogl_texture_set_region (priv->texture, 0, 0, 0, 0, width, height, width, height,
136                                    CLUTTER_CAIRO_FORMAT_ARGB32,
137                                    cairo_image_surface_get_stride (surface),
138                                    cairo_image_surface_get_data (surface));
139 
140           cairo_surface_destroy (surface);
141         }
142     }
143 
144   cogl_material_set_layer (priv->material, 0, priv->texture);
145 
146   if (priv->texture)
147     {
148       paint_opacity = clutter_actor_get_paint_opacity (self);
149       cogl_color_set_from_4ub (&color,
150                                paint_opacity, paint_opacity, paint_opacity, paint_opacity);
151       cogl_material_set_color (priv->material, &color);
152 
153       cogl_set_source (priv->material);
154       cogl_rectangle_with_texture_coords (content_box.x1, content_box.y1,
155                                           width, height,
156                                           0.0f, 0.0f, 1.0f, 1.0f);
157     }
158 }
159 
160 static void
161 st_drawing_area_style_changed (StWidget  *self)
162 {
163   StDrawingArea *area = ST_DRAWING_AREA (self);
164   StDrawingAreaPrivate *priv = area->priv;
165 
166   (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
167 
168   priv->needs_repaint = TRUE;
169 }
170 
171 static void
172 st_drawing_area_class_init (StDrawingAreaClass *klass)
173 {
174   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
175   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
176   StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
177 
178   gobject_class->dispose = st_drawing_area_dispose;
179   actor_class->paint = st_drawing_area_paint;
180   widget_class->style_changed = st_drawing_area_style_changed;
181 
182   st_drawing_area_signals[REPAINT] =
183     g_signal_new ("repaint",
184                   G_TYPE_FROM_CLASS (klass),
185                   G_SIGNAL_RUN_LAST,
186                   G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
187                   NULL, NULL, NULL,
188                   G_TYPE_NONE, 0);
189 
190   g_type_class_add_private (gobject_class, sizeof (StDrawingAreaPrivate));
191 }
192 
193 static void
194 st_drawing_area_init (StDrawingArea *area)
195 {
196   area->priv = G_TYPE_INSTANCE_GET_PRIVATE (area, ST_TYPE_DRAWING_AREA,
197                                             StDrawingAreaPrivate);
198   area->priv->texture = COGL_INVALID_HANDLE;
199 }
200 
201 /**
202  * st_drawing_area_queue_repaint:
203  * @area: the #StDrawingArea
204  *
205  * Will cause the actor to emit a ::repaint signal before it is next
206  * drawn to the scene. Useful if some parameters for the area being
207  * drawn other than the size or style have changed. Note that
208  * clutter_actor_queue_redraw() will simply result in the same
209  * contents being drawn to the scene again.
210  */
211 void
212 st_drawing_area_queue_repaint (StDrawingArea *area)
213 {
214   StDrawingAreaPrivate *priv;
215 
216   g_return_if_fail (ST_IS_DRAWING_AREA (area));
217 
218   priv = area->priv;
219 
220   priv->needs_repaint = TRUE;
221   clutter_actor_queue_redraw (CLUTTER_ACTOR (area));
222 }
223 
224 /**
225  * st_drawing_area_get_context:
226  * @area: the #StDrawingArea
227  *
228  * Gets the Cairo context to paint to. This function must only be called
229  * from a signal hander for the ::repaint signal.
230  *
231  * Return Value: (transfer none): the Cairo context for the paint operation
232  */
233 cairo_t *
234 st_drawing_area_get_context (StDrawingArea *area)
235 {
236   g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
237   g_return_val_if_fail (area->priv->in_repaint, NULL);
238 
239   return area->priv->context;
240 }
241 
242 /**
243  * st_drawing_area_get_surface_size:
244  * @area: the #StDrawingArea
245  * @width: (out): location to store the width of the painted area
246  * @height: (out): location to store the height of the painted area
247  *
248  * Gets the size of the cairo surface being painted to, which is equal
249  * to the size of the content area of the widget. This function must
250  * only be called from a signal hander for the ::repaint signal.
251  */
252 void
253 st_drawing_area_get_surface_size (StDrawingArea *area,
254                                   guint         *width,
255                                   guint         *height)
256 {
257   StDrawingAreaPrivate *priv;
258 
259   g_return_if_fail (ST_IS_DRAWING_AREA (area));
260   g_return_if_fail (area->priv->in_repaint);
261 
262   priv = area->priv;
263 
264   if (width)
265     *width = cogl_texture_get_width (priv->texture);
266   if (height)
267     *height = cogl_texture_get_height (priv->texture);
268 }