1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2019 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#ifndef __GTK_CSS_BOXES_IMPL_PRIVATE_H__
19#define __GTK_CSS_BOXES_IMPL_PRIVATE_H__
20
21#include "gtkcssboxesprivate.h"
22
23#include "gtkcsscornervalueprivate.h"
24#include "gtkcssnodeprivate.h"
25#include "gtkcssnumbervalueprivate.h"
26#include "gtkcssdimensionvalueprivate.h"
27#include "gtkwidgetprivate.h"
28
29/* This file is included from gtkcssboxesprivate.h */
30
31static inline void
32gtk_css_boxes_init (GtkCssBoxes *boxes,
33 GtkWidget *widget)
34{
35 GtkWidgetPrivate *priv = widget->priv;
36
37 gtk_css_boxes_init_content_box (boxes,
38 style: gtk_css_node_get_style (cssnode: priv->cssnode),
39 x: 0, y: 0,
40 width: priv->width,
41 height: priv->height);
42}
43
44static inline void
45gtk_css_boxes_init_content_box (GtkCssBoxes *boxes,
46 GtkCssStyle *style,
47 double x,
48 double y,
49 double width,
50 double height)
51{
52 memset (s: boxes, c: 0, n: sizeof (GtkCssBoxes));
53
54 boxes->style = style;
55 boxes->box[GTK_CSS_AREA_CONTENT_BOX].bounds = GRAPHENE_RECT_INIT (x, y, width, height);
56 boxes->has_rect[GTK_CSS_AREA_CONTENT_BOX] = TRUE;
57}
58
59static inline void
60gtk_css_boxes_init_border_box (GtkCssBoxes *boxes,
61 GtkCssStyle *style,
62 double x,
63 double y,
64 double width,
65 double height)
66{
67 memset (s: boxes, c: 0, n: sizeof (GtkCssBoxes));
68
69 boxes->style = style;
70 boxes->box[GTK_CSS_AREA_BORDER_BOX].bounds = GRAPHENE_RECT_INIT (x, y, width, height);
71 boxes->has_rect[GTK_CSS_AREA_BORDER_BOX] = TRUE;
72}
73
74static inline void
75gtk_css_boxes_rect_grow (GskRoundedRect *dest,
76 GskRoundedRect *src,
77 GtkCssValue *top,
78 GtkCssValue *right,
79 GtkCssValue *bottom,
80 GtkCssValue *left)
81{
82 if (gtk_css_dimension_value_is_zero (value: left))
83 {
84 dest->bounds.origin.x = src->bounds.origin.x;
85 if (gtk_css_dimension_value_is_zero (value: right))
86 dest->bounds.size.width = src->bounds.size.width;
87 else
88 dest->bounds.size.width = src->bounds.size.width + _gtk_css_number_value_get (number: right, one_hundred_percent: 100);
89 }
90 else
91 {
92 const double left_value = _gtk_css_number_value_get (number: left, one_hundred_percent: 100);
93
94 dest->bounds.origin.x = src->bounds.origin.x - left_value;
95 if (gtk_css_dimension_value_is_zero (value: right))
96 dest->bounds.size.width = src->bounds.size.width + left_value;
97 else
98 dest->bounds.size.width = src->bounds.size.width + left_value + _gtk_css_number_value_get (number: right, one_hundred_percent: 100);
99
100 }
101
102
103 if (gtk_css_dimension_value_is_zero (value: top))
104 {
105 dest->bounds.origin.y = src->bounds.origin.y;
106 if (gtk_css_dimension_value_is_zero (value: bottom))
107 dest->bounds.size.height = src->bounds.size.height;
108 else
109 dest->bounds.size.height = src->bounds.size.height + _gtk_css_number_value_get (number: bottom, one_hundred_percent: 100);
110 }
111 else
112 {
113 const double top_value = _gtk_css_number_value_get (number: top, one_hundred_percent: 100);
114
115 dest->bounds.origin.y = src->bounds.origin.y - top_value;
116 if (gtk_css_dimension_value_is_zero (value: bottom))
117 dest->bounds.size.height = src->bounds.size.height + top_value;
118 else
119 dest->bounds.size.height = src->bounds.size.height + top_value + _gtk_css_number_value_get (number: bottom, one_hundred_percent: 100);
120 }
121}
122
123static inline void
124gtk_css_boxes_rect_shrink (GskRoundedRect *dest,
125 GskRoundedRect *src,
126 GtkCssValue *top_value,
127 GtkCssValue *right_value,
128 GtkCssValue *bottom_value,
129 GtkCssValue *left_value)
130{
131 double top = _gtk_css_number_value_get (number: top_value, one_hundred_percent: 100);
132 double right = _gtk_css_number_value_get (number: right_value, one_hundred_percent: 100);
133 double bottom = _gtk_css_number_value_get (number: bottom_value, one_hundred_percent: 100);
134 double left = _gtk_css_number_value_get (number: left_value, one_hundred_percent: 100);
135
136 /* FIXME: Do we need underflow checks here? */
137 dest->bounds.origin.x = src->bounds.origin.x + left;
138 dest->bounds.origin.y = src->bounds.origin.y + top;
139 dest->bounds.size.width = src->bounds.size.width - left - right;
140 dest->bounds.size.height = src->bounds.size.height - top - bottom;
141}
142
143static inline void gtk_css_boxes_compute_padding_rect (GtkCssBoxes *boxes);
144
145static inline const graphene_rect_t *
146gtk_css_boxes_get_rect (GtkCssBoxes *boxes,
147 GtkCssArea area)
148{
149 switch (area)
150 {
151 case GTK_CSS_AREA_BORDER_BOX:
152 return gtk_css_boxes_get_border_rect (boxes);
153 case GTK_CSS_AREA_PADDING_BOX:
154 return gtk_css_boxes_get_padding_rect (boxes);
155 case GTK_CSS_AREA_CONTENT_BOX:
156 return gtk_css_boxes_get_content_rect (boxes);
157 default:
158 g_assert_not_reached ();
159 return NULL;
160 }
161}
162
163static inline void
164gtk_css_boxes_compute_border_rect (GtkCssBoxes *boxes)
165{
166 if (boxes->has_rect[GTK_CSS_AREA_BORDER_BOX])
167 return;
168
169 gtk_css_boxes_compute_padding_rect (boxes);
170
171 gtk_css_boxes_rect_grow (dest: &boxes->box[GTK_CSS_AREA_BORDER_BOX],
172 src: &boxes->box[GTK_CSS_AREA_PADDING_BOX],
173 top: boxes->style->border->border_top_width,
174 right: boxes->style->border->border_right_width,
175 bottom: boxes->style->border->border_bottom_width,
176 left: boxes->style->border->border_left_width);
177
178 boxes->has_rect[GTK_CSS_AREA_BORDER_BOX] = TRUE;
179}
180
181static inline void
182gtk_css_boxes_compute_padding_rect (GtkCssBoxes *boxes)
183{
184 if (boxes->has_rect[GTK_CSS_AREA_PADDING_BOX])
185 return;
186
187 if (boxes->has_rect[GTK_CSS_AREA_BORDER_BOX])
188 {
189 gtk_css_boxes_rect_shrink (dest: &boxes->box[GTK_CSS_AREA_PADDING_BOX],
190 src: &boxes->box[GTK_CSS_AREA_BORDER_BOX],
191 top_value: boxes->style->border->border_top_width,
192 right_value: boxes->style->border->border_right_width,
193 bottom_value: boxes->style->border->border_bottom_width,
194 left_value: boxes->style->border->border_left_width);
195 }
196 else
197 {
198 gtk_css_boxes_rect_grow (dest: &boxes->box[GTK_CSS_AREA_PADDING_BOX],
199 src: &boxes->box[GTK_CSS_AREA_CONTENT_BOX],
200 top: boxes->style->size->padding_top,
201 right: boxes->style->size->padding_right,
202 bottom: boxes->style->size->padding_bottom,
203 left: boxes->style->size->padding_left);
204 }
205
206 boxes->has_rect[GTK_CSS_AREA_PADDING_BOX] = TRUE;
207}
208
209static inline void
210gtk_css_boxes_compute_content_rect (GtkCssBoxes *boxes)
211{
212 if (boxes->has_rect[GTK_CSS_AREA_CONTENT_BOX])
213 return;
214
215 gtk_css_boxes_compute_padding_rect (boxes);
216
217 gtk_css_boxes_rect_shrink (dest: &boxes->box[GTK_CSS_AREA_CONTENT_BOX],
218 src: &boxes->box[GTK_CSS_AREA_PADDING_BOX],
219 top_value: boxes->style->size->padding_top,
220 right_value: boxes->style->size->padding_right,
221 bottom_value: boxes->style->size->padding_bottom,
222 left_value: boxes->style->size->padding_left);
223
224 boxes->has_rect[GTK_CSS_AREA_CONTENT_BOX] = TRUE;
225}
226
227static inline void
228gtk_css_boxes_compute_margin_rect (GtkCssBoxes *boxes)
229{
230 if (boxes->has_rect[GTK_CSS_AREA_MARGIN_BOX])
231 return;
232
233 gtk_css_boxes_compute_border_rect (boxes);
234
235 gtk_css_boxes_rect_grow (dest: &boxes->box[GTK_CSS_AREA_MARGIN_BOX],
236 src: &boxes->box[GTK_CSS_AREA_BORDER_BOX],
237 top: boxes->style->size->margin_top,
238 right: boxes->style->size->margin_right,
239 bottom: boxes->style->size->margin_bottom,
240 left: boxes->style->size->margin_left);
241
242 boxes->has_rect[GTK_CSS_AREA_MARGIN_BOX] = TRUE;
243}
244
245static inline void
246gtk_css_boxes_compute_outline_rect (GtkCssBoxes *boxes)
247{
248 graphene_rect_t *dest, *src;
249 double d;
250
251 if (boxes->has_rect[GTK_CSS_AREA_OUTLINE_BOX])
252 return;
253
254 gtk_css_boxes_compute_border_rect (boxes);
255
256 dest = &boxes->box[GTK_CSS_AREA_OUTLINE_BOX].bounds;
257 src = &boxes->box[GTK_CSS_AREA_BORDER_BOX].bounds;
258
259 d = _gtk_css_number_value_get (number: boxes->style->outline->outline_offset, one_hundred_percent: 100) +
260 _gtk_css_number_value_get (number: boxes->style->outline->outline_width, one_hundred_percent: 100);
261
262 dest->origin.x = src->origin.x - d;
263 dest->origin.y = src->origin.y - d;
264 dest->size.width = src->size.width + d + d;
265 dest->size.height = src->size.height + d + d;
266
267 boxes->has_rect[GTK_CSS_AREA_OUTLINE_BOX] = TRUE;
268}
269
270static inline const graphene_rect_t *
271gtk_css_boxes_get_margin_rect (GtkCssBoxes *boxes)
272{
273 gtk_css_boxes_compute_margin_rect (boxes);
274
275 return &boxes->box[GTK_CSS_AREA_MARGIN_BOX].bounds;
276}
277
278static inline const graphene_rect_t *
279gtk_css_boxes_get_border_rect (GtkCssBoxes *boxes)
280{
281 gtk_css_boxes_compute_border_rect (boxes);
282
283 return &boxes->box[GTK_CSS_AREA_BORDER_BOX].bounds;
284}
285
286static inline const graphene_rect_t *
287gtk_css_boxes_get_padding_rect (GtkCssBoxes *boxes)
288{
289 gtk_css_boxes_compute_padding_rect (boxes);
290
291 return &boxes->box[GTK_CSS_AREA_PADDING_BOX].bounds;
292}
293
294static inline const graphene_rect_t *
295gtk_css_boxes_get_content_rect (GtkCssBoxes *boxes)
296{
297 gtk_css_boxes_compute_content_rect (boxes);
298
299 return &boxes->box[GTK_CSS_AREA_CONTENT_BOX].bounds;
300}
301
302static inline const graphene_rect_t *
303gtk_css_boxes_get_outline_rect (GtkCssBoxes *boxes)
304{
305 gtk_css_boxes_compute_outline_rect (boxes);
306
307 return &boxes->box[GTK_CSS_AREA_OUTLINE_BOX].bounds;
308}
309
310/* clamp border radius, following CSS specs */
311static inline void
312gtk_css_boxes_clamp_border_radius (GskRoundedRect *box)
313{
314 double factor = 1.0;
315 double corners;
316
317 corners = box->corner[GSK_CORNER_TOP_LEFT].width + box->corner[GSK_CORNER_TOP_RIGHT].width;
318 if (corners != 0)
319 factor = MIN (factor, box->bounds.size.width / corners);
320
321 corners = box->corner[GSK_CORNER_TOP_RIGHT].height + box->corner[GSK_CORNER_BOTTOM_RIGHT].height;
322 if (corners != 0)
323 factor = MIN (factor, box->bounds.size.height / corners);
324
325 corners = box->corner[GSK_CORNER_BOTTOM_RIGHT].width + box->corner[GSK_CORNER_BOTTOM_LEFT].width;
326 if (corners != 0)
327 factor = MIN (factor, box->bounds.size.width / corners);
328
329 corners = box->corner[GSK_CORNER_TOP_LEFT].height + box->corner[GSK_CORNER_BOTTOM_LEFT].height;
330 if (corners != 0)
331 factor = MIN (factor, box->bounds.size.height / corners);
332
333 box->corner[GSK_CORNER_TOP_LEFT].width *= factor;
334 box->corner[GSK_CORNER_TOP_LEFT].height *= factor;
335 box->corner[GSK_CORNER_TOP_RIGHT].width *= factor;
336 box->corner[GSK_CORNER_TOP_RIGHT].height *= factor;
337 box->corner[GSK_CORNER_BOTTOM_RIGHT].width *= factor;
338 box->corner[GSK_CORNER_BOTTOM_RIGHT].height *= factor;
339 box->corner[GSK_CORNER_BOTTOM_LEFT].width *= factor;
340 box->corner[GSK_CORNER_BOTTOM_LEFT].height *= factor;
341}
342
343static inline void
344gtk_css_boxes_apply_border_radius (GskRoundedRect *box,
345 const GtkCssValue *top_left,
346 const GtkCssValue *top_right,
347 const GtkCssValue *bottom_right,
348 const GtkCssValue *bottom_left)
349{
350 gboolean has_border_radius = FALSE;
351
352 if (!gtk_css_corner_value_is_zero (corner: top_left))
353 {
354 box->corner[GSK_CORNER_TOP_LEFT].width = _gtk_css_corner_value_get_x (corner: top_left, one_hundred_percent: box->bounds.size.width);
355 box->corner[GSK_CORNER_TOP_LEFT].height = _gtk_css_corner_value_get_y (corner: top_left, one_hundred_percent: box->bounds.size.height);
356 has_border_radius = TRUE;
357 }
358
359 if (!gtk_css_corner_value_is_zero (corner: top_right))
360 {
361 box->corner[GSK_CORNER_TOP_RIGHT].width = _gtk_css_corner_value_get_x (corner: top_right, one_hundred_percent: box->bounds.size.width);
362 box->corner[GSK_CORNER_TOP_RIGHT].height = _gtk_css_corner_value_get_y (corner: top_right, one_hundred_percent: box->bounds.size.height);
363 has_border_radius = TRUE;
364 }
365
366 if (!gtk_css_corner_value_is_zero (corner: bottom_right))
367 {
368 box->corner[GSK_CORNER_BOTTOM_RIGHT].width = _gtk_css_corner_value_get_x (corner: bottom_right, one_hundred_percent: box->bounds.size.width);
369 box->corner[GSK_CORNER_BOTTOM_RIGHT].height = _gtk_css_corner_value_get_y (corner: bottom_right, one_hundred_percent: box->bounds.size.height);
370 has_border_radius = TRUE;
371 }
372
373 if (!gtk_css_corner_value_is_zero (corner: bottom_left))
374 {
375 box->corner[GSK_CORNER_BOTTOM_LEFT].width = _gtk_css_corner_value_get_x (corner: bottom_left, one_hundred_percent: box->bounds.size.width);
376 box->corner[GSK_CORNER_BOTTOM_LEFT].height = _gtk_css_corner_value_get_y (corner: bottom_left, one_hundred_percent: box->bounds.size.height);
377 has_border_radius = TRUE;
378 }
379
380 if (has_border_radius)
381 gtk_css_boxes_clamp_border_radius (box);
382}
383
384/* NB: width and height must be >= 0 */
385static inline void
386gtk_css_boxes_shrink_border_radius (graphene_size_t *dest,
387 const graphene_size_t *src,
388 double width,
389 double height)
390{
391 dest->width = src->width - width;
392 dest->height = src->height - height;
393
394 if (dest->width <= 0 || dest->height <= 0)
395 {
396 dest->width = 0;
397 dest->height = 0;
398 }
399}
400
401static inline void
402gtk_css_boxes_shrink_corners (GskRoundedRect *dest,
403 const GskRoundedRect *src)
404{
405 double top = dest->bounds.origin.y - src->bounds.origin.y;
406 double right = src->bounds.origin.x + src->bounds.size.width - dest->bounds.origin.x - dest->bounds.size.width;
407 double bottom = src->bounds.origin.y + src->bounds.size.height - dest->bounds.origin.y - dest->bounds.size.height;
408 double left = dest->bounds.origin.x - src->bounds.origin.x;
409
410 gtk_css_boxes_shrink_border_radius (dest: &dest->corner[GSK_CORNER_TOP_LEFT],
411 src: &src->corner[GSK_CORNER_TOP_LEFT],
412 width: top, height: left);
413 gtk_css_boxes_shrink_border_radius (dest: &dest->corner[GSK_CORNER_TOP_RIGHT],
414 src: &src->corner[GSK_CORNER_TOP_RIGHT],
415 width: top, height: right);
416 gtk_css_boxes_shrink_border_radius (dest: &dest->corner[GSK_CORNER_BOTTOM_RIGHT],
417 src: &src->corner[GSK_CORNER_BOTTOM_RIGHT],
418 width: bottom, height: right);
419 gtk_css_boxes_shrink_border_radius (dest: &dest->corner[GSK_CORNER_BOTTOM_LEFT],
420 src: &src->corner[GSK_CORNER_BOTTOM_LEFT],
421 width: bottom, height: left);
422}
423
424static inline void
425gtk_css_boxes_compute_border_box (GtkCssBoxes *boxes)
426{
427 if (boxes->has_box[GTK_CSS_AREA_BORDER_BOX])
428 return;
429
430 gtk_css_boxes_compute_border_rect (boxes);
431
432 gtk_css_boxes_apply_border_radius (box: &boxes->box[GTK_CSS_AREA_BORDER_BOX],
433 top_left: boxes->style->border->border_top_left_radius,
434 top_right: boxes->style->border->border_top_right_radius,
435 bottom_right: boxes->style->border->border_bottom_right_radius,
436 bottom_left: boxes->style->border->border_bottom_left_radius);
437
438 boxes->has_box[GTK_CSS_AREA_BORDER_BOX] = TRUE;
439}
440
441static inline void
442gtk_css_boxes_compute_padding_box (GtkCssBoxes *boxes)
443{
444 if (boxes->has_box[GTK_CSS_AREA_PADDING_BOX])
445 return;
446
447 gtk_css_boxes_compute_border_box (boxes);
448 gtk_css_boxes_compute_padding_rect (boxes);
449
450 gtk_css_boxes_shrink_corners (dest: &boxes->box[GTK_CSS_AREA_PADDING_BOX],
451 src: &boxes->box[GTK_CSS_AREA_BORDER_BOX]);
452
453 boxes->has_box[GTK_CSS_AREA_PADDING_BOX] = TRUE;
454}
455
456static inline void
457gtk_css_boxes_compute_content_box (GtkCssBoxes *boxes)
458{
459 if (boxes->has_box[GTK_CSS_AREA_CONTENT_BOX])
460 return;
461
462 gtk_css_boxes_compute_padding_box (boxes);
463 gtk_css_boxes_compute_content_rect (boxes);
464
465 gtk_css_boxes_shrink_corners (dest: &boxes->box[GTK_CSS_AREA_CONTENT_BOX],
466 src: &boxes->box[GTK_CSS_AREA_PADDING_BOX]);
467
468 boxes->has_box[GTK_CSS_AREA_CONTENT_BOX] = TRUE;
469}
470
471static inline void
472gtk_css_boxes_compute_outline_box (GtkCssBoxes *boxes)
473{
474 const GskRoundedRect *src;
475 GskRoundedRect *dest;
476 double d;
477 int i;
478
479 if (boxes->has_box[GTK_CSS_AREA_OUTLINE_BOX])
480 return;
481
482 gtk_css_boxes_compute_border_box (boxes);
483
484 src = &boxes->box[GTK_CSS_AREA_BORDER_BOX];
485 dest = &boxes->box[GTK_CSS_AREA_OUTLINE_BOX];
486
487 d = _gtk_css_number_value_get (number: boxes->style->outline->outline_offset, one_hundred_percent: 100) +
488 _gtk_css_number_value_get (number: boxes->style->outline->outline_width, one_hundred_percent: 100);
489
490 /* Grow border rect into outline rect */
491 dest->bounds.origin.x = src->bounds.origin.x - d;
492 dest->bounds.origin.y = src->bounds.origin.y - d;
493 dest->bounds.size.width = src->bounds.size.width + d + d;
494 dest->bounds.size.height = src->bounds.size.height + d + d;
495
496 /* Grow corner radii of border rect */
497 for (i = 0; i < 4; i ++)
498 {
499 if (src->corner[i].width > 0) dest->corner[i].width = src->corner[i].width + d;
500 if (src->corner[i].height > 0) dest->corner[i].height = src->corner[i].height + d;
501
502 if (dest->corner[i].width <= 0 || dest->corner[i].height <= 0)
503 {
504 dest->corner[i].width = 0;
505 dest->corner[i].height = 0;
506 }
507 else
508 {
509 dest->corner[i].width = MIN (dest->corner[i].width, dest->bounds.size.width);
510 dest->corner[i].height = MIN (dest->corner[i].height, dest->bounds.size.height);
511 }
512 }
513
514 boxes->has_box[GTK_CSS_AREA_OUTLINE_BOX] = TRUE;
515}
516
517static inline const GskRoundedRect *
518gtk_css_boxes_get_box (GtkCssBoxes *boxes,
519 GtkCssArea area)
520{
521 switch (area)
522 {
523 case GTK_CSS_AREA_BORDER_BOX:
524 return gtk_css_boxes_get_border_box (boxes);
525 case GTK_CSS_AREA_PADDING_BOX:
526 return gtk_css_boxes_get_padding_box (boxes);
527 case GTK_CSS_AREA_CONTENT_BOX:
528 return gtk_css_boxes_get_content_box (boxes);
529 default:
530 g_assert_not_reached ();
531 return NULL;
532 }
533}
534
535static inline const GskRoundedRect *
536gtk_css_boxes_get_border_box (GtkCssBoxes *boxes)
537{
538 gtk_css_boxes_compute_border_box (boxes);
539
540 return &boxes->box[GTK_CSS_AREA_BORDER_BOX];
541}
542
543static inline const GskRoundedRect *
544gtk_css_boxes_get_padding_box (GtkCssBoxes *boxes)
545{
546 gtk_css_boxes_compute_padding_box (boxes);
547
548 return &boxes->box[GTK_CSS_AREA_PADDING_BOX];
549}
550
551static inline const GskRoundedRect *
552gtk_css_boxes_get_content_box (GtkCssBoxes *boxes)
553{
554 gtk_css_boxes_compute_content_box (boxes);
555
556 return &boxes->box[GTK_CSS_AREA_CONTENT_BOX];
557}
558
559static inline const GskRoundedRect *
560gtk_css_boxes_get_outline_box (GtkCssBoxes *boxes)
561{
562 gtk_css_boxes_compute_outline_box (boxes);
563
564 return &boxes->box[GTK_CSS_AREA_OUTLINE_BOX];
565}
566
567#endif /* __GTK_CSS_BOXES_IMPL_PRIVATE_H__ */
568

source code of gtk/gtk/gtkcssboxesimplprivate.h