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 "gtkrenderbackgroundprivate.h" |
25 | |
26 | #include "gtkcssarrayvalueprivate.h" |
27 | #include "gtkcssbgsizevalueprivate.h" |
28 | #include "gtkcssboxesprivate.h" |
29 | #include "gtkcsscornervalueprivate.h" |
30 | #include "gtkcssenumvalueprivate.h" |
31 | #include "gtkcssimagevalueprivate.h" |
32 | #include "gtkcssnumbervalueprivate.h" |
33 | #include "gtkcssshadowvalueprivate.h" |
34 | #include "gtkcsspositionvalueprivate.h" |
35 | #include "gtkcssrepeatvalueprivate.h" |
36 | #include "gtkcsscolorvalueprivate.h" |
37 | #include "gtkcssstyleprivate.h" |
38 | #include "gtkcsstypesprivate.h" |
39 | |
40 | #include <math.h> |
41 | |
42 | #include <gdk/gdk.h> |
43 | |
44 | #include "gsk/gskroundedrectprivate.h" |
45 | |
46 | static void |
47 | gtk_theming_background_snapshot_color (GtkCssBoxes *boxes, |
48 | GtkSnapshot *snapshot, |
49 | const GdkRGBA *bg_color, |
50 | guint n_bg_values) |
51 | { |
52 | const GskRoundedRect *box; |
53 | GtkCssArea clip; |
54 | |
55 | clip = _gtk_css_area_value_get (value: _gtk_css_array_value_get_nth (value: boxes->style->background->background_clip, i: n_bg_values - 1)); |
56 | box = gtk_css_boxes_get_box (boxes, area: clip); |
57 | |
58 | if (gsk_rounded_rect_is_rectilinear (self: box)) |
59 | { |
60 | gtk_snapshot_append_color (snapshot, |
61 | color: bg_color, |
62 | bounds: &box->bounds); |
63 | } |
64 | else |
65 | { |
66 | gtk_snapshot_push_rounded_clip (snapshot, bounds: box); |
67 | gtk_snapshot_append_color (snapshot, |
68 | color: bg_color, |
69 | bounds: &box->bounds); |
70 | gtk_snapshot_pop (snapshot); |
71 | } |
72 | } |
73 | |
74 | static void |
75 | gtk_theming_background_snapshot_layer (GtkCssBoxes *bg, |
76 | guint idx, |
77 | GtkSnapshot *snapshot) |
78 | { |
79 | GtkCssBackgroundValues *background = bg->style->background; |
80 | GtkCssRepeatStyle hrepeat, vrepeat; |
81 | const GtkCssValue *pos, *repeat; |
82 | GtkCssImage *image; |
83 | const GskRoundedRect *origin, *clip; |
84 | double image_width, image_height; |
85 | double width, height; |
86 | double x, y; |
87 | |
88 | image = _gtk_css_image_value_get_image (image: _gtk_css_array_value_get_nth (value: background->background_image, i: idx)); |
89 | |
90 | if (image == NULL) |
91 | return; |
92 | |
93 | pos = _gtk_css_array_value_get_nth (value: background->background_position, i: idx); |
94 | repeat = _gtk_css_array_value_get_nth (value: background->background_repeat, i: idx); |
95 | |
96 | origin = gtk_css_boxes_get_box (boxes: bg, |
97 | area: _gtk_css_area_value_get ( |
98 | value: _gtk_css_array_value_get_nth (value: background->background_origin, i: idx))); |
99 | |
100 | width = origin->bounds.size.width; |
101 | height = origin->bounds.size.height; |
102 | |
103 | if (width <= 0 || height <= 0) |
104 | return; |
105 | |
106 | clip = gtk_css_boxes_get_box (boxes: bg, |
107 | area: _gtk_css_area_value_get ( |
108 | value: _gtk_css_array_value_get_nth (value: background->background_clip, i: idx))); |
109 | |
110 | _gtk_css_bg_size_value_compute_size (bg_size: _gtk_css_array_value_get_nth (value: background->background_size, i: idx), |
111 | image, |
112 | area_width: width, |
113 | area_height: height, |
114 | out_width: &image_width, |
115 | out_height: &image_height); |
116 | |
117 | if (image_width <= 0 || image_height <= 0) |
118 | return; |
119 | |
120 | /* optimization */ |
121 | if (image_width == width) |
122 | hrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT; |
123 | else |
124 | hrepeat = _gtk_css_background_repeat_value_get_x (repeat); |
125 | |
126 | if (image_height == height) |
127 | vrepeat = GTK_CSS_REPEAT_STYLE_NO_REPEAT; |
128 | else |
129 | vrepeat = _gtk_css_background_repeat_value_get_y (repeat); |
130 | |
131 | gtk_snapshot_push_debug (snapshot, message: "Layer %u" , idx); |
132 | gtk_snapshot_push_rounded_clip (snapshot, bounds: clip); |
133 | |
134 | x = _gtk_css_position_value_get_x (position: pos, one_hundred_percent: width - image_width) + origin->bounds.origin.x; |
135 | y = _gtk_css_position_value_get_y (position: pos, one_hundred_percent: height - image_height) + origin->bounds.origin.y; |
136 | if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT && vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT) |
137 | { |
138 | /* shortcut for normal case */ |
139 | if (x != 0 || y != 0) |
140 | { |
141 | gtk_snapshot_save (snapshot); |
142 | gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y)); |
143 | gtk_css_image_snapshot (image, snapshot, width: image_width, height: image_height); |
144 | gtk_snapshot_restore (snapshot); |
145 | } |
146 | else |
147 | { |
148 | gtk_css_image_snapshot (image, snapshot, width: image_width, height: image_height); |
149 | } |
150 | } |
151 | else |
152 | { |
153 | float repeat_width, repeat_height; |
154 | graphene_rect_t fill_rect; |
155 | |
156 | /* If ‘background-repeat’ is ‘round’ for one (or both) dimensions, |
157 | * there is a second step. The UA must scale the image in that |
158 | * dimension (or both dimensions) so that it fits a whole number of |
159 | * times in the background positioning area. In the case of the width |
160 | * (height is analogous): |
161 | * |
162 | * If X ≠ 0 is the width of the image after step one and W is the width |
163 | * of the background positioning area, then the rounded width |
164 | * X' = W / round(W / X) where round() is a function that returns the |
165 | * nearest natural number (integer greater than zero). |
166 | * |
167 | * If ‘background-repeat’ is ‘round’ for one dimension only and if |
168 | * ‘background-size’ is ‘auto’ for the other dimension, then there is |
169 | * a third step: that other dimension is scaled so that the original |
170 | * aspect ratio is restored. |
171 | */ |
172 | if (hrepeat == GTK_CSS_REPEAT_STYLE_ROUND) |
173 | { |
174 | double n = round (x: width / image_width); |
175 | |
176 | n = MAX (1, n); |
177 | |
178 | if (vrepeat != GTK_CSS_REPEAT_STYLE_ROUND |
179 | /* && vsize == auto (it is by default) */) |
180 | image_height *= width / (image_width * n); |
181 | image_width = width / n; |
182 | } |
183 | if (vrepeat == GTK_CSS_REPEAT_STYLE_ROUND) |
184 | { |
185 | double n = round (x: height / image_height); |
186 | |
187 | n = MAX (1, n); |
188 | |
189 | if (hrepeat != GTK_CSS_REPEAT_STYLE_ROUND |
190 | /* && hsize == auto (it is by default) */) |
191 | image_width *= height / (image_height * n); |
192 | image_height = height / n; |
193 | } |
194 | |
195 | /* if hrepeat or vrepeat is 'space', we create a somewhat larger surface |
196 | * to store the extra space. */ |
197 | if (hrepeat == GTK_CSS_REPEAT_STYLE_SPACE) |
198 | { |
199 | double n = floor (x: width / image_width); |
200 | repeat_width = n ? round (x: width / n) : 0; |
201 | } |
202 | else |
203 | repeat_width = round (x: image_width); |
204 | |
205 | if (vrepeat == GTK_CSS_REPEAT_STYLE_SPACE) |
206 | { |
207 | double n = floor (x: height / image_height); |
208 | repeat_height = n ? round (x: height / n) : 0; |
209 | } |
210 | else |
211 | repeat_height = round (x: image_height); |
212 | |
213 | fill_rect = clip->bounds; |
214 | if (hrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT) |
215 | { |
216 | fill_rect.origin.x = _gtk_css_position_value_get_x (position: pos, one_hundred_percent: width - image_width); |
217 | fill_rect.size.width = image_width; |
218 | } |
219 | if (vrepeat == GTK_CSS_REPEAT_STYLE_NO_REPEAT) |
220 | { |
221 | fill_rect.origin.y = _gtk_css_position_value_get_y (position: pos, one_hundred_percent: height - image_height); |
222 | fill_rect.size.height = image_height; |
223 | } |
224 | |
225 | gtk_snapshot_push_repeat (snapshot, |
226 | bounds: &fill_rect, |
227 | child_bounds: &GRAPHENE_RECT_INIT ( |
228 | x, y, |
229 | repeat_width, repeat_height |
230 | )); |
231 | |
232 | gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT ( |
233 | x + 0.5 * (repeat_width - image_width), |
234 | y + 0.5 * (repeat_height - image_height))); |
235 | gtk_css_image_snapshot (image, snapshot, width: image_width, height: image_height); |
236 | |
237 | gtk_snapshot_pop (snapshot); |
238 | } |
239 | |
240 | gtk_snapshot_pop (snapshot); |
241 | gtk_snapshot_pop (snapshot); |
242 | } |
243 | |
244 | void |
245 | gtk_css_style_snapshot_background (GtkCssBoxes *boxes, |
246 | GtkSnapshot *snapshot) |
247 | { |
248 | const GtkCssBackgroundValues *background = boxes->style->background; |
249 | GtkCssValue *background_image; |
250 | const GdkRGBA *bg_color; |
251 | const GtkCssValue *box_shadow; |
252 | gboolean has_bg_color; |
253 | gboolean has_bg_image; |
254 | gboolean has_shadow; |
255 | int idx; |
256 | guint number_of_layers; |
257 | |
258 | if (background->base.type == GTK_CSS_BACKGROUND_INITIAL_VALUES) |
259 | return; |
260 | |
261 | background_image = background->background_image; |
262 | bg_color = gtk_css_color_value_get_rgba (color: background->background_color); |
263 | box_shadow = background->box_shadow; |
264 | |
265 | has_bg_color = !gdk_rgba_is_clear (rgba: bg_color); |
266 | has_bg_image = _gtk_css_image_value_get_image (image: _gtk_css_array_value_get_nth (value: background_image, i: 0)) != NULL; |
267 | has_shadow = !gtk_css_shadow_value_is_none (shadow: box_shadow); |
268 | |
269 | /* This is the common default case of no background */ |
270 | if (!has_bg_color && !has_bg_image && !has_shadow) |
271 | return; |
272 | |
273 | gtk_snapshot_push_debug (snapshot, message: "CSS background" ); |
274 | |
275 | if (has_shadow) |
276 | gtk_css_shadow_value_snapshot_outset (shadow: box_shadow, |
277 | snapshot, |
278 | border_box: gtk_css_boxes_get_border_box (boxes)); |
279 | |
280 | number_of_layers = _gtk_css_array_value_get_n_values (value: background_image); |
281 | |
282 | if (has_bg_image) |
283 | { |
284 | GtkCssValue *blend_modes = background->background_blend_mode; |
285 | GskBlendMode *blend_mode_values = g_alloca (sizeof (GskBlendMode) * number_of_layers); |
286 | |
287 | for (idx = number_of_layers - 1; idx >= 0; idx--) |
288 | { |
289 | blend_mode_values[idx] = _gtk_css_blend_mode_value_get (value: _gtk_css_array_value_get_nth (value: blend_modes, i: idx)); |
290 | |
291 | if (blend_mode_values[idx] != GSK_BLEND_MODE_DEFAULT) |
292 | gtk_snapshot_push_blend (snapshot, blend_mode: blend_mode_values[idx]); |
293 | } |
294 | |
295 | if (has_bg_color) |
296 | gtk_theming_background_snapshot_color (boxes, snapshot, bg_color, n_bg_values: number_of_layers); |
297 | |
298 | for (idx = number_of_layers - 1; idx >= 0; idx--) |
299 | { |
300 | if (blend_mode_values[idx] == GSK_BLEND_MODE_DEFAULT) |
301 | { |
302 | gtk_theming_background_snapshot_layer (bg: boxes, idx, snapshot); |
303 | } |
304 | else |
305 | { |
306 | gtk_snapshot_pop (snapshot); |
307 | gtk_theming_background_snapshot_layer (bg: boxes, idx, snapshot); |
308 | gtk_snapshot_pop (snapshot); |
309 | } |
310 | } |
311 | } |
312 | else if (has_bg_color) |
313 | { |
314 | gtk_theming_background_snapshot_color (boxes, snapshot, bg_color, n_bg_values: number_of_layers); |
315 | } |
316 | |
317 | if (has_shadow) |
318 | gtk_css_shadow_value_snapshot_inset (shadow: box_shadow, |
319 | snapshot, |
320 | padding_box: gtk_css_boxes_get_padding_box (boxes)); |
321 | |
322 | gtk_snapshot_pop (snapshot); |
323 | } |
324 | |
325 | |