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 allows drawing via Cairo; the primary difference is that
26  * it is dynamically sized. To use, connect to the #StDrawingArea::repaint
27  * signal, and inside the signal handler, call
28  * st_drawing_area_get_context() to get the Cairo context to draw to.  The
29  * #StDrawingArea::repaint signal will be emitted by default when the area is
30  * resized or the CSS style changes; you can use the
31  * st_drawing_area_queue_repaint() as well.
32  */
33 
34 #include "st-drawing-area.h"
35 
36 #include <cairo.h>
37 #include <math.h>
38 
39 typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate;
40 struct _StDrawingAreaPrivate {
41   cairo_t *context;
42   guint in_repaint : 1;
43 };
44 
45 G_DEFINE_TYPE_WITH_PRIVATE (StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
46 
47 /* Signals */
48 enum
49 {
50   REPAINT,
51   LAST_SIGNAL
52 };
53 
54 static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
55 
56 static gboolean
draw_content(ClutterCanvas * canvas,cairo_t * cr,int width,int height,gpointer user_data)57 draw_content (ClutterCanvas *canvas,
58               cairo_t       *cr,
59               int            width,
60               int            height,
61               gpointer       user_data)
62 {
63   StDrawingArea *area = ST_DRAWING_AREA (user_data);
64   StDrawingAreaPrivate *priv = st_drawing_area_get_instance_private (area);
65 
66   priv->context = cr;
67   priv->in_repaint = TRUE;
68 
69   clutter_cairo_clear (cr);
70   g_signal_emit (area, st_drawing_area_signals[REPAINT], 0);
71 
72   priv->context = NULL;
73   priv->in_repaint = FALSE;
74 
75   return TRUE;
76 }
77 
78 static void
st_drawing_area_allocate(ClutterActor * self,const ClutterActorBox * box)79 st_drawing_area_allocate (ClutterActor          *self,
80                           const ClutterActorBox *box)
81 {
82   StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
83   ClutterContent *content = clutter_actor_get_content (self);
84   ClutterActorBox content_box;
85   int width, height;
86   float resource_scale;
87 
88   resource_scale = clutter_actor_get_resource_scale (self);
89 
90   clutter_actor_set_allocation (self, box);
91   st_theme_node_get_content_box (theme_node, box, &content_box);
92 
93   width = (int)(0.5 + content_box.x2 - content_box.x1);
94   height = (int)(0.5 + content_box.y2 - content_box.y1);
95 
96   clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
97   clutter_canvas_set_size (CLUTTER_CANVAS (content), width, height);
98 }
99 
100 static void
st_drawing_area_style_changed(StWidget * self)101 st_drawing_area_style_changed (StWidget  *self)
102 {
103   (ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
104 
105   st_drawing_area_queue_repaint (ST_DRAWING_AREA (self));
106 }
107 
108 static void
st_drawing_area_resource_scale_changed(ClutterActor * self)109 st_drawing_area_resource_scale_changed (ClutterActor *self)
110 {
111   float resource_scale;
112   ClutterContent *content = clutter_actor_get_content (self);
113 
114   resource_scale = clutter_actor_get_resource_scale (self);
115   clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
116 
117   if (CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed)
118     CLUTTER_ACTOR_CLASS (st_drawing_area_parent_class)->resource_scale_changed (self);
119 }
120 
121 static void
st_drawing_area_class_init(StDrawingAreaClass * klass)122 st_drawing_area_class_init (StDrawingAreaClass *klass)
123 {
124   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
125   StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
126 
127   actor_class->allocate = st_drawing_area_allocate;
128   widget_class->style_changed = st_drawing_area_style_changed;
129   actor_class->resource_scale_changed = st_drawing_area_resource_scale_changed;
130 
131   st_drawing_area_signals[REPAINT] =
132     g_signal_new ("repaint",
133                   G_TYPE_FROM_CLASS (klass),
134                   G_SIGNAL_RUN_LAST,
135                   G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
136                   NULL, NULL, NULL,
137                   G_TYPE_NONE, 0);
138 }
139 
140 static void
st_drawing_area_init(StDrawingArea * area)141 st_drawing_area_init (StDrawingArea *area)
142 {
143   ClutterContent *content = clutter_canvas_new ();
144   g_signal_connect (content, "draw", G_CALLBACK (draw_content), area);
145   clutter_actor_set_content (CLUTTER_ACTOR (area), content);
146   g_object_unref (content);
147 }
148 
149 /**
150  * st_drawing_area_queue_repaint:
151  * @area: the #StDrawingArea
152  *
153  * Will cause the actor to emit a #StDrawingArea::repaint signal before it is
154  * next drawn to the scene. Useful if some parameters for the area being
155  * drawn other than the size or style have changed. Note that
156  * clutter_actor_queue_redraw() will simply result in the same
157  * contents being drawn to the scene again.
158  */
159 void
st_drawing_area_queue_repaint(StDrawingArea * area)160 st_drawing_area_queue_repaint (StDrawingArea *area)
161 {
162   g_return_if_fail (ST_IS_DRAWING_AREA (area));
163 
164   clutter_content_invalidate (clutter_actor_get_content (CLUTTER_ACTOR (area)));
165 }
166 
167 /**
168  * st_drawing_area_get_context:
169  * @area: the #StDrawingArea
170  *
171  * Gets the Cairo context to paint to. This function must only be called
172  * from a signal handler or virtual function for the #StDrawingArea::repaint
173  * signal.
174  *
175  * JavaScript code must call the special dispose function before returning from
176  * the signal handler or virtual function to avoid leaking memory:
177  *
178  * |[<!-- language="JavaScript" -->
179  * function onRepaint(area) {
180  *     let cr = area.get_context();
181  *
182  *     // Draw to the context
183  *
184  *     cr.$dispose();
185  * }
186  *
187  * let area = new St.DrawingArea();
188  * area.connect('repaint', onRepaint);
189  * ]|
190  *
191  * Returns: (transfer none): the Cairo context for the paint operation
192  */
193 cairo_t *
st_drawing_area_get_context(StDrawingArea * area)194 st_drawing_area_get_context (StDrawingArea *area)
195 {
196   StDrawingAreaPrivate *priv;
197 
198   g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
199 
200   priv = st_drawing_area_get_instance_private (area);
201   g_return_val_if_fail (priv->in_repaint, NULL);
202 
203   return priv->context;
204 }
205 
206 /**
207  * st_drawing_area_get_surface_size:
208  * @area: the #StDrawingArea
209  * @width: (out) (optional): location to store the width of the painted area
210  * @height: (out) (optional): location to store the height of the painted area
211  *
212  * Gets the size of the cairo surface being painted to, which is equal
213  * to the size of the content area of the widget. This function must
214  * only be called from a signal handler for the #StDrawingArea::repaint signal.
215  */
216 void
st_drawing_area_get_surface_size(StDrawingArea * area,guint * width,guint * height)217 st_drawing_area_get_surface_size (StDrawingArea *area,
218                                   guint         *width,
219                                   guint         *height)
220 {
221   StDrawingAreaPrivate *priv;
222   ClutterContent *content;
223   float w, h, resource_scale;
224 
225   g_return_if_fail (ST_IS_DRAWING_AREA (area));
226 
227   priv = st_drawing_area_get_instance_private (area);
228   g_return_if_fail (priv->in_repaint);
229 
230   content = clutter_actor_get_content (CLUTTER_ACTOR (area));
231   clutter_content_get_preferred_size (content, &w, &h);
232 
233   resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (area));
234 
235   w /= resource_scale;
236   h /= resource_scale;
237 
238   if (width)
239     *width = ceilf (w);
240   if (height)
241     *height = ceilf (h);
242 }
243