1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org> |
3 | * Copyright (C) 2011 Red Hat, Inc. |
4 | * |
5 | * Authors: Carlos Garnacho <carlosg@gnome.org> |
6 | * Cosimo Cecchi <cosimoc@gnome.org> |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Lesser General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Lesser General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Lesser General Public |
19 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | #include <config.h> |
23 | |
24 | #include "gtkrenderborderprivate.h" |
25 | |
26 | #include <cairo-gobject.h> |
27 | #include <math.h> |
28 | |
29 | #include "gtkcssbordervalueprivate.h" |
30 | #include "gtkcssenumvalueprivate.h" |
31 | #include "gtkcssimagevalueprivate.h" |
32 | #include "gtkcssnumbervalueprivate.h" |
33 | #include "gtkcssrepeatvalueprivate.h" |
34 | #include "gtkcsscolorvalueprivate.h" |
35 | #include "gtkcssstyleprivate.h" |
36 | #include "gtkroundedboxprivate.h" |
37 | #include "gtksnapshotprivate.h" |
38 | |
39 | #include "gdk/gdkhslaprivate.h" |
40 | #include "gsk/gskroundedrectprivate.h" |
41 | |
42 | typedef struct _GtkBorderImage GtkBorderImage; |
43 | |
44 | struct _GtkBorderImage { |
45 | GtkCssImage *source; |
46 | |
47 | GtkCssValue *slice; |
48 | GtkCssValue *width; |
49 | GtkCssValue *repeat; |
50 | }; |
51 | |
52 | static gboolean |
53 | gtk_border_image_init (GtkBorderImage *image, |
54 | GtkCssStyle *style) |
55 | { |
56 | image->source = _gtk_css_image_value_get_image (image: style->border->border_image_source); |
57 | if (image->source == NULL) |
58 | return FALSE; |
59 | |
60 | image->slice = style->border->border_image_slice; |
61 | image->width = style->border->border_image_width; |
62 | image->repeat = style->border->border_image_repeat; |
63 | |
64 | return TRUE; |
65 | } |
66 | |
67 | typedef struct _GtkBorderImageSliceSize GtkBorderImageSliceSize; |
68 | struct _GtkBorderImageSliceSize { |
69 | double offset; |
70 | double size; |
71 | }; |
72 | |
73 | static void |
74 | gtk_border_image_compute_border_size (GtkBorderImageSliceSize sizes[3], |
75 | double offset, |
76 | double area_size, |
77 | double start_border_width, |
78 | double end_border_width, |
79 | const GtkCssValue *start_border, |
80 | const GtkCssValue *end_border) |
81 | { |
82 | double start, end; |
83 | |
84 | if (gtk_css_number_value_get_dimension (value: start_border) == GTK_CSS_DIMENSION_NUMBER) |
85 | start = start_border_width * _gtk_css_number_value_get (number: start_border, one_hundred_percent: 100); |
86 | else |
87 | start = _gtk_css_number_value_get (number: start_border, one_hundred_percent: area_size); |
88 | if (gtk_css_number_value_get_dimension (value: end_border) == GTK_CSS_DIMENSION_NUMBER) |
89 | end = end_border_width * _gtk_css_number_value_get (number: end_border, one_hundred_percent: 100); |
90 | else |
91 | end = _gtk_css_number_value_get (number: end_border, one_hundred_percent: area_size); |
92 | |
93 | /* XXX: reduce vertical and horizontal by the same factor */ |
94 | if (start + end > area_size) |
95 | { |
96 | start = start * area_size / (start + end); |
97 | end = end * area_size / (start + end); |
98 | } |
99 | |
100 | sizes[0].offset = offset; |
101 | sizes[0].size = start; |
102 | sizes[1].offset = offset + start; |
103 | sizes[1].size = area_size - start - end; |
104 | sizes[2].offset = offset + area_size - end; |
105 | sizes[2].size = end; |
106 | } |
107 | |
108 | static void |
109 | gtk_border_image_render_slice (cairo_t *cr, |
110 | cairo_surface_t *slice, |
111 | double slice_width, |
112 | double slice_height, |
113 | double x, |
114 | double y, |
115 | double width, |
116 | double height, |
117 | GtkCssRepeatStyle hrepeat, |
118 | GtkCssRepeatStyle vrepeat) |
119 | { |
120 | double hscale, vscale; |
121 | double xstep, ystep; |
122 | cairo_extend_t extend = CAIRO_EXTEND_PAD; |
123 | cairo_matrix_t matrix; |
124 | cairo_pattern_t *pattern; |
125 | |
126 | /* We can't draw center tiles yet */ |
127 | g_assert (hrepeat == GTK_CSS_REPEAT_STYLE_STRETCH || vrepeat == GTK_CSS_REPEAT_STYLE_STRETCH); |
128 | |
129 | hscale = width / slice_width; |
130 | vscale = height / slice_height; |
131 | xstep = width; |
132 | ystep = height; |
133 | |
134 | switch (hrepeat) |
135 | { |
136 | case GTK_CSS_REPEAT_STYLE_REPEAT: |
137 | extend = CAIRO_EXTEND_REPEAT; |
138 | hscale = vscale; |
139 | break; |
140 | case GTK_CSS_REPEAT_STYLE_SPACE: |
141 | { |
142 | double space, n; |
143 | |
144 | extend = CAIRO_EXTEND_NONE; |
145 | hscale = vscale; |
146 | |
147 | xstep = hscale * slice_width; |
148 | n = floor (x: width / xstep); |
149 | space = (width - n * xstep) / (n + 1); |
150 | xstep += space; |
151 | x += space; |
152 | width -= 2 * space; |
153 | } |
154 | break; |
155 | case GTK_CSS_REPEAT_STYLE_STRETCH: |
156 | break; |
157 | case GTK_CSS_REPEAT_STYLE_ROUND: |
158 | extend = CAIRO_EXTEND_REPEAT; |
159 | hscale = width / (slice_width * MAX (round (width / (slice_width * vscale)), 1)); |
160 | break; |
161 | default: |
162 | g_assert_not_reached (); |
163 | break; |
164 | } |
165 | |
166 | switch (vrepeat) |
167 | { |
168 | case GTK_CSS_REPEAT_STYLE_REPEAT: |
169 | extend = CAIRO_EXTEND_REPEAT; |
170 | vscale = hscale; |
171 | break; |
172 | case GTK_CSS_REPEAT_STYLE_SPACE: |
173 | { |
174 | double space, n; |
175 | |
176 | extend = CAIRO_EXTEND_NONE; |
177 | vscale = hscale; |
178 | |
179 | ystep = vscale * slice_height; |
180 | n = floor (x: height / ystep); |
181 | space = (height - n * ystep) / (n + 1); |
182 | ystep += space; |
183 | y += space; |
184 | height -= 2 * space; |
185 | } |
186 | break; |
187 | case GTK_CSS_REPEAT_STYLE_STRETCH: |
188 | break; |
189 | case GTK_CSS_REPEAT_STYLE_ROUND: |
190 | extend = CAIRO_EXTEND_REPEAT; |
191 | vscale = height / (slice_height * MAX (round (height / (slice_height * hscale)), 1)); |
192 | break; |
193 | default: |
194 | g_assert_not_reached (); |
195 | break; |
196 | } |
197 | |
198 | pattern = cairo_pattern_create_for_surface (surface: slice); |
199 | |
200 | cairo_matrix_init_translate (matrix: &matrix, |
201 | tx: hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_width / 2 : 0, |
202 | ty: vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_height / 2 : 0); |
203 | cairo_matrix_scale (matrix: &matrix, sx: 1 / hscale, sy: 1 / vscale); |
204 | cairo_matrix_translate (matrix: &matrix, |
205 | tx: hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - width / 2 : 0, |
206 | ty: vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - height / 2 : 0); |
207 | |
208 | cairo_pattern_set_matrix (pattern, matrix: &matrix); |
209 | cairo_pattern_set_extend (pattern, extend); |
210 | |
211 | cairo_save (cr); |
212 | cairo_translate (cr, tx: x, ty: y); |
213 | |
214 | for (y = 0; y < height; y += ystep) |
215 | { |
216 | for (x = 0; x < width; x += xstep) |
217 | { |
218 | cairo_save (cr); |
219 | cairo_translate (cr, tx: x, ty: y); |
220 | cairo_set_source (cr, source: pattern); |
221 | cairo_rectangle (cr, x: 0, y: 0, width: xstep, height: ystep); |
222 | cairo_fill (cr); |
223 | cairo_restore (cr); |
224 | } |
225 | } |
226 | |
227 | cairo_restore (cr); |
228 | |
229 | cairo_pattern_destroy (pattern); |
230 | } |
231 | |
232 | static void |
233 | gtk_border_image_compute_slice_size (GtkBorderImageSliceSize sizes[3], |
234 | int surface_size, |
235 | int start_size, |
236 | int end_size) |
237 | { |
238 | sizes[0].size = MIN (start_size, surface_size); |
239 | sizes[0].offset = 0; |
240 | |
241 | sizes[2].size = MIN (end_size, surface_size); |
242 | sizes[2].offset = surface_size - sizes[2].size; |
243 | |
244 | sizes[1].size = MAX (0, surface_size - sizes[0].size - sizes[2].size); |
245 | sizes[1].offset = sizes[0].size; |
246 | } |
247 | |
248 | static void |
249 | gtk_border_image_render (GtkBorderImage *image, |
250 | const float border_width[4], |
251 | cairo_t *cr, |
252 | const graphene_rect_t *rect) |
253 | { |
254 | cairo_surface_t *surface, *slice; |
255 | GtkBorderImageSliceSize vertical_slice[3], horizontal_slice[3]; |
256 | GtkBorderImageSliceSize vertical_border[3], horizontal_border[3]; |
257 | double source_width, source_height; |
258 | int h, v; |
259 | |
260 | _gtk_css_image_get_concrete_size (image: image->source, |
261 | specified_width: 0, specified_height: 0, |
262 | default_width: rect->size.width, default_height: rect->size.height, |
263 | concrete_width: &source_width, concrete_height: &source_height); |
264 | |
265 | /* XXX: Optimize for (source_width == width && source_height == height) */ |
266 | |
267 | surface = _gtk_css_image_get_surface (image: image->source, |
268 | target: cairo_get_target (cr), |
269 | surface_width: source_width, surface_height: source_height); |
270 | |
271 | gtk_border_image_compute_slice_size (sizes: horizontal_slice, |
272 | surface_size: source_width, |
273 | start_size: _gtk_css_number_value_get (number: _gtk_css_border_value_get_left (value: image->slice), one_hundred_percent: source_width), |
274 | end_size: _gtk_css_number_value_get (number: _gtk_css_border_value_get_right (value: image->slice), one_hundred_percent: source_width)); |
275 | gtk_border_image_compute_slice_size (sizes: vertical_slice, |
276 | surface_size: source_height, |
277 | start_size: _gtk_css_number_value_get (number: _gtk_css_border_value_get_top (value: image->slice), one_hundred_percent: source_height), |
278 | end_size: _gtk_css_number_value_get (number: _gtk_css_border_value_get_bottom (value: image->slice), one_hundred_percent: source_height)); |
279 | gtk_border_image_compute_border_size (sizes: horizontal_border, |
280 | offset: rect->origin.x, |
281 | area_size: rect->size.width, |
282 | start_border_width: border_width[GTK_CSS_LEFT], |
283 | end_border_width: border_width[GTK_CSS_RIGHT], |
284 | start_border: _gtk_css_border_value_get_left (value: image->width), |
285 | end_border: _gtk_css_border_value_get_right (value: image->width)); |
286 | gtk_border_image_compute_border_size (sizes: vertical_border, |
287 | offset: rect->origin.y, |
288 | area_size: rect->size.height, |
289 | start_border_width: border_width[GTK_CSS_TOP], |
290 | end_border_width: border_width[GTK_CSS_BOTTOM], |
291 | start_border: _gtk_css_border_value_get_top (value: image->width), |
292 | end_border: _gtk_css_border_value_get_bottom(value: image->width)); |
293 | |
294 | for (v = 0; v < 3; v++) |
295 | { |
296 | if (vertical_slice[v].size == 0 || |
297 | vertical_border[v].size == 0) |
298 | continue; |
299 | |
300 | for (h = 0; h < 3; h++) |
301 | { |
302 | if (horizontal_slice[h].size == 0 || |
303 | horizontal_border[h].size == 0) |
304 | continue; |
305 | |
306 | if (h == 1 && v == 1) |
307 | continue; |
308 | |
309 | slice = cairo_surface_create_for_rectangle (target: surface, |
310 | x: horizontal_slice[h].offset, |
311 | y: vertical_slice[v].offset, |
312 | width: horizontal_slice[h].size, |
313 | height: vertical_slice[v].size); |
314 | |
315 | gtk_border_image_render_slice (cr, |
316 | slice, |
317 | slice_width: horizontal_slice[h].size, |
318 | slice_height: vertical_slice[v].size, |
319 | x: horizontal_border[h].offset, |
320 | y: vertical_border[v].offset, |
321 | width: horizontal_border[h].size, |
322 | height: vertical_border[v].size, |
323 | hrepeat: h == 1 ? _gtk_css_border_repeat_value_get_x (repeat: image->repeat) : GTK_CSS_REPEAT_STYLE_STRETCH, |
324 | vrepeat: v == 1 ? _gtk_css_border_repeat_value_get_y (repeat: image->repeat) : GTK_CSS_REPEAT_STYLE_STRETCH); |
325 | |
326 | cairo_surface_destroy (surface: slice); |
327 | } |
328 | } |
329 | |
330 | cairo_surface_destroy (surface); |
331 | } |
332 | |
333 | static void |
334 | snapshot_frame_fill (GtkSnapshot *snapshot, |
335 | const GskRoundedRect *outline, |
336 | const float border_width[4], |
337 | const GdkRGBA colors[4], |
338 | guint hidden_side) |
339 | { |
340 | if (hidden_side) |
341 | { |
342 | GdkRGBA real_colors[4]; |
343 | guint i; |
344 | |
345 | for (i = 0; i < 4; i++) |
346 | { |
347 | if (hidden_side & (1 << i)) |
348 | real_colors[i] = (GdkRGBA) { 0, 0, 0, 0 }; |
349 | else |
350 | real_colors[i] = colors[i]; |
351 | } |
352 | |
353 | snapshot_frame_fill (snapshot, outline, border_width, colors: real_colors, hidden_side: 0); |
354 | return; |
355 | } |
356 | |
357 | gtk_snapshot_append_border (snapshot, outline, border_width, border_color: colors); |
358 | } |
359 | |
360 | static void |
361 | set_stroke_style (cairo_t *cr, |
362 | double line_width, |
363 | GtkBorderStyle style, |
364 | double length) |
365 | { |
366 | double segments[2]; |
367 | double n; |
368 | |
369 | cairo_set_line_width (cr, width: line_width); |
370 | |
371 | if (style == GTK_BORDER_STYLE_DOTTED) |
372 | { |
373 | n = round (x: 0.5 * length / line_width); |
374 | |
375 | segments[0] = 0; |
376 | segments[1] = n ? length / n : 2; |
377 | cairo_set_dash (cr, dashes: segments, G_N_ELEMENTS (segments), offset: 0); |
378 | |
379 | cairo_set_line_cap (cr, line_cap: CAIRO_LINE_CAP_ROUND); |
380 | cairo_set_line_join (cr, line_join: CAIRO_LINE_JOIN_ROUND); |
381 | } |
382 | else |
383 | { |
384 | n = length / line_width; |
385 | /* Optimize the common case of an integer-sized rectangle |
386 | * Again, we care about focus rectangles. |
387 | */ |
388 | if (n == nearbyint (x: n)) |
389 | { |
390 | segments[0] = line_width; |
391 | segments[1] = 2 * line_width; |
392 | } |
393 | else |
394 | { |
395 | n = round (x: (1. / 3) * n); |
396 | |
397 | segments[0] = n ? (1. / 3) * length / n : 1; |
398 | segments[1] = 2 * segments[0]; |
399 | } |
400 | cairo_set_dash (cr, dashes: segments, G_N_ELEMENTS (segments), offset: 0); |
401 | |
402 | cairo_set_line_cap (cr, line_cap: CAIRO_LINE_CAP_SQUARE); |
403 | cairo_set_line_join (cr, line_join: CAIRO_LINE_JOIN_MITER); |
404 | } |
405 | } |
406 | |
407 | static void |
408 | render_frame_stroke (cairo_t *cr, |
409 | const GskRoundedRect *border_box, |
410 | const double border_width[4], |
411 | GdkRGBA colors[4], |
412 | guint hidden_side, |
413 | GtkBorderStyle stroke_style) |
414 | { |
415 | gboolean different_colors, different_borders; |
416 | GskRoundedRect stroke_box; |
417 | guint i; |
418 | |
419 | different_colors = !gdk_rgba_equal (p1: &colors[0], p2: &colors[1]) || |
420 | !gdk_rgba_equal (p1: &colors[0], p2: &colors[2]) || |
421 | !gdk_rgba_equal (p1: &colors[0], p2: &colors[3]); |
422 | different_borders = border_width[0] != border_width[1] || |
423 | border_width[0] != border_width[2] || |
424 | border_width[0] != border_width[3] ; |
425 | |
426 | stroke_box = *border_box; |
427 | gsk_rounded_rect_shrink (self: &stroke_box, |
428 | top: border_width[GTK_CSS_TOP] / 2.0, |
429 | right: border_width[GTK_CSS_RIGHT] / 2.0, |
430 | bottom: border_width[GTK_CSS_BOTTOM] / 2.0, |
431 | left: border_width[GTK_CSS_LEFT] / 2.0); |
432 | |
433 | if (!different_colors && !different_borders && hidden_side == 0) |
434 | { |
435 | double length = 0; |
436 | |
437 | /* FAST PATH: |
438 | * Mostly expected to trigger for focus rectangles */ |
439 | for (i = 0; i < 4; i++) |
440 | { |
441 | length += _gtk_rounded_box_guess_length (box: &stroke_box, side: i); |
442 | } |
443 | |
444 | gsk_rounded_rect_path (self: &stroke_box, cr); |
445 | gdk_cairo_set_source_rgba (cr, rgba: &colors[0]); |
446 | set_stroke_style (cr, line_width: border_width[0], style: stroke_style, length); |
447 | cairo_stroke (cr); |
448 | } |
449 | else |
450 | { |
451 | GskRoundedRect padding_box; |
452 | |
453 | padding_box = *border_box; |
454 | gsk_rounded_rect_shrink (self: &padding_box, |
455 | top: border_width[GTK_CSS_TOP], |
456 | right: border_width[GTK_CSS_RIGHT], |
457 | bottom: border_width[GTK_CSS_BOTTOM], |
458 | left: border_width[GTK_CSS_LEFT]); |
459 | |
460 | for (i = 0; i < 4; i++) |
461 | { |
462 | if (hidden_side & (1 << i)) |
463 | continue; |
464 | |
465 | if (border_width[i] == 0) |
466 | continue; |
467 | |
468 | cairo_save (cr); |
469 | |
470 | if (i == 0) |
471 | _gtk_rounded_box_path_top (outer: border_box, inner: &padding_box, cr); |
472 | else if (i == 1) |
473 | _gtk_rounded_box_path_right (outer: border_box, inner: &padding_box, cr); |
474 | else if (i == 2) |
475 | _gtk_rounded_box_path_bottom (outer: border_box, inner: &padding_box, cr); |
476 | else if (i == 3) |
477 | _gtk_rounded_box_path_left (outer: border_box, inner: &padding_box, cr); |
478 | cairo_clip (cr); |
479 | |
480 | _gtk_rounded_box_path_side (box: &stroke_box, cr, side: i); |
481 | |
482 | gdk_cairo_set_source_rgba (cr, rgba: &colors[i]); |
483 | set_stroke_style (cr, |
484 | line_width: border_width[i], |
485 | style: stroke_style, |
486 | length: _gtk_rounded_box_guess_length (box: &stroke_box, side: i)); |
487 | cairo_stroke (cr); |
488 | |
489 | cairo_restore (cr); |
490 | } |
491 | } |
492 | } |
493 | |
494 | static void |
495 | snapshot_frame_stroke (GtkSnapshot *snapshot, |
496 | const GskRoundedRect *outline, |
497 | const float border_width[4], |
498 | GdkRGBA colors[4], |
499 | guint hidden_side, |
500 | GtkBorderStyle stroke_style) |
501 | { |
502 | double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] }; |
503 | cairo_t *cr; |
504 | |
505 | cr = gtk_snapshot_append_cairo (snapshot, |
506 | bounds: &outline->bounds); |
507 | render_frame_stroke (cr, border_box: outline, border_width: double_width, colors, hidden_side, stroke_style); |
508 | cairo_destroy (cr); |
509 | } |
510 | |
511 | static void |
512 | color_shade (const GdkRGBA *color, |
513 | double factor, |
514 | GdkRGBA *color_return) |
515 | { |
516 | GdkHSLA hsla; |
517 | |
518 | _gdk_hsla_init_from_rgba (hsla: &hsla, rgba: color); |
519 | _gdk_hsla_shade (dest: &hsla, src: &hsla, factor); |
520 | _gdk_rgba_init_from_hsla (rgba: color_return, hsla: &hsla); |
521 | } |
522 | |
523 | static void |
524 | snapshot_border (GtkSnapshot *snapshot, |
525 | const GskRoundedRect *border_box, |
526 | const float border_width[4], |
527 | GdkRGBA colors[4], |
528 | GtkBorderStyle border_style[4]) |
529 | { |
530 | guint hidden_side = 0; |
531 | guint i, j; |
532 | |
533 | for (i = 0; i < 4; i++) |
534 | { |
535 | if (hidden_side & (1 << i)) |
536 | continue; |
537 | |
538 | /* NB: code below divides by this value */ |
539 | /* a border smaller than this will not noticeably modify |
540 | * pixels on screen, and since we don't compare with 0, |
541 | * we'll use this value */ |
542 | if (border_width[i] < 1.0 / 1024) |
543 | continue; |
544 | |
545 | switch (border_style[i]) |
546 | { |
547 | case GTK_BORDER_STYLE_NONE: |
548 | case GTK_BORDER_STYLE_HIDDEN: |
549 | case GTK_BORDER_STYLE_SOLID: |
550 | break; |
551 | case GTK_BORDER_STYLE_INSET: |
552 | if (i == 1 || i == 2) |
553 | color_shade (color: &colors[i], factor: 1.8, color_return: &colors[i]); |
554 | break; |
555 | case GTK_BORDER_STYLE_OUTSET: |
556 | if (i == 0 || i == 3) |
557 | color_shade (color: &colors[i], factor: 1.8, color_return: &colors[i]); |
558 | break; |
559 | case GTK_BORDER_STYLE_DOTTED: |
560 | case GTK_BORDER_STYLE_DASHED: |
561 | { |
562 | guint dont_draw = hidden_side; |
563 | |
564 | for (j = 0; j < 4; j++) |
565 | { |
566 | if (border_style[j] == border_style[i]) |
567 | hidden_side |= (1 << j); |
568 | else |
569 | dont_draw |= (1 << j); |
570 | } |
571 | |
572 | snapshot_frame_stroke (snapshot, outline: border_box, border_width, colors, hidden_side: dont_draw, stroke_style: border_style[i]); |
573 | } |
574 | break; |
575 | case GTK_BORDER_STYLE_DOUBLE: |
576 | { |
577 | GskRoundedRect other_box; |
578 | float other_border[4]; |
579 | guint dont_draw = hidden_side; |
580 | |
581 | for (j = 0; j < 4; j++) |
582 | { |
583 | if (border_style[j] == GTK_BORDER_STYLE_DOUBLE) |
584 | hidden_side |= (1 << j); |
585 | else |
586 | dont_draw |= (1 << j); |
587 | |
588 | other_border[j] = border_width[j] / 3; |
589 | } |
590 | |
591 | snapshot_frame_fill (snapshot, outline: border_box, border_width: other_border, colors, hidden_side: dont_draw); |
592 | |
593 | other_box = *border_box; |
594 | gsk_rounded_rect_shrink (self: &other_box, |
595 | top: 2 * other_border[GTK_CSS_TOP], |
596 | right: 2 * other_border[GTK_CSS_RIGHT], |
597 | bottom: 2 * other_border[GTK_CSS_BOTTOM], |
598 | left: 2 * other_border[GTK_CSS_LEFT]); |
599 | snapshot_frame_fill (snapshot, outline: &other_box, border_width: other_border, colors, hidden_side: dont_draw); |
600 | } |
601 | break; |
602 | case GTK_BORDER_STYLE_GROOVE: |
603 | case GTK_BORDER_STYLE_RIDGE: |
604 | { |
605 | GskRoundedRect other_box; |
606 | GdkRGBA other_colors[4]; |
607 | guint dont_draw = hidden_side; |
608 | float other_border[4]; |
609 | |
610 | for (j = 0; j < 4; j++) |
611 | { |
612 | other_colors[j] = colors[j]; |
613 | if ((j == 0 || j == 3) ^ (border_style[j] == GTK_BORDER_STYLE_RIDGE)) |
614 | color_shade (color: &other_colors[j], factor: 1.8, color_return: &other_colors[j]); |
615 | else |
616 | color_shade (color: &colors[j], factor: 1.8, color_return: &colors[j]); |
617 | if (border_style[j] == GTK_BORDER_STYLE_GROOVE || |
618 | border_style[j] == GTK_BORDER_STYLE_RIDGE) |
619 | hidden_side |= (1 << j); |
620 | else |
621 | dont_draw |= (1 << j); |
622 | other_border[j] = border_width[j] / 2; |
623 | } |
624 | |
625 | snapshot_frame_fill (snapshot, outline: border_box, border_width: other_border, colors, hidden_side: dont_draw); |
626 | |
627 | other_box = *border_box; |
628 | gsk_rounded_rect_shrink (self: &other_box, |
629 | top: other_border[GTK_CSS_TOP], |
630 | right: other_border[GTK_CSS_RIGHT], |
631 | bottom: other_border[GTK_CSS_BOTTOM], |
632 | left: other_border[GTK_CSS_LEFT]); |
633 | snapshot_frame_fill (snapshot, outline: &other_box, border_width: other_border, colors: other_colors, hidden_side: dont_draw); |
634 | } |
635 | break; |
636 | default: |
637 | g_assert_not_reached (); |
638 | break; |
639 | } |
640 | } |
641 | |
642 | snapshot_frame_fill (snapshot, outline: border_box, border_width, colors, hidden_side); |
643 | } |
644 | |
645 | void |
646 | gtk_css_style_snapshot_border (GtkCssBoxes *boxes, |
647 | GtkSnapshot *snapshot) |
648 | { |
649 | const GtkCssBorderValues *border = boxes->style->border; |
650 | GtkBorderImage border_image; |
651 | float border_width[4]; |
652 | |
653 | if (border->base.type == GTK_CSS_BORDER_INITIAL_VALUES) |
654 | return; |
655 | |
656 | if (gtk_border_image_init (image: &border_image, style: boxes->style)) |
657 | { |
658 | cairo_t *cr; |
659 | const graphene_rect_t *bounds; |
660 | |
661 | border_width[0] = _gtk_css_number_value_get (number: border->border_top_width, one_hundred_percent: 100); |
662 | border_width[1] = _gtk_css_number_value_get (number: border->border_right_width, one_hundred_percent: 100); |
663 | border_width[2] = _gtk_css_number_value_get (number: border->border_bottom_width, one_hundred_percent: 100); |
664 | border_width[3] = _gtk_css_number_value_get (number: border->border_left_width, one_hundred_percent: 100); |
665 | |
666 | bounds = gtk_css_boxes_get_border_rect (boxes); |
667 | |
668 | gtk_snapshot_push_debug (snapshot, message: "CSS border image" ); |
669 | cr = gtk_snapshot_append_cairo (snapshot, bounds); |
670 | gtk_border_image_render (image: &border_image, border_width, cr, rect: bounds); |
671 | cairo_destroy (cr); |
672 | gtk_snapshot_pop (snapshot); |
673 | } |
674 | else |
675 | { |
676 | GtkBorderStyle border_style[4]; |
677 | GdkRGBA colors[4]; |
678 | graphene_simd4f_t alpha_test_vector; |
679 | |
680 | /* Optimize the most common case of "This widget has no border" */ |
681 | if (graphene_rect_equal (a: gtk_css_boxes_get_border_rect (boxes), |
682 | b: gtk_css_boxes_get_padding_rect (boxes))) |
683 | return; |
684 | |
685 | colors[0] = *gtk_css_color_value_get_rgba (color: border->border_top_color ? border->border_top_color : boxes->style->core->color); |
686 | colors[1] = *gtk_css_color_value_get_rgba (color: border->border_right_color ? border->border_right_color : boxes->style->core->color); |
687 | colors[2] = *gtk_css_color_value_get_rgba (color: border->border_bottom_color ? border->border_bottom_color : boxes->style->core->color); |
688 | colors[3] = *gtk_css_color_value_get_rgba (color: border->border_left_color ? border->border_left_color : boxes->style->core->color); |
689 | |
690 | alpha_test_vector = graphene_simd4f_init (colors[0].alpha, colors[1].alpha, colors[2].alpha, colors[3].alpha); |
691 | if (graphene_simd4f_is_zero4 (v: alpha_test_vector)) |
692 | return; |
693 | |
694 | border_style[0] = _gtk_css_border_style_value_get (value: border->border_top_style); |
695 | border_style[1] = _gtk_css_border_style_value_get (value: border->border_right_style); |
696 | border_style[2] = _gtk_css_border_style_value_get (value: border->border_bottom_style); |
697 | border_style[3] = _gtk_css_border_style_value_get (value: border->border_left_style); |
698 | |
699 | border_width[0] = _gtk_css_number_value_get (number: border->border_top_width, one_hundred_percent: 100); |
700 | border_width[1] = _gtk_css_number_value_get (number: border->border_right_width, one_hundred_percent: 100); |
701 | border_width[2] = _gtk_css_number_value_get (number: border->border_bottom_width, one_hundred_percent: 100); |
702 | border_width[3] = _gtk_css_number_value_get (number: border->border_left_width, one_hundred_percent: 100); |
703 | |
704 | gtk_snapshot_push_debug (snapshot, message: "CSS border" ); |
705 | if (border_style[0] <= GTK_BORDER_STYLE_SOLID && |
706 | border_style[1] <= GTK_BORDER_STYLE_SOLID && |
707 | border_style[2] <= GTK_BORDER_STYLE_SOLID && |
708 | border_style[3] <= GTK_BORDER_STYLE_SOLID) |
709 | { |
710 | /* The most common case of a solid border */ |
711 | gtk_snapshot_append_border (snapshot, |
712 | outline: gtk_css_boxes_get_border_box (boxes), |
713 | border_width, |
714 | border_color: colors); |
715 | } |
716 | else |
717 | { |
718 | snapshot_border (snapshot, |
719 | border_box: gtk_css_boxes_get_border_box (boxes), |
720 | border_width, |
721 | colors, |
722 | border_style); |
723 | } |
724 | gtk_snapshot_pop (snapshot); |
725 | } |
726 | } |
727 | |
728 | void |
729 | gtk_css_style_snapshot_outline (GtkCssBoxes *boxes, |
730 | GtkSnapshot *snapshot) |
731 | { |
732 | GtkCssOutlineValues *outline = boxes->style->outline; |
733 | GtkBorderStyle border_style[4]; |
734 | float border_width[4]; |
735 | GdkRGBA colors[4]; |
736 | |
737 | border_style[0] = _gtk_css_border_style_value_get (value: outline->outline_style); |
738 | if (border_style[0] != GTK_BORDER_STYLE_NONE) |
739 | { |
740 | const GdkRGBA *color = gtk_css_color_value_get_rgba (color: outline->outline_color ? |
741 | outline->outline_color : |
742 | boxes->style->core->color); |
743 | if (gdk_rgba_is_clear (rgba: color)) |
744 | return; |
745 | |
746 | border_width[0] = _gtk_css_number_value_get (number: outline->outline_width, one_hundred_percent: 100); |
747 | |
748 | if (G_APPROX_VALUE (border_width[0], 0, FLT_EPSILON)) |
749 | return; |
750 | |
751 | border_style[1] = border_style[2] = border_style[3] = border_style[0]; |
752 | border_width[3] = border_width[2] = border_width[1] = border_width[0]; |
753 | colors[0] = colors[1] = colors[2] = colors[3] = *color; |
754 | |
755 | snapshot_border (snapshot, |
756 | border_box: gtk_css_boxes_get_outline_box (boxes), |
757 | border_width, |
758 | colors, |
759 | border_style); |
760 | } |
761 | } |
762 | |