]> Pileus Git - ~andy/gtk/blob - gtk/gtkroundedbox.c
roundedbox: close path
[~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   cairo_close_path (cr);
249 }
250
251 double
252 _gtk_rounded_box_guess_length (const GtkRoundedBox *box,
253                                GtkCssSide           side)
254 {
255   double length;
256   GtkCssCorner before, after;
257
258   before = side;
259   after = (side + 1) % 4;
260
261   if (side & 1)
262     length = box->box.height
263              - box->corner[before].vertical
264              - box->corner[after].vertical;
265   else
266     length = box->box.width
267              - box->corner[before].horizontal
268              - box->corner[after].horizontal;
269
270   length += G_PI * 0.125 * (box->corner[before].horizontal
271                             + box->corner[before].vertical
272                             + box->corner[after].horizontal
273                             + box->corner[after].vertical);
274
275   return length;
276 }
277
278 void
279 _gtk_rounded_box_path_side (const GtkRoundedBox *box,
280                             cairo_t             *cr,
281                             GtkCssSide           side)
282 {
283   switch (side)
284     {
285     case GTK_CSS_TOP:
286       _cairo_ellipsis (cr,
287                        box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal,
288                        box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical,
289                        box->corner[GTK_CSS_TOP_LEFT].horizontal,
290                        box->corner[GTK_CSS_TOP_LEFT].vertical,
291                        5 * G_PI / 4, 3 * G_PI / 2);
292       _cairo_ellipsis (cr, 
293                        box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal,
294                        box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical,
295                        box->corner[GTK_CSS_TOP_RIGHT].horizontal,
296                        box->corner[GTK_CSS_TOP_RIGHT].vertical,
297                        - G_PI / 2, -G_PI / 4);
298       break;
299     case GTK_CSS_RIGHT:
300       _cairo_ellipsis (cr, 
301                        box->box.x + box->box.width - box->corner[GTK_CSS_TOP_RIGHT].horizontal,
302                        box->box.y + box->corner[GTK_CSS_TOP_RIGHT].vertical,
303                        box->corner[GTK_CSS_TOP_RIGHT].horizontal,
304                        box->corner[GTK_CSS_TOP_RIGHT].vertical,
305                        - G_PI / 4, 0);
306       _cairo_ellipsis (cr,
307                        box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
308                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
309                        box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
310                        box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
311                        0, G_PI / 4);
312       break;
313     case GTK_CSS_BOTTOM:
314       _cairo_ellipsis (cr,
315                        box->box.x + box->box.width - box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
316                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
317                        box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
318                        box->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
319                        G_PI / 4, G_PI / 2);
320       _cairo_ellipsis (cr,
321                        box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
322                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
323                        box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
324                        box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
325                        G_PI / 2, 3 * G_PI / 4);
326       break;
327     case GTK_CSS_LEFT:
328       _cairo_ellipsis (cr,
329                        box->box.x + box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
330                        box->box.y + box->box.height - box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
331                        box->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
332                        box->corner[GTK_CSS_BOTTOM_LEFT].vertical,
333                        3 * G_PI / 4, G_PI);
334       _cairo_ellipsis (cr,
335                        box->box.x + box->corner[GTK_CSS_TOP_LEFT].horizontal,
336                        box->box.y + box->corner[GTK_CSS_TOP_LEFT].vertical,
337                        box->corner[GTK_CSS_TOP_LEFT].horizontal,
338                        box->corner[GTK_CSS_TOP_LEFT].vertical,
339                        G_PI, 5 * G_PI / 4);
340       break;
341     default:
342       g_assert_not_reached ();
343       break;
344     }
345 }
346
347 void
348 _gtk_rounded_box_path_top (const GtkRoundedBox *outer,
349                            const GtkRoundedBox *inner,
350                            cairo_t             *cr)
351 {
352   cairo_new_sub_path (cr);
353
354   _cairo_ellipsis (cr,
355                    outer->box.x + outer->corner[GTK_CSS_TOP_LEFT].horizontal,
356                    outer->box.y + outer->corner[GTK_CSS_TOP_LEFT].vertical,
357                    outer->corner[GTK_CSS_TOP_LEFT].horizontal,
358                    outer->corner[GTK_CSS_TOP_LEFT].vertical,
359                    5 * G_PI / 4, 3 * G_PI / 2);
360   _cairo_ellipsis (cr, 
361                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
362                    outer->box.y + outer->corner[GTK_CSS_TOP_RIGHT].vertical,
363                    outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
364                    outer->corner[GTK_CSS_TOP_RIGHT].vertical,
365                    - G_PI / 2, -G_PI / 4);
366
367   _cairo_ellipsis_negative (cr, 
368                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
369                             inner->box.y + inner->corner[GTK_CSS_TOP_RIGHT].vertical,
370                             inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
371                             inner->corner[GTK_CSS_TOP_RIGHT].vertical,
372                             -G_PI / 4, - G_PI / 2);
373   _cairo_ellipsis_negative (cr,
374                             inner->box.x + inner->corner[GTK_CSS_TOP_LEFT].horizontal,
375                             inner->box.y + inner->corner[GTK_CSS_TOP_LEFT].vertical,
376                             inner->corner[GTK_CSS_TOP_LEFT].horizontal,
377                             inner->corner[GTK_CSS_TOP_LEFT].vertical,
378                             3 * G_PI / 2, 5 * G_PI / 4);
379
380   cairo_close_path (cr);
381 }
382
383 void
384 _gtk_rounded_box_path_right (const GtkRoundedBox *outer,
385                              const GtkRoundedBox *inner,
386                              cairo_t             *cr)
387 {
388   cairo_new_sub_path (cr);
389
390   _cairo_ellipsis (cr, 
391                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
392                    outer->box.y + outer->corner[GTK_CSS_TOP_RIGHT].vertical,
393                    outer->corner[GTK_CSS_TOP_RIGHT].horizontal,
394                    outer->corner[GTK_CSS_TOP_RIGHT].vertical,
395                    - G_PI / 4, 0);
396   _cairo_ellipsis (cr,
397                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
398                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
399                    outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
400                    outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
401                    0, G_PI / 4);
402
403   _cairo_ellipsis_negative (cr,
404                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
405                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
406                             inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
407                             inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
408                             G_PI / 4, 0);
409   _cairo_ellipsis_negative (cr, 
410                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
411                             inner->box.y + inner->corner[GTK_CSS_TOP_RIGHT].vertical,
412                             inner->corner[GTK_CSS_TOP_RIGHT].horizontal,
413                             inner->corner[GTK_CSS_TOP_RIGHT].vertical,
414                             0, - G_PI / 4);
415
416   cairo_close_path (cr);
417 }
418
419 void
420 _gtk_rounded_box_path_bottom (const GtkRoundedBox *outer,
421                               const GtkRoundedBox *inner,
422                               cairo_t             *cr)
423 {
424   cairo_new_sub_path (cr);
425
426   _cairo_ellipsis (cr,
427                    outer->box.x + outer->box.width - outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
428                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
429                    outer->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
430                    outer->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
431                    G_PI / 4, G_PI / 2);
432   _cairo_ellipsis (cr,
433                    outer->box.x + outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
434                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
435                    outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
436                    outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
437                    G_PI / 2, 3 * G_PI / 4);
438
439   _cairo_ellipsis_negative (cr,
440                             inner->box.x + inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
441                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
442                             inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
443                             inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
444                             3 * G_PI / 4, G_PI / 2);
445   _cairo_ellipsis_negative (cr,
446                             inner->box.x + inner->box.width - inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
447                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
448                             inner->corner[GTK_CSS_BOTTOM_RIGHT].horizontal,
449                             inner->corner[GTK_CSS_BOTTOM_RIGHT].vertical,
450                             G_PI / 2, G_PI / 4);
451
452   cairo_close_path (cr);
453 }
454
455 void
456 _gtk_rounded_box_path_left (const GtkRoundedBox *outer,
457                             const GtkRoundedBox *inner,
458                             cairo_t             *cr)
459 {
460   cairo_new_sub_path (cr);
461
462   _cairo_ellipsis (cr,
463                    outer->box.x + outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
464                    outer->box.y + outer->box.height - outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
465                    outer->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
466                    outer->corner[GTK_CSS_BOTTOM_LEFT].vertical,
467                    3 * G_PI / 4, G_PI);
468   _cairo_ellipsis (cr,
469                    outer->box.x + outer->corner[GTK_CSS_TOP_LEFT].horizontal,
470                    outer->box.y + outer->corner[GTK_CSS_TOP_LEFT].vertical,
471                    outer->corner[GTK_CSS_TOP_LEFT].horizontal,
472                    outer->corner[GTK_CSS_TOP_LEFT].vertical,
473                    G_PI, 5 * G_PI / 4);
474
475   _cairo_ellipsis_negative (cr,
476                             inner->box.x + inner->corner[GTK_CSS_TOP_LEFT].horizontal,
477                             inner->box.y + inner->corner[GTK_CSS_TOP_LEFT].vertical,
478                             inner->corner[GTK_CSS_TOP_LEFT].horizontal,
479                             inner->corner[GTK_CSS_TOP_LEFT].vertical,
480                             5 * G_PI / 4, G_PI);
481   _cairo_ellipsis_negative (cr,
482                             inner->box.x + inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
483                             inner->box.y + inner->box.height - inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
484                             inner->corner[GTK_CSS_BOTTOM_LEFT].horizontal,
485                             inner->corner[GTK_CSS_BOTTOM_LEFT].vertical,
486                             G_PI, 3 * G_PI / 4);
487
488   cairo_close_path (cr);
489 }
490
491 void
492 _gtk_rounded_box_clip_path (const GtkRoundedBox *box,
493                             cairo_t             *cr)
494 {
495   cairo_rectangle (cr,
496                    box->box.x, box->box.y,
497                    box->box.width, box->box.height);
498 }
499