1/* GSK - The GTK Scene Kit
2 *
3 * Copyright 2016 Endless
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19/**
20 * GskRoundedRect:
21 * @bounds: the bounds of the rectangle
22 * @corner: the size of the 4 rounded corners
23 *
24 * A rectangular region with rounded corners.
25 *
26 * Application code should normalize rectangles using
27 * [method@Gsk.RoundedRect.normalize]; this function will ensure that
28 * the bounds of the rectangle are normalized and ensure that the corner
29 * values are positive and the corners do not overlap.
30 *
31 * All functions taking a `GskRoundedRect` as an argument will internally
32 * operate on a normalized copy; all functions returning a `GskRoundedRect`
33 * will always return a normalized one.
34 *
35 * The algorithm used for normalizing corner sizes is described in
36 * [the CSS specification](https://drafts.csswg.org/css-backgrounds-3/#border-radius).
37 */
38
39#include "config.h"
40
41#include "gskroundedrect.h"
42#include "gskroundedrectprivate.h"
43
44#include "gskdebugprivate.h"
45
46#include <math.h>
47
48static void
49gsk_rounded_rect_normalize_in_place (GskRoundedRect *self)
50{
51 float factor = 1.0;
52 float corners;
53 guint i;
54
55 graphene_rect_normalize (r: &self->bounds);
56
57 for (i = 0; i < 4; i++)
58 {
59 self->corner[i].width = MAX (self->corner[i].width, 0);
60 self->corner[i].height = MAX (self->corner[i].height, 0);
61 }
62
63 /* clamp border radius, following CSS specs */
64 corners = self->corner[GSK_CORNER_TOP_LEFT].width + self->corner[GSK_CORNER_TOP_RIGHT].width;
65 if (corners > self->bounds.size.width)
66 factor = MIN (factor, self->bounds.size.width / corners);
67
68 corners = self->corner[GSK_CORNER_TOP_RIGHT].height + self->corner[GSK_CORNER_BOTTOM_RIGHT].height;
69 if (corners > self->bounds.size.height)
70 factor = MIN (factor, self->bounds.size.height / corners);
71
72 corners = self->corner[GSK_CORNER_BOTTOM_RIGHT].width + self->corner[GSK_CORNER_BOTTOM_LEFT].width;
73 if (corners > self->bounds.size.width)
74 factor = MIN (factor, self->bounds.size.width / corners);
75
76 corners = self->corner[GSK_CORNER_TOP_LEFT].height + self->corner[GSK_CORNER_BOTTOM_LEFT].height;
77 if (corners > self->bounds.size.height)
78 factor = MIN (factor, self->bounds.size.height / corners);
79
80 for (i = 0; i < 4; i++)
81 graphene_size_scale (s: &self->corner[i], factor, res: &self->corner[i]);
82}
83
84/**
85 * gsk_rounded_rect_init:
86 * @self: The `GskRoundedRect` to initialize
87 * @bounds: a `graphene_rect_t` describing the bounds
88 * @top_left: the rounding radius of the top left corner
89 * @top_right: the rounding radius of the top right corner
90 * @bottom_right: the rounding radius of the bottom right corner
91 * @bottom_left: the rounding radius of the bottom left corner
92 *
93 * Initializes the given `GskRoundedRect` with the given values.
94 *
95 * This function will implicitly normalize the `GskRoundedRect`
96 * before returning.
97 *
98 * Returns: (transfer none): the initialized rectangle
99 */
100GskRoundedRect *
101gsk_rounded_rect_init (GskRoundedRect *self,
102 const graphene_rect_t *bounds,
103 const graphene_size_t *top_left,
104 const graphene_size_t *top_right,
105 const graphene_size_t *bottom_right,
106 const graphene_size_t *bottom_left)
107{
108 graphene_rect_init_from_rect (r: &self->bounds, src: bounds);
109 graphene_size_init_from_size (s: &self->corner[GSK_CORNER_TOP_LEFT], src: top_left);
110 graphene_size_init_from_size (s: &self->corner[GSK_CORNER_TOP_RIGHT], src: top_right);
111 graphene_size_init_from_size (s: &self->corner[GSK_CORNER_BOTTOM_RIGHT], src: bottom_right);
112 graphene_size_init_from_size (s: &self->corner[GSK_CORNER_BOTTOM_LEFT], src: bottom_left);
113
114 gsk_rounded_rect_normalize_in_place (self);
115
116 return self;
117}
118
119/**
120 * gsk_rounded_rect_init_copy:
121 * @self: a `GskRoundedRect`
122 * @src: a `GskRoundedRect`
123 *
124 * Initializes @self using the given @src rectangle.
125 *
126 * This function will not normalize the `GskRoundedRect`,
127 * so make sure the source is normalized.
128 *
129 * Returns: (transfer none): the initialized rectangle
130 */
131GskRoundedRect *
132gsk_rounded_rect_init_copy (GskRoundedRect *self,
133 const GskRoundedRect *src)
134{
135 *self = *src;
136
137 return self;
138}
139
140/**
141 * gsk_rounded_rect_init_from_rect:
142 * @self: a `GskRoundedRect`
143 * @bounds: a `graphene_rect_t`
144 * @radius: the border radius
145 *
146 * Initializes @self to the given @bounds and sets the radius
147 * of all four corners to @radius.
148 *
149 * Returns: (transfer none): the initialized rectangle
150 **/
151GskRoundedRect *
152gsk_rounded_rect_init_from_rect (GskRoundedRect *self,
153 const graphene_rect_t *bounds,
154 float radius)
155{
156 graphene_size_t corner = GRAPHENE_SIZE_INIT(radius, radius);
157
158 return gsk_rounded_rect_init (self, bounds, top_left: &corner, top_right: &corner, bottom_right: &corner, bottom_left: &corner);
159}
160
161/**
162 * gsk_rounded_rect_normalize:
163 * @self: a `GskRoundedRect`
164 *
165 * Normalizes the passed rectangle.
166 *
167 * This function will ensure that the bounds of the rectangle
168 * are normalized and ensure that the corner values are positive
169 * and the corners do not overlap.
170 *
171 * Returns: (transfer none): the normalized rectangle
172 */
173GskRoundedRect *
174gsk_rounded_rect_normalize (GskRoundedRect *self)
175{
176 gsk_rounded_rect_normalize_in_place (self);
177
178 return self;
179}
180
181/**
182 * gsk_rounded_rect_offset:
183 * @self: a `GskRoundedRect`
184 * @dx: the horizontal offset
185 * @dy: the vertical offset
186 *
187 * Offsets the bound's origin by @dx and @dy.
188 *
189 * The size and corners of the rectangle are unchanged.
190 *
191 * Returns: (transfer none): the offset rectangle
192 */
193GskRoundedRect *
194gsk_rounded_rect_offset (GskRoundedRect *self,
195 float dx,
196 float dy)
197{
198 gsk_rounded_rect_normalize (self);
199
200 self->bounds.origin.x += dx;
201 self->bounds.origin.y += dy;
202
203 return self;
204}
205
206static inline void
207border_radius_shrink (graphene_size_t *corner,
208 double width,
209 double height,
210 const graphene_size_t *max)
211{
212 if (corner->width > 0)
213 corner->width -= width;
214 if (corner->height > 0)
215 corner->height -= height;
216
217 if (corner->width <= 0 || corner->height <= 0)
218 {
219 corner->width = 0;
220 corner->height = 0;
221 }
222 else
223 {
224 corner->width = MIN (corner->width, max->width);
225 corner->height = MIN (corner->height, max->height);
226 }
227}
228
229/**
230 * gsk_rounded_rect_shrink:
231 * @self: The `GskRoundedRect` to shrink or grow
232 * @top: How far to move the top side downwards
233 * @right: How far to move the right side to the left
234 * @bottom: How far to move the bottom side upwards
235 * @left: How far to move the left side to the right
236 *
237 * Shrinks (or grows) the given rectangle by moving the 4 sides
238 * according to the offsets given.
239 *
240 * The corner radii will be changed in a way that tries to keep
241 * the center of the corner circle intact. This emulates CSS behavior.
242 *
243 * This function also works for growing rectangles if you pass
244 * negative values for the @top, @right, @bottom or @left.
245 *
246 * Returns: (transfer none): the resized `GskRoundedRect`
247 **/
248GskRoundedRect *
249gsk_rounded_rect_shrink (GskRoundedRect *self,
250 float top,
251 float right,
252 float bottom,
253 float left)
254{
255 float width = left + right;
256 float height = top + bottom;
257
258 if (self->bounds.size.width - width < 0)
259 {
260 self->bounds.origin.x += left * self->bounds.size.width / width;
261 self->bounds.size.width = 0;
262 }
263 else
264 {
265 self->bounds.origin.x += left;
266 self->bounds.size.width -= width;
267 }
268
269 if (self->bounds.size.height - height < 0)
270 {
271 self->bounds.origin.y += top * self->bounds.size.height / height;
272 self->bounds.size.height = 0;
273 }
274 else
275 {
276 self->bounds.origin.y += top;
277 self->bounds.size.height -= height;
278 }
279
280 border_radius_shrink (corner: &self->corner[GSK_CORNER_TOP_LEFT], width: left, height: top, max: &self->bounds.size);
281 border_radius_shrink (corner: &self->corner[GSK_CORNER_TOP_RIGHT], width: right, height: top, max: &self->bounds.size);
282 border_radius_shrink (corner: &self->corner[GSK_CORNER_BOTTOM_RIGHT], width: right, height: bottom, max: &self->bounds.size);
283 border_radius_shrink (corner: &self->corner[GSK_CORNER_BOTTOM_LEFT], width: left, height: bottom, max: &self->bounds.size);
284
285 return self;
286}
287
288void
289gsk_rounded_rect_scale_affine (GskRoundedRect *dest,
290 const GskRoundedRect *src,
291 float scale_x,
292 float scale_y,
293 float dx,
294 float dy)
295{
296 guint flip = ((scale_x < 0) ? 1 : 0) + (scale_y < 0 ? 2 : 0);
297
298 g_assert (dest != src);
299
300 graphene_rect_scale (r: &src->bounds, s_h: scale_x, s_v: scale_y, res: &dest->bounds);
301 graphene_rect_offset (r: &dest->bounds, d_x: dx, d_y: dy);
302
303 scale_x = fabs (x: scale_x);
304 scale_y = fabs (x: scale_y);
305
306 for (guint i = 0; i < 4; i++)
307 {
308 dest->corner[i].width = src->corner[i ^ flip].width * scale_x;
309 dest->corner[i].height = src->corner[i ^ flip].height * scale_y;
310 }
311}
312
313/*<private>
314 * gsk_rounded_rect_is_circular:
315 * @self: the `GskRoundedRect` to check
316 *
317 * Checks if all corners of @self are quarter-circles (as
318 * opposed to quarter-ellipses).
319 *
320 * Note that different corners can still have different radii.
321 *
322 * Returns: %TRUE if the rectangle is circular.
323 */
324gboolean
325gsk_rounded_rect_is_circular (const GskRoundedRect *self)
326{
327 for (guint i = 0; i < 4; i++)
328 {
329 if (self->corner[i].width != self->corner[i].height)
330 return FALSE;
331 }
332
333 return TRUE;
334}
335
336/**
337 * gsk_rounded_rect_is_rectilinear:
338 * @self: the `GskRoundedRect` to check
339 *
340 * Checks if all corners of @self are right angles and the
341 * rectangle covers all of its bounds.
342 *
343 * This information can be used to decide if [ctor@Gsk.ClipNode.new]
344 * or [ctor@Gsk.RoundedClipNode.new] should be called.
345 *
346 * Returns: %TRUE if the rectangle is rectilinear
347 **/
348gboolean
349gsk_rounded_rect_is_rectilinear (const GskRoundedRect *self)
350{
351 for (guint i = 0; i < 4; i++)
352 {
353 if (self->corner[i].width > 0 ||
354 self->corner[i].height > 0)
355 return FALSE;
356 }
357
358 return TRUE;
359}
360
361static inline gboolean
362ellipsis_contains_point (const graphene_size_t *ellipsis,
363 const graphene_point_t *point)
364{
365 return (point->x * point->x) / (ellipsis->width * ellipsis->width)
366 + (point->y * point->y) / (ellipsis->height * ellipsis->height) <= 1;
367}
368
369typedef enum
370{
371 INSIDE,
372 OUTSIDE_TOP_LEFT,
373 OUTSIDE_TOP_RIGHT,
374 OUTSIDE_BOTTOM_LEFT,
375 OUTSIDE_BOTTOM_RIGHT,
376 OUTSIDE
377} Location;
378
379static Location
380gsk_rounded_rect_locate_point (const GskRoundedRect *self,
381 const graphene_point_t *point)
382{
383 float px, py;
384 float ox, oy;
385
386 ox = self->bounds.origin.x + self->bounds.size.width;
387 oy = self->bounds.origin.y + self->bounds.size.height;
388
389 if (point->x < self->bounds.origin.x ||
390 point->y < self->bounds.origin.y ||
391 point->x > ox ||
392 point->y > oy)
393 return OUTSIDE;
394
395 px = self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width - point->x;
396 py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height - point->y;
397 if (px > 0 && py > 0 &&
398 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_TOP_LEFT], point: &GRAPHENE_POINT_INIT (px, py)))
399 return OUTSIDE_TOP_LEFT;
400
401 px = ox - self->corner[GSK_CORNER_TOP_RIGHT].width - point->x;
402 py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height - point->y;
403 if (px < 0 && py > 0 &&
404 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_TOP_RIGHT], point: &GRAPHENE_POINT_INIT (px, py)))
405 return OUTSIDE_TOP_RIGHT;
406
407 px = self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width - point->x;
408 py = oy - self->corner[GSK_CORNER_BOTTOM_LEFT].height - point->y;
409 if (px > 0 && py < 0 &&
410 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_BOTTOM_LEFT],
411 point: &GRAPHENE_POINT_INIT (px, py)))
412 return OUTSIDE_BOTTOM_LEFT;
413
414 px = ox - self->corner[GSK_CORNER_BOTTOM_RIGHT].width - point->x;
415 py = oy - self->corner[GSK_CORNER_BOTTOM_RIGHT].height - point->y;
416 if (px < 0 && py < 0 &&
417 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_BOTTOM_RIGHT],
418 point: &GRAPHENE_POINT_INIT (px, py)))
419 return OUTSIDE_BOTTOM_RIGHT;
420
421 return INSIDE;
422}
423
424/**
425 * gsk_rounded_rect_contains_point:
426 * @self: a `GskRoundedRect`
427 * @point: the point to check
428 *
429 * Checks if the given @point is inside the rounded rectangle.
430 *
431 * Returns: %TRUE if the @point is inside the rounded rectangle
432 **/
433gboolean
434gsk_rounded_rect_contains_point (const GskRoundedRect *self,
435 const graphene_point_t *point)
436{
437 return gsk_rounded_rect_locate_point (self, point) == INSIDE;
438}
439
440/**
441 * gsk_rounded_rect_contains_rect:
442 * @self: a `GskRoundedRect`
443 * @rect: the rectangle to check
444 *
445 * Checks if the given @rect is contained inside the rounded rectangle.
446 *
447 * Returns: %TRUE if the @rect is fully contained inside the rounded rectangle
448 **/
449gboolean
450gsk_rounded_rect_contains_rect (const GskRoundedRect *self,
451 const graphene_rect_t *rect)
452{
453 float tx, ty;
454 float px, py;
455 float ox, oy;
456
457 tx = rect->origin.x + rect->size.width;
458 ty = rect->origin.y + rect->size.height;
459 ox = self->bounds.origin.x + self->bounds.size.width;
460 oy = self->bounds.origin.y + self->bounds.size.height;
461
462 if (rect->origin.x < self->bounds.origin.x ||
463 rect->origin.y < self->bounds.origin.y ||
464 tx > ox ||
465 ty > oy)
466 return FALSE;
467
468 px = self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width - rect->origin.x;
469 py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height - rect->origin.y;
470 if (px > 0 && py > 0 &&
471 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_TOP_LEFT], point: &GRAPHENE_POINT_INIT (px, py)))
472 return FALSE;
473
474 px = ox - self->corner[GSK_CORNER_TOP_RIGHT].width - tx;
475 py = self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height - rect->origin.y;
476 if (px < 0 && py > 0 &&
477 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_TOP_RIGHT], point: &GRAPHENE_POINT_INIT (px, py)))
478 return FALSE;
479
480 px = self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width - rect->origin.x;
481 py = oy - self->corner[GSK_CORNER_BOTTOM_LEFT].height - ty;
482 if (px > 0 && py < 0 &&
483 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_BOTTOM_LEFT],
484 point: &GRAPHENE_POINT_INIT (px, py)))
485 return FALSE;
486
487 px = ox - self->corner[GSK_CORNER_BOTTOM_RIGHT].width - tx;
488 py = oy - self->corner[GSK_CORNER_BOTTOM_RIGHT].height - ty;
489 if (px < 0 && py < 0 &&
490 !ellipsis_contains_point (ellipsis: &self->corner[GSK_CORNER_BOTTOM_RIGHT],
491 point: &GRAPHENE_POINT_INIT (px, py)))
492 return FALSE;
493
494 return TRUE;
495}
496
497/**
498 * gsk_rounded_rect_intersects_rect:
499 * @self: a `GskRoundedRect`
500 * @rect: the rectangle to check
501 *
502 * Checks if part of the given @rect is contained inside the rounded rectangle.
503 *
504 * Returns: %TRUE if the @rect intersects with the rounded rectangle
505 */
506gboolean
507gsk_rounded_rect_intersects_rect (const GskRoundedRect *self,
508 const graphene_rect_t *rect)
509{
510 if (!graphene_rect_intersection (a: &self->bounds, b: rect, NULL))
511 return FALSE;
512
513 /* If the bounding boxes intersect but the rectangles don't,
514 * one of the rect's corners must be in the opposite corner's
515 * outside region
516 */
517 if (gsk_rounded_rect_locate_point (self, point: &rect->origin) == OUTSIDE_BOTTOM_RIGHT ||
518 gsk_rounded_rect_locate_point (self, point: &GRAPHENE_POINT_INIT (rect->origin.x + rect->size.width, rect->origin.y)) == OUTSIDE_BOTTOM_LEFT ||
519 gsk_rounded_rect_locate_point (self, point: &GRAPHENE_POINT_INIT (rect->origin.x, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_RIGHT ||
520 gsk_rounded_rect_locate_point (self, point: &GRAPHENE_POINT_INIT (rect->origin.x + rect->size.width, rect->origin.y + rect->size.height)) == OUTSIDE_TOP_LEFT)
521 return FALSE;
522
523 return TRUE;
524}
525
526static void
527append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
528{
529 if (negative)
530 cairo_arc_negative (cr, xc: 0.0, yc: 0.0, radius: 1.0, angle1, angle2);
531 else
532 cairo_arc (cr, xc: 0.0, yc: 0.0, radius: 1.0, angle1, angle2);
533}
534
535static void
536_cairo_ellipsis (cairo_t *cr,
537 double xc, double yc,
538 double xradius, double yradius,
539 double angle1, double angle2)
540{
541 cairo_matrix_t save;
542
543 if (xradius <= 0.0 || yradius <= 0.0)
544 {
545 cairo_line_to (cr, x: xc, y: yc);
546 return;
547 }
548
549 cairo_get_matrix (cr, matrix: &save);
550 cairo_translate (cr, tx: xc, ty: yc);
551 cairo_scale (cr, sx: xradius, sy: yradius);
552 append_arc (cr, angle1, angle2, FALSE);
553 cairo_set_matrix (cr, matrix: &save);
554}
555
556void
557gsk_rounded_rect_path (const GskRoundedRect *self,
558 cairo_t *cr)
559{
560 cairo_new_sub_path (cr);
561
562 _cairo_ellipsis (cr,
563 xc: self->bounds.origin.x + self->corner[GSK_CORNER_TOP_LEFT].width,
564 yc: self->bounds.origin.y + self->corner[GSK_CORNER_TOP_LEFT].height,
565 xradius: self->corner[GSK_CORNER_TOP_LEFT].width,
566 yradius: self->corner[GSK_CORNER_TOP_LEFT].height,
567 G_PI, angle2: 3 * G_PI_2);
568 _cairo_ellipsis (cr,
569 xc: self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_TOP_RIGHT].width,
570 yc: self->bounds.origin.y + self->corner[GSK_CORNER_TOP_RIGHT].height,
571 xradius: self->corner[GSK_CORNER_TOP_RIGHT].width,
572 yradius: self->corner[GSK_CORNER_TOP_RIGHT].height,
573 angle1: - G_PI_2, angle2: 0);
574 _cairo_ellipsis (cr,
575 xc: self->bounds.origin.x + self->bounds.size.width - self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
576 yc: self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
577 xradius: self->corner[GSK_CORNER_BOTTOM_RIGHT].width,
578 yradius: self->corner[GSK_CORNER_BOTTOM_RIGHT].height,
579 angle1: 0, G_PI_2);
580 _cairo_ellipsis (cr,
581 xc: self->bounds.origin.x + self->corner[GSK_CORNER_BOTTOM_LEFT].width,
582 yc: self->bounds.origin.y + self->bounds.size.height - self->corner[GSK_CORNER_BOTTOM_LEFT].height,
583 xradius: self->corner[GSK_CORNER_BOTTOM_LEFT].width,
584 yradius: self->corner[GSK_CORNER_BOTTOM_LEFT].height,
585 G_PI_2, G_PI);
586
587 cairo_close_path (cr);
588}
589
590/*< private >
591 * Converts to the format we use in our shaders:
592 * vec4 rect;
593 * vec4 corner_widths;
594 * vec4 corner_heights;
595 * rect is (x, y, width, height), the corners are the same
596 * order as in the rounded rect.
597 *
598 * This is so that shaders can use just the first vec4 for
599 * rectilinear rects, the 2nd vec4 for circular rects and
600 * only look at the last vec4 if they have to.
601 */
602void
603gsk_rounded_rect_to_float (const GskRoundedRect *self,
604 float rect[12])
605{
606 guint i;
607
608 rect[0] = self->bounds.origin.x;
609 rect[1] = self->bounds.origin.y;
610 rect[2] = self->bounds.size.width;
611 rect[3] = self->bounds.size.height;
612
613 for (i = 0; i < 4; i++)
614 {
615 rect[4 + i] = self->corner[i].width;
616 rect[8 + i] = self->corner[i].height;
617 }
618}
619
620gboolean
621gsk_rounded_rect_equal (gconstpointer rect1,
622 gconstpointer rect2)
623{
624 const GskRoundedRect *self1 = rect1;
625 const GskRoundedRect *self2 = rect2;
626
627 return graphene_rect_equal (a: &self1->bounds, b: &self2->bounds)
628 && graphene_size_equal (a: &self1->corner[0], b: &self2->corner[0])
629 && graphene_size_equal (a: &self1->corner[1], b: &self2->corner[1])
630 && graphene_size_equal (a: &self1->corner[2], b: &self2->corner[2])
631 && graphene_size_equal (a: &self1->corner[3], b: &self2->corner[3]);
632}
633
634char *
635gsk_rounded_rect_to_string (const GskRoundedRect *self)
636{
637 return g_strdup_printf (format: "GskRoundedRect %p: Bounds: (%f, %f, %f, %f)"
638 " Corners: (%f, %f) (%f, %f) (%f, %f) (%f, %f)",
639 self,
640 self->bounds.origin.x,
641 self->bounds.origin.y,
642 self->bounds.size.width,
643 self->bounds.size.height,
644 self->corner[0].width,
645 self->corner[0].height,
646 self->corner[1].width,
647 self->corner[1].height,
648 self->corner[2].width,
649 self->corner[2].height,
650 self->corner[3].width,
651 self->corner[3].height);
652
653}
654

source code of gtk/gsk/gskroundedrect.c