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
30typedef struct {
31 double angle1;
32 double angle2;
33 gboolean negative;
34} Arc;
35
36static inline guint
37mem_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
51static guint
52arc_path_hash (Arc *arc)
53{
54 return mem_hash (v: (gconstpointer)arc, len: sizeof (Arc));
55}
56
57static gboolean
58arc_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 */
67static cairo_path_t *
68fixup_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
79static void
80append_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 (s: &key, c: 0, n: 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 (hash_func: (GHashFunc)arc_path_hash,
93 key_equal_func: (GEqualFunc)arc_path_equal,
94 key_destroy_func: g_free, value_destroy_func: (GDestroyNotify)cairo_path_destroy);
95
96 arc = g_hash_table_lookup (hash_table: arc_path_cache, key: &key);
97 if (arc == NULL)
98 {
99 cairo_surface_t *surface;
100 cairo_t *tmp;
101
102 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width: 1, height: 1);
103 tmp = cairo_create (target: surface);
104
105 if (negative)
106 cairo_arc_negative (cr: tmp, xc: 0.0, yc: 0.0, radius: 1.0, angle1, angle2);
107 else
108 cairo_arc (cr: tmp, xc: 0.0, yc: 0.0, radius: 1.0, angle1, angle2);
109
110 arc = fixup_path (path: cairo_copy_path (cr: tmp));
111 g_hash_table_insert (hash_table: arc_path_cache, key: g_memdup2 (mem: &key, byte_size: sizeof (key)), value: arc);
112
113 cairo_destroy (cr: tmp);
114 cairo_surface_destroy (surface);
115 }
116
117 cairo_append_path (cr, path: arc);
118}
119
120static void
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, x: xc, y: yc);
131 return;
132 }
133
134 cairo_get_matrix (cr, matrix: &save);
135 cairo_translate (cr, tx: xc, ty: yc);
136 cairo_scale (cr, sx: xradius, sy: yradius);
137 append_arc (cr, angle1, angle2, FALSE);
138 cairo_set_matrix (cr, matrix: &save);
139}
140
141static void
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, x: xc, y: yc);
152 return;
153 }
154
155 cairo_get_matrix (cr, matrix: &save);
156 cairo_translate (cr, tx: xc, ty: yc);
157 cairo_scale (cr, sx: xradius, sy: yradius);
158 append_arc (cr, angle1, angle2, TRUE);
159 cairo_set_matrix (cr, matrix: &save);
160}
161
162double
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
189void
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 xc: box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
199 yc: box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
200 xradius: box->corner[GSK_CORNER_TOP_LEFT].width,
201 yradius: box->corner[GSK_CORNER_TOP_LEFT].height,
202 angle1: 5 * G_PI_4, angle2: 3 * G_PI_2);
203 _cairo_ellipsis (cr,
204 xc: box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
205 yc: box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
206 xradius: box->corner[GSK_CORNER_TOP_RIGHT].width,
207 yradius: box->corner[GSK_CORNER_TOP_RIGHT].height,
208 angle1: - G_PI_2, angle2: -G_PI_4);
209 break;
210 case GTK_CSS_RIGHT:
211 _cairo_ellipsis (cr,
212 xc: box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
213 yc: box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
214 xradius: box->corner[GSK_CORNER_TOP_RIGHT].width,
215 yradius: box->corner[GSK_CORNER_TOP_RIGHT].height,
216 angle1: - G_PI_4, angle2: 0);
217 _cairo_ellipsis (cr,
218 xc: box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
219 yc: box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
220 xradius: box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
221 yradius: box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
222 angle1: 0, G_PI_4);
223 break;
224 case GTK_CSS_BOTTOM:
225 _cairo_ellipsis (cr,
226 xc: box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
227 yc: box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
228 xradius: box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
229 yradius: box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
230 G_PI_4, G_PI_2);
231 _cairo_ellipsis (cr,
232 xc: box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
233 yc: box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
234 xradius: box->corner[GSK_CORNER_BOTTOM_LEFT].width,
235 yradius: box->corner[GSK_CORNER_BOTTOM_LEFT].height,
236 G_PI_2, angle2: 3 * G_PI_4);
237 break;
238 case GTK_CSS_LEFT:
239 _cairo_ellipsis (cr,
240 xc: box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
241 yc: box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
242 xradius: box->corner[GSK_CORNER_BOTTOM_LEFT].width,
243 yradius: box->corner[GSK_CORNER_BOTTOM_LEFT].height,
244 angle1: 3 * G_PI_4, G_PI);
245 _cairo_ellipsis (cr,
246 xc: box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
247 yc: box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
248 xradius: box->corner[GSK_CORNER_TOP_LEFT].width,
249 yradius: box->corner[GSK_CORNER_TOP_LEFT].height,
250 G_PI, angle2: 5 * G_PI_4);
251 break;
252 default:
253 g_assert_not_reached ();
254 break;
255 }
256}
257
258void
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 xc: outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
282 yc: outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
283 xradius: outer->corner[GSK_CORNER_TOP_LEFT].width,
284 yradius: outer->corner[GSK_CORNER_TOP_LEFT].height,
285 angle1: start_angle, angle2: middle_angle);
286 _cairo_ellipsis (cr,
287 xc: outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
288 yc: outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
289 xradius: outer->corner[GSK_CORNER_TOP_RIGHT].width,
290 yradius: outer->corner[GSK_CORNER_TOP_RIGHT].height,
291 angle1: middle_angle, angle2: end_angle);
292
293 _cairo_ellipsis_negative (cr,
294 xc: inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
295 yc: inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
296 xradius: inner->corner[GSK_CORNER_TOP_RIGHT].width,
297 yradius: inner->corner[GSK_CORNER_TOP_RIGHT].height,
298 angle1: end_angle, angle2: middle_angle);
299 _cairo_ellipsis_negative (cr,
300 xc: inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
301 yc: inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
302 xradius: inner->corner[GSK_CORNER_TOP_LEFT].width,
303 yradius: inner->corner[GSK_CORNER_TOP_LEFT].height,
304 angle1: middle_angle, angle2: start_angle);
305
306 cairo_close_path (cr);
307}
308
309void
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 xc: outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
333 yc: outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
334 xradius: outer->corner[GSK_CORNER_TOP_RIGHT].width,
335 yradius: outer->corner[GSK_CORNER_TOP_RIGHT].height,
336 angle1: start_angle, angle2: middle_angle);
337 _cairo_ellipsis (cr,
338 xc: outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
339 yc: outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
340 xradius: outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
341 yradius: outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
342 angle1: middle_angle, angle2: end_angle);
343
344 _cairo_ellipsis_negative (cr,
345 xc: inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
346 yc: inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
347 xradius: inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
348 yradius: inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
349 angle1: end_angle, angle2: middle_angle);
350 _cairo_ellipsis_negative (cr,
351 xc: inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
352 yc: inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
353 xradius: inner->corner[GSK_CORNER_TOP_RIGHT].width,
354 yradius: inner->corner[GSK_CORNER_TOP_RIGHT].height,
355 angle1: middle_angle, angle2: start_angle);
356
357 cairo_close_path (cr);
358}
359
360void
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 xc: outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
384 yc: outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
385 xradius: outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
386 yradius: outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
387 angle1: start_angle, angle2: middle_angle);
388 _cairo_ellipsis (cr,
389 xc: outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
390 yc: outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
391 xradius: outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
392 yradius: outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
393 angle1: middle_angle, angle2: end_angle);
394
395 _cairo_ellipsis_negative (cr,
396 xc: inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
397 yc: inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
398 xradius: inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
399 yradius: inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
400 angle1: end_angle, angle2: middle_angle);
401 _cairo_ellipsis_negative (cr,
402 xc: inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
403 yc: inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
404 xradius: inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
405 yradius: inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
406 angle1: middle_angle, angle2: start_angle);
407
408 cairo_close_path (cr);
409}
410
411void
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 xc: outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
435 yc: outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
436 xradius: outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
437 yradius: outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
438 angle1: start_angle, angle2: middle_angle);
439 _cairo_ellipsis (cr,
440 xc: outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
441 yc: outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
442 xradius: outer->corner[GSK_CORNER_TOP_LEFT].width,
443 yradius: outer->corner[GSK_CORNER_TOP_LEFT].height,
444 angle1: middle_angle, angle2: end_angle);
445
446 _cairo_ellipsis_negative (cr,
447 xc: inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
448 yc: inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
449 xradius: inner->corner[GSK_CORNER_TOP_LEFT].width,
450 yradius: inner->corner[GSK_CORNER_TOP_LEFT].height,
451 angle1: end_angle, angle2: middle_angle);
452 _cairo_ellipsis_negative (cr,
453 xc: inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
454 yc: inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
455 xradius: inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
456 yradius: inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
457 angle1: middle_angle, angle2: start_angle);
458
459 cairo_close_path (cr);
460}
461
462

source code of gtk/gtk/gtkroundedbox.c