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 |
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 |
52 | arc_path_hash (Arc *arc) |
53 | { |
54 | return mem_hash (v: (gconstpointer)arc, len: sizeof (Arc)); |
55 | } |
56 | |
57 | static gboolean |
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 * |
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 |
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 (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 | |
120 | static 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 | |
141 | static 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 | |
162 | double |
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 |
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 | |
258 | void |
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 | |
309 | void |
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 | |
360 | void |
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 | |
411 | void |
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 | |