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 }