]> Pileus Git - ~andy/gtk/blob - gtk/gtkroundedbox.c
themingengine: Implement 'dotted' and 'dashed'
[~andy/gtk] / gtk / gtkroundedbox.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkroundedboxprivate.h"
23
24 #include <string.h>
25
26 /**
27  * _gtk_rounded_box_init_rect:
28  * @box: box to initialize
29  * @x: x coordinate of box
30  * @y: y coordinate of box
31  * @width: width of box
32  * @height: height of box
33  *
34  * Initializes the given @box to represent the given rectangle.
35  * The
36  **/
37 void
38 _gtk_rounded_box_init_rect (GtkRoundedBox *box,
39                             double         x,
40                             double         y,
41                             double         width,
42                             double         height)
43 {
44   memset (box, 0, sizeof (GtkRoundedBox));
45
46   box->box.x = x;
47   box->box.y = y;
48   box->box.width = width;
49   box->box.height = height;
50 }
51
52 /* clamp border radius, following CSS specs */
53 static void
54 gtk_rounded_box_clamp_border_radius (GtkRoundedBox *box)
55 {
56   gdouble factor = 1.0;
57
58   /* note: division by zero leads to +INF, which is > factor, so will be ignored */
59   factor = MIN (factor, box->box.width / (box->corner[GTK_CSS_TOP_LEFT].horizontal +
60                                           box->corner[GTK_CSS_TOP_RIGHT].horizontal));
61   factor = MIN (factor, box->box.height / (box->corner[GTK_CSS_TOP_RIGHT].vertical +
62                                            box->corner[GTK_CSS_BOTTOM_RIGHT].vertical));
63   factor = MIN (factor, box->box.width / (box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal +
64                                           box->corner[GTK_CSS_BOTTOM_LEFT].horizontal));
65   factor = MIN (factor, box->box.height / (box->corner[GTK_CSS_TOP_LEFT].vertical +
66                                            box->corner[GTK_CSS_BOTTOM_LEFT].vertical));
67
68   box->corner[GTK_CSS_TOP_LEFT].horizontal *= factor;
69   box->corner[GTK_CSS_TOP_LEFT].vertical *= factor;
70   box->corner[GTK_CSS_TOP_RIGHT].horizontal *= factor;
71   box->corner[GTK_CSS_TOP_RIGHT].vertical *= factor;
72   box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal *= factor;
73   box->corner[GTK_CSS_BOTTOM_RIGHT].vertical *= factor;
74   box->corner[GTK_CSS_BOTTOM_LEFT].horizontal *= factor;
75   box->corner[GTK_CSS_BOTTOM_LEFT].vertical *= factor;
76 }
77
78 void
79 _gtk_rounded_box_apply_border_radius (GtkRoundedBox    *box,
80                                       GtkThemingEngine *engine,
81                                       GtkStateFlags     state,
82                                       GtkJunctionSides  junction)
83 {
84   GtkCssBorderCornerRadius *corner[4];
85   guint i;
86
87   gtk_theming_engine_get (engine, state,
88                           /* Can't use border-radius as it's an int for
89                            * backwards compat */
90                           "border-top-left-radius", &corner[GTK_CSS_TOP_LEFT],
91                           "border-top-right-radius", &corner[GTK_CSS_TOP_RIGHT],
92                           "border-bottom-right-radius", &corner[GTK_CSS_BOTTOM_RIGHT],
93                           "border-bottom-left-radius", &corner[GTK_CSS_BOTTOM_LEFT],
94                           NULL);
95
96   if (corner[GTK_CSS_TOP_LEFT] && (junction & GTK_JUNCTION_CORNER_TOPLEFT) == 0)
97     box->corner[GTK_CSS_TOP_LEFT] = *corner[GTK_CSS_TOP_LEFT];
98   if (corner[GTK_CSS_TOP_RIGHT] && (junction & GTK_JUNCTION_CORNER_TOPRIGHT) == 0)
99     box->corner[GTK_CSS_TOP_RIGHT] = *corner[GTK_CSS_TOP_RIGHT];
100   if (corner[GTK_CSS_BOTTOM_RIGHT] && (junction & GTK_JUNCTION_CORNER_BOTTOMRIGHT) == 0)
101     box->corner[GTK_CSS_BOTTOM_RIGHT] = *corner[GTK_CSS_BOTTOM_RIGHT];
102   if (corner[GTK_CSS_BOTTOM_LEFT] && (junction & GTK_JUNCTION_CORNER_BOTTOMLEFT) == 0)
103     box->corner[GTK_CSS_BOTTOM_LEFT] = *corner[GTK_CSS_BOTTOM_LEFT];
104
105   gtk_rounded_box_clamp_border_radius (box);
106
107   for (i = 0; i < 4; i++)
108     g_free (corner[i]);
109 }
110
111 static void
112 gtk_css_border_radius_grow (GtkCssBorderCornerRadius *corner,
113                             double                    horizontal,
114                             double                    vertical)
115 {
116   corner->horizontal += horizontal;
117   corner->vertical += vertical;
118
119   if (corner->horizontal <= 0 || corner->vertical <= 0)
120     {
121       corner->horizontal = 0;
122       corner->vertical = 0;
123     }
124 }
125 void
126 _gtk_rounded_box_grow (GtkRoundedBox *box,
127                        double         top,
128                        double         right,
129                        double         bottom,
130                        double         left)
131 {
132   if (box->box.width + left + right < 0)
133     {
134       box->box.x -= left * box->box.width / (left + right);
135       box->box.width = 0;
136     }
137   else
138     {
139       box->box.x -= left;
140       box->box.width += left + right;
141     }
142
143   if (box->box.height + bottom + right < 0)
144     {
145       box->box.y -= top * box->box.height / (top + bottom);
146       box->box.height = 0;
147     }
148   else
149     {
150       box->box.y -= top;
151       box->box.height += top + bottom;
152     }
153
154   gtk_css_border_radius_grow (&box->corner[GTK_CSS_TOP_LEFT], left, top);
155   gtk_css_border_radius_grow (&box->corner[GTK_CSS_TOP_RIGHT], right, bottom);
156   gtk_css_border_radius_grow (&box->corner[GTK_CSS_BOTTOM_RIGHT], right, top);
157   gtk_css_border_radius_grow (&box->corner[GTK_CSS_BOTTOM_LEFT], left, bottom);
158 }
159
160 void
161 _gtk_rounded_box_shrink (GtkRoundedBox *box,
162                          double         top,
163                          double         right,
164                          double         bottom,
165                          double         left)
166 {
167   _gtk_rounded_box_grow (box, -top, -right, -bottom, -left);
168 }
169
170 void
171 _gtk_rounded_box_move (GtkRoundedBox *box,
172                        double         dx,
173                        double         dy)
174 {
175   box->box.x += dx;
176   box->box.y += dy;
177 }
178
179 static void
180 _cairo_ellipsis (cairo_t *cr,
181                  double xc, double yc,
182                  double xradius, double yradius,
183                  double angle1, double angle2)
184 {
185   if (xradius <= 0.0 || yradius <= 0.0)
186     {
187       cairo_line_to (cr, xc, yc);
188       return;
189     }
190
191   cairo_save (cr);
192   cairo_translate (cr, xc, yc);
193   cairo_scale (cr, xradius, yradius);
194   cairo_arc (cr, 0, 0, 1.0, angle1, angle2);
195   cairo_restore (cr);
196 }
197
198 static void
199 _cairo_ellipsis_negative (cairo_t *cr,
200                           double xc, double yc,
201                           double xradius, double yradius,
202                           double angle1, double angle2)
203 {
204   if (xradius <= 0.0 || yradius <= 0.0)
205     {
206       cairo_line_to (cr, xc, yc);
207       return;
208     }
209
210   cairo_save (cr);
211   cairo_translate (cr, xc, yc);
212   cairo_scale (cr, xradius, yradius);
213   cairo_arc_negative (cr, 0, 0, 1.0, angle1, angle2);
214   cairo_restore (cr);
215 }
216
217 void
218 _gtk_rounded_box_path (const GtkRoundedBox *box,
219                        cairo_t             *cr)
220 {
221   cairo_new_sub_path (cr);
222
223   _cairo_ellipsis (cr,
224                    box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal,
225                    box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical,
226                    box->corner[GTK_CSS_TOP_LEFT].horizontal,
227                    box->corner[GTK_CSS_TOP_LEFT].vertical,
228                    G_PI, 3 * G_PI / 2);
229   _cairo_ellipsis (cr, 
230                    box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal,
231                    box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical,
232                    box->corner[GTK_CSS_TOP_RIGHT].horizontal,
233                    box->corner[GTK_CSS_TOP_RIGHT].vertical,
234                    - G_PI / 2, 0);
235   _cairo_ellipsis (cr,
236                    box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
237                    box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
238                    box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
239                    box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
240                    0, G_PI / 2);
241   _cairo_ellipsis (cr,
242                    box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
243                    box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
244                    box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
245                    box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
246                    G_PI / 2, G_PI);
247 }
248
249 double
250 _gtk_rounded_box_guess_length (const GtkRoundedBox *box,
251                                GtkCssSide           side)
252 {
253   double length;
254   GtkCssCorner before, after;
255
256   before = side;
257   after = (side + 1) % 4;
258
259   if (side & 1)
260     length = box->box.height
261              - box->corner[before].vertical
262              - box->corner[after].vertical;
263   else
264     length = box->box.width
265              - box->corner[before].horizontal
266              - box->corner[after].horizontal;
267
268   length += G_PI * 0.125 * (box->corner[before].horizontal
269                             + box->corner[before].vertical
270                             + box->corner[after].horizontal
271                             + box->corner[after].vertical);
272
273   return length;
274 }
275
276 void
277 _gtk_rounded_box_path_side (const GtkRoundedBox *box,
278                             cairo_t             *cr,
279                             GtkCssSide           side)
280 {
281   switch (side)
282     {
283     case GTK_CSS_TOP:
284       _cairo_ellipsis (cr,
285                        box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal,
286                        box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical,
287                        box->corner[GTK_CSS_TOP_LEFT].horizontal,
288                        box->corner[GTK_CSS_TOP_LEFT].vertical,
289                        5 * G_PI / 4, 3 * G_PI / 2);
290       _cairo_ellipsis (cr, 
291                        box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal,
292                        box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical,
293                        box->corner[GTK_CSS_TOP_RIGHT].horizontal,
294                        box->corner[GTK_CSS_TOP_RIGHT].vertical,
295                        - G_PI / 2, -G_PI / 4);
296       break;
297     case GTK_CSS_RIGHT:
298       _cairo_ellipsis (cr, 
299                        box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal,
300                        box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical,
301                        box->corner[GTK_CSS_TOP_RIGHT].horizontal,
302                        box->corner[GTK_CSS_TOP_RIGHT].vertical,
303                        - G_PI / 4, 0);
304       _cairo_ellipsis (cr,
305                        box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
306                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
307                        box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
308                        box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
309                        0, G_PI / 4);
310       break;
311     case GTK_CSS_BOTTOM:
312       _cairo_ellipsis (cr,
313                        box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
314                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
315                        box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
316                        box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
317                        G_PI / 4, G_PI / 2);
318       _cairo_ellipsis (cr,
319                        box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
320                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
321                        box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
322                        box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
323                        G_PI / 2, 3 * G_PI / 4);
324       break;
325     case GTK_CSS_LEFT:
326       _cairo_ellipsis (cr,
327                        box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
328                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
329                        box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
330                        box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
331                        3 * G_PI / 4, G_PI);
332       _cairo_ellipsis (cr,
333                        box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal,
334                        box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical,
335                        box->corner[GTK_CSS_TOP_LEFT].horizontal,
336                        box->corner[GTK_CSS_TOP_LEFT].vertical,
337                        G_PI, 5 * G_PI / 4);
338       break;
339     default:
340       g_assert_not_reached ();
341       break;
342     }
343 }
344
345 void
346 _gtk_rounded_box_path_top (const GtkRoundedBox *outer,
347                            const GtkRoundedBox *inner,
348                            cairo_t             *cr)
349 {
350   cairo_new_sub_path (cr);
351
352   _cairo_ellipsis (cr,
353                    outer->box.x + outer->corner[GTK_CSS_TOP_LEFT].horizontal,
354                    outer->box.y + outer->corner[GTK_CSS_TOP_LEFT].vertical,
355                    outer->corner[GTK_CSS_TOP_LEFT].horizontal,
356                    outer->corner[GTK_CSS_TOP_LEFT].vertical,
357                    5 * G_PI / 4, 3 * G_PI / 2);
358   _cairo_ellipsis (cr, 
359                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
360                    outer->box.y + outer->corner[GTK_CSS_TOP_RIGHT].vertical,
361                    outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
362                    outer->corner[GTK_CSS_TOP_RIGHT].vertical,
363                    - G_PI / 2, -G_PI / 4);
364
365   _cairo_ellipsis_negative (cr, 
366                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
367                             inner->box.y + inner->corner[GTK_CSS_TOP_RIGHT].vertical,
368                             inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
369                             inner->corner[GTK_CSS_TOP_RIGHT].vertical,
370                             -G_PI / 4, - G_PI / 2);
371   _cairo_ellipsis_negative (cr,
372                             inner->box.x + inner->corner[GTK_CSS_TOP_LEFT].horizontal,
373                             inner->box.y + inner->corner[GTK_CSS_TOP_LEFT].vertical,
374                             inner->corner[GTK_CSS_TOP_LEFT].horizontal,
375                             inner->corner[GTK_CSS_TOP_LEFT].vertical,
376                             3 * G_PI / 2, 5 * G_PI / 4);
377
378   cairo_close_path (cr);
379 }
380
381 void
382 _gtk_rounded_box_path_right (const GtkRoundedBox *outer,
383                              const GtkRoundedBox *inner,
384                              cairo_t             *cr)
385 {
386   cairo_new_sub_path (cr);
387
388   _cairo_ellipsis (cr, 
389                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
390                    outer->box.y + outer->corner[GTK_CSS_TOP_RIGHT].vertical,
391                    outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
392                    outer->corner[GTK_CSS_TOP_RIGHT].vertical,
393                    - G_PI / 4, 0);
394   _cairo_ellipsis (cr,
395                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
396                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
397                    outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
398                    outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
399                    0, G_PI / 4);
400
401   _cairo_ellipsis_negative (cr,
402                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
403                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
404                             inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
405                             inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
406                             G_PI / 4, 0);
407   _cairo_ellipsis_negative (cr, 
408                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
409                             inner->box.y + inner->corner[GTK_CSS_TOP_RIGHT].vertical,
410                             inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
411                             inner->corner[GTK_CSS_TOP_RIGHT].vertical,
412                             0, - G_PI / 4);
413
414   cairo_close_path (cr);
415 }
416
417 void
418 _gtk_rounded_box_path_bottom (const GtkRoundedBox *outer,
419                               const GtkRoundedBox *inner,
420                               cairo_t             *cr)
421 {
422   cairo_new_sub_path (cr);
423
424   _cairo_ellipsis (cr,
425                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
426                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
427                    outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
428                    outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
429                    G_PI / 4, G_PI / 2);
430   _cairo_ellipsis (cr,
431                    outer->box.x + outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
432                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
433                    outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
434                    outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
435                    G_PI / 2, 3 * G_PI / 4);
436
437   _cairo_ellipsis_negative (cr,
438                             inner->box.x + inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
439                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
440                             inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
441                             inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
442                             3 * G_PI / 4, G_PI / 2);
443   _cairo_ellipsis_negative (cr,
444                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
445                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
446                             inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
447                             inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
448                             G_PI / 2, G_PI / 4);
449
450   cairo_close_path (cr);
451 }
452
453 void
454 _gtk_rounded_box_path_left (const GtkRoundedBox *outer,
455                             const GtkRoundedBox *inner,
456                             cairo_t             *cr)
457 {
458   cairo_new_sub_path (cr);
459
460   _cairo_ellipsis (cr,
461                    outer->box.x + outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
462                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
463                    outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
464                    outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
465                    3 * G_PI / 4, G_PI);
466   _cairo_ellipsis (cr,
467                    outer->box.x + outer->corner[GTK_CSS_TOP_LEFT].horizontal,
468                    outer->box.y + outer->corner[GTK_CSS_TOP_LEFT].vertical,
469                    outer->corner[GTK_CSS_TOP_LEFT].horizontal,
470                    outer->corner[GTK_CSS_TOP_LEFT].vertical,
471                    G_PI, 5 * G_PI / 4);
472
473   _cairo_ellipsis_negative (cr,
474                             inner->box.x + inner->corner[GTK_CSS_TOP_LEFT].horizontal,
475                             inner->box.y + inner->corner[GTK_CSS_TOP_LEFT].vertical,
476                             inner->corner[GTK_CSS_TOP_LEFT].horizontal,
477                             inner->corner[GTK_CSS_TOP_LEFT].vertical,
478                             5 * G_PI / 4, G_PI);
479   _cairo_ellipsis_negative (cr,
480                             inner->box.x + inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
481                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
482                             inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
483                             inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
484                             G_PI, 3 * G_PI / 4);
485
486   cairo_close_path (cr);
487 }
488
489 void
490 _gtk_rounded_box_clip_path (const GtkRoundedBox *box,
491                             cairo_t             *cr)
492 {
493   cairo_rectangle (cr,
494                    box->box.x, box->box.y,
495                    box->box.width, box->box.height);
496 }
497