1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include "gtkroundedboxprivate.h"
21
22 #include "gtkcsscornervalueprivate.h"
23 #include "gtkcssnumbervalueprivate.h"
24 #include "gtkcsstypesprivate.h"
25
26 #include "gtkprivate.h"
27
28 #include <string.h>
29
30 typedef struct {
31 double angle1;
32 double angle2;
33 gboolean negative;
34 } Arc;
35
36 static inline guint
mem_hash(gconstpointer v,int len)37 mem_hash (gconstpointer v, int len)
38 {
39 const signed char *p;
40 const signed char *end;
41 guint32 h = 5381;
42
43 p = v;
44 end = p + len;
45 for (; p < end; p++)
46 h = (h << 5) + h + *p;
47
48 return h;
49 }
50
51 static guint
arc_path_hash(Arc * arc)52 arc_path_hash (Arc *arc)
53 {
54 return mem_hash ((gconstpointer)arc, sizeof (Arc));
55 }
56
57 static gboolean
arc_path_equal(Arc * arc1,Arc * arc2)58 arc_path_equal (Arc *arc1,
59 Arc *arc2)
60 {
61 return arc1->angle1 == arc2->angle1 &&
62 arc1->angle2 == arc2->angle2 &&
63 arc1->negative == arc2->negative;
64 }
65
66 /* We need the path to start with a line-to */
67 static cairo_path_t *
fixup_path(cairo_path_t * path)68 fixup_path (cairo_path_t *path)
69 {
70 cairo_path_data_t *data;
71
72 data = &path->data[0];
73 if (data->header.type == CAIRO_PATH_MOVE_TO)
74 data->header.type = CAIRO_PATH_LINE_TO;
75
76 return path;
77 }
78
79 static void
append_arc(cairo_t * cr,double angle1,double angle2,gboolean negative)80 append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
81 {
82 static GHashTable *arc_path_cache;
83 Arc key;
84 cairo_path_t *arc;
85
86 memset (&key, 0, sizeof (Arc));
87 key.angle1 = angle1;
88 key.angle2 = angle2;
89 key.negative = negative;
90
91 if (arc_path_cache == NULL)
92 arc_path_cache = g_hash_table_new_full ((GHashFunc)arc_path_hash,
93 (GEqualFunc)arc_path_equal,
94 g_free, (GDestroyNotify)cairo_path_destroy);
95
96 arc = g_hash_table_lookup (arc_path_cache, &key);
97 if (arc == NULL)
98 {
99 cairo_surface_t *surface;
100 cairo_t *tmp;
101
102 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
103 tmp = cairo_create (surface);
104
105 if (negative)
106 cairo_arc_negative (tmp, 0.0, 0.0, 1.0, angle1, angle2);
107 else
108 cairo_arc (tmp, 0.0, 0.0, 1.0, angle1, angle2);
109
110 arc = fixup_path (cairo_copy_path (tmp));
111 g_hash_table_insert (arc_path_cache, g_memdup2 (&key, sizeof (key)), arc);
112
113 cairo_destroy (tmp);
114 cairo_surface_destroy (surface);
115 }
116
117 cairo_append_path (cr, arc);
118 }
119
120 static void
_cairo_ellipsis(cairo_t * cr,double xc,double yc,double xradius,double yradius,double angle1,double angle2)121 _cairo_ellipsis (cairo_t *cr,
122 double xc, double yc,
123 double xradius, double yradius,
124 double angle1, double angle2)
125 {
126 cairo_matrix_t save;
127
128 if (xradius <= 0.0 || yradius <= 0.0)
129 {
130 cairo_line_to (cr, xc, yc);
131 return;
132 }
133
134 cairo_get_matrix (cr, &save);
135 cairo_translate (cr, xc, yc);
136 cairo_scale (cr, xradius, yradius);
137 append_arc (cr, angle1, angle2, FALSE);
138 cairo_set_matrix (cr, &save);
139 }
140
141 static void
_cairo_ellipsis_negative(cairo_t * cr,double xc,double yc,double xradius,double yradius,double angle1,double angle2)142 _cairo_ellipsis_negative (cairo_t *cr,
143 double xc, double yc,
144 double xradius, double yradius,
145 double angle1, double angle2)
146 {
147 cairo_matrix_t save;
148
149 if (xradius <= 0.0 || yradius <= 0.0)
150 {
151 cairo_line_to (cr, xc, yc);
152 return;
153 }
154
155 cairo_get_matrix (cr, &save);
156 cairo_translate (cr, xc, yc);
157 cairo_scale (cr, xradius, yradius);
158 append_arc (cr, angle1, angle2, TRUE);
159 cairo_set_matrix (cr, &save);
160 }
161
162 double
_gtk_rounded_box_guess_length(const GskRoundedRect * box,GtkCssSide side)163 _gtk_rounded_box_guess_length (const GskRoundedRect *box,
164 GtkCssSide side)
165 {
166 double length;
167 GtkCssSide before, after;
168
169 before = side;
170 after = (side + 1) % 4;
171
172 if (side & 1)
173 length = box->bounds.size.height
174 - box->corner[before].height
175 - box->corner[after].height;
176 else
177 length = box->bounds.size.width
178 - box->corner[before].width
179 - box->corner[after].width;
180
181 length += G_PI * 0.125 * (box->corner[before].width
182 + box->corner[before].height
183 + box->corner[after].width
184 + box->corner[after].height);
185
186 return length;
187 }
188
189 void
_gtk_rounded_box_path_side(const GskRoundedRect * box,cairo_t * cr,GtkCssSide side)190 _gtk_rounded_box_path_side (const GskRoundedRect *box,
191 cairo_t *cr,
192 GtkCssSide side)
193 {
194 switch (side)
195 {
196 case GTK_CSS_TOP:
197 _cairo_ellipsis (cr,
198 box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
199 box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
200 box->corner[GSK_CORNER_TOP_LEFT].width,
201 box->corner[GSK_CORNER_TOP_LEFT].height,
202 5 * G_PI_4, 3 * G_PI_2);
203 _cairo_ellipsis (cr,
204 box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
205 box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
206 box->corner[GSK_CORNER_TOP_RIGHT].width,
207 box->corner[GSK_CORNER_TOP_RIGHT].height,
208 - G_PI_2, -G_PI_4);
209 break;
210 case GTK_CSS_RIGHT:
211 _cairo_ellipsis (cr,
212 box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
213 box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
214 box->corner[GSK_CORNER_TOP_RIGHT].width,
215 box->corner[GSK_CORNER_TOP_RIGHT].height,
216 - G_PI_4, 0);
217 _cairo_ellipsis (cr,
218 box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
219 box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
220 box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
221 box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
222 0, G_PI_4);
223 break;
224 case GTK_CSS_BOTTOM:
225 _cairo_ellipsis (cr,
226 box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
227 box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
228 box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
229 box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
230 G_PI_4, G_PI_2);
231 _cairo_ellipsis (cr,
232 box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
233 box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
234 box->corner[GSK_CORNER_BOTTOM_LEFT].width,
235 box->corner[GSK_CORNER_BOTTOM_LEFT].height,
236 G_PI_2, 3 * G_PI_4);
237 break;
238 case GTK_CSS_LEFT:
239 _cairo_ellipsis (cr,
240 box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
241 box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
242 box->corner[GSK_CORNER_BOTTOM_LEFT].width,
243 box->corner[GSK_CORNER_BOTTOM_LEFT].height,
244 3 * G_PI_4, G_PI);
245 _cairo_ellipsis (cr,
246 box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
247 box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
248 box->corner[GSK_CORNER_TOP_LEFT].width,
249 box->corner[GSK_CORNER_TOP_LEFT].height,
250 G_PI, 5 * G_PI_4);
251 break;
252 default:
253 g_assert_not_reached ();
254 break;
255 }
256 }
257
258 void
_gtk_rounded_box_path_top(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)259 _gtk_rounded_box_path_top (const GskRoundedRect *outer,
260 const GskRoundedRect *inner,
261 cairo_t *cr)
262 {
263 double start_angle, middle_angle, end_angle;
264
265 if (outer->bounds.origin.y == inner->bounds.origin.y)
266 return;
267
268 if (outer->bounds.origin.x == inner->bounds.origin.x)
269 start_angle = G_PI;
270 else
271 start_angle = 5 * G_PI_4;
272 middle_angle = 3 * G_PI_2;
273 if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
274 end_angle = 0;
275 else
276 end_angle = 7 * G_PI_4;
277
278 cairo_new_sub_path (cr);
279
280 _cairo_ellipsis (cr,
281 outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
282 outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
283 outer->corner[GSK_CORNER_TOP_LEFT].width,
284 outer->corner[GSK_CORNER_TOP_LEFT].height,
285 start_angle, middle_angle);
286 _cairo_ellipsis (cr,
287 outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
288 outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
289 outer->corner[GSK_CORNER_TOP_RIGHT].width,
290 outer->corner[GSK_CORNER_TOP_RIGHT].height,
291 middle_angle, end_angle);
292
293 _cairo_ellipsis_negative (cr,
294 inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
295 inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
296 inner->corner[GSK_CORNER_TOP_RIGHT].width,
297 inner->corner[GSK_CORNER_TOP_RIGHT].height,
298 end_angle, middle_angle);
299 _cairo_ellipsis_negative (cr,
300 inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
301 inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
302 inner->corner[GSK_CORNER_TOP_LEFT].width,
303 inner->corner[GSK_CORNER_TOP_LEFT].height,
304 middle_angle, start_angle);
305
306 cairo_close_path (cr);
307 }
308
309 void
_gtk_rounded_box_path_right(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)310 _gtk_rounded_box_path_right (const GskRoundedRect *outer,
311 const GskRoundedRect *inner,
312 cairo_t *cr)
313 {
314 double start_angle, middle_angle, end_angle;
315
316 if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
317 return;
318
319 if (outer->bounds.origin.y == inner->bounds.origin.y)
320 start_angle = 3 * G_PI_2;
321 else
322 start_angle = 7 * G_PI_4;
323 middle_angle = 0;
324 if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
325 end_angle = G_PI_2;
326 else
327 end_angle = G_PI_4;
328
329 cairo_new_sub_path (cr);
330
331 _cairo_ellipsis (cr,
332 outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
333 outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
334 outer->corner[GSK_CORNER_TOP_RIGHT].width,
335 outer->corner[GSK_CORNER_TOP_RIGHT].height,
336 start_angle, middle_angle);
337 _cairo_ellipsis (cr,
338 outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
339 outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
340 outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
341 outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
342 middle_angle, end_angle);
343
344 _cairo_ellipsis_negative (cr,
345 inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
346 inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
347 inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
348 inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
349 end_angle, middle_angle);
350 _cairo_ellipsis_negative (cr,
351 inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
352 inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
353 inner->corner[GSK_CORNER_TOP_RIGHT].width,
354 inner->corner[GSK_CORNER_TOP_RIGHT].height,
355 middle_angle, start_angle);
356
357 cairo_close_path (cr);
358 }
359
360 void
_gtk_rounded_box_path_bottom(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)361 _gtk_rounded_box_path_bottom (const GskRoundedRect *outer,
362 const GskRoundedRect *inner,
363 cairo_t *cr)
364 {
365 double start_angle, middle_angle, end_angle;
366
367 if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
368 return;
369
370 if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
371 start_angle = 0;
372 else
373 start_angle = G_PI_4;
374 middle_angle = G_PI_2;
375 if (outer->bounds.origin.x == inner->bounds.origin.x)
376 end_angle = G_PI;
377 else
378 end_angle = 3 * G_PI_4;
379
380 cairo_new_sub_path (cr);
381
382 _cairo_ellipsis (cr,
383 outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
384 outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
385 outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
386 outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
387 start_angle, middle_angle);
388 _cairo_ellipsis (cr,
389 outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
390 outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
391 outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
392 outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
393 middle_angle, end_angle);
394
395 _cairo_ellipsis_negative (cr,
396 inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
397 inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
398 inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
399 inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
400 end_angle, middle_angle);
401 _cairo_ellipsis_negative (cr,
402 inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
403 inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
404 inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
405 inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
406 middle_angle, start_angle);
407
408 cairo_close_path (cr);
409 }
410
411 void
_gtk_rounded_box_path_left(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)412 _gtk_rounded_box_path_left (const GskRoundedRect *outer,
413 const GskRoundedRect *inner,
414 cairo_t *cr)
415 {
416 double start_angle, middle_angle, end_angle;
417
418 if (outer->bounds.origin.x == inner->bounds.origin.x)
419 return;
420
421 if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
422 start_angle = G_PI_2;
423 else
424 start_angle = 3 * G_PI_4;
425 middle_angle = G_PI;
426 if (outer->bounds.origin.y == inner->bounds.origin.y)
427 end_angle = 3 * G_PI_2;
428 else
429 end_angle = 5 * G_PI_4;
430
431 cairo_new_sub_path (cr);
432
433 _cairo_ellipsis (cr,
434 outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
435 outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
436 outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
437 outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
438 start_angle, middle_angle);
439 _cairo_ellipsis (cr,
440 outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
441 outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
442 outer->corner[GSK_CORNER_TOP_LEFT].width,
443 outer->corner[GSK_CORNER_TOP_LEFT].height,
444 middle_angle, end_angle);
445
446 _cairo_ellipsis_negative (cr,
447 inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
448 inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
449 inner->corner[GSK_CORNER_TOP_LEFT].width,
450 inner->corner[GSK_CORNER_TOP_LEFT].height,
451 end_angle, middle_angle);
452 _cairo_ellipsis_negative (cr,
453 inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
454 inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
455 inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
456 inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
457 middle_angle, start_angle);
458
459 cairo_close_path (cr);
460 }
461
462