1 | /* |
2 | * Copyright © 2019 Red Hat, Inc. |
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.1 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 | * Authors: Matthias Clasen <mclasen@redhat.com> |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #include <math.h> |
23 | |
24 | #include "gtkintl.h" |
25 | |
26 | #include "gtkcssboxesimplprivate.h" |
27 | #include "gtkcsscolorvalueprivate.h" |
28 | #include "gtkcsscornervalueprivate.h" |
29 | #include "gtkcssnodeprivate.h" |
30 | #include "gtkcssshadowvalueprivate.h" |
31 | #include "gtknativeprivate.h" |
32 | #include "gtkwidgetprivate.h" |
33 | |
34 | #include "gdk/gdksurfaceprivate.h" |
35 | |
36 | typedef struct _GtkNativePrivate |
37 | { |
38 | gulong update_handler_id; |
39 | gulong layout_handler_id; |
40 | gulong scale_changed_handler_id; |
41 | } GtkNativePrivate; |
42 | |
43 | static GQuark quark_gtk_native_private; |
44 | |
45 | /** |
46 | * GtkNative: |
47 | * |
48 | * `GtkNative` is the interface implemented by all widgets that have |
49 | * their own `GdkSurface`. |
50 | * |
51 | * The obvious example of a `GtkNative` is `GtkWindow`. |
52 | * |
53 | * Every widget that is not itself a `GtkNative` is contained in one, |
54 | * and you can get it with [method@Gtk.Widget.get_native]. |
55 | * |
56 | * To get the surface of a `GtkNative`, use [method@Gtk.Native.get_surface]. |
57 | * It is also possible to find the `GtkNative` to which a surface |
58 | * belongs, with [func@Gtk.Native.get_for_surface]. |
59 | * |
60 | * In addition to a [class@Gdk.Surface], a `GtkNative` also provides |
61 | * a [class@Gsk.Renderer] for rendering on that surface. To get the |
62 | * renderer, use [method@Gtk.Native.get_renderer]. |
63 | */ |
64 | |
65 | G_DEFINE_INTERFACE (GtkNative, gtk_native, GTK_TYPE_WIDGET) |
66 | |
67 | static GskRenderer * |
68 | gtk_native_default_get_renderer (GtkNative *self) |
69 | { |
70 | return NULL; |
71 | } |
72 | |
73 | static void |
74 | gtk_native_default_get_surface_transform (GtkNative *self, |
75 | double *x, |
76 | double *y) |
77 | { |
78 | *x = 0; |
79 | *y = 0; |
80 | } |
81 | |
82 | static void |
83 | gtk_native_default_layout (GtkNative *self, |
84 | int width, |
85 | int height) |
86 | { |
87 | } |
88 | |
89 | static void |
90 | gtk_native_default_init (GtkNativeInterface *iface) |
91 | { |
92 | iface->get_renderer = gtk_native_default_get_renderer; |
93 | iface->get_surface_transform = gtk_native_default_get_surface_transform; |
94 | iface->layout = gtk_native_default_layout; |
95 | |
96 | quark_gtk_native_private = g_quark_from_static_string (string: "gtk-native-private" ); |
97 | } |
98 | |
99 | static void |
100 | frame_clock_update_cb (GdkFrameClock *clock, |
101 | GtkNative *native) |
102 | { |
103 | if (GTK_IS_ROOT (ptr: native)) |
104 | gtk_css_node_validate (cssnode: gtk_widget_get_css_node (GTK_WIDGET (native))); |
105 | } |
106 | |
107 | static void |
108 | gtk_native_layout (GtkNative *self, |
109 | int width, |
110 | int height) |
111 | { |
112 | GTK_NATIVE_GET_IFACE (ptr: self)->layout (self, width, height); |
113 | } |
114 | |
115 | static void |
116 | surface_layout_cb (GdkSurface *surface, |
117 | int width, |
118 | int height, |
119 | GtkNative *native) |
120 | { |
121 | gtk_native_layout (self: native, width, height); |
122 | |
123 | if (gtk_widget_needs_allocate (GTK_WIDGET (native))) |
124 | gtk_native_queue_relayout (native); |
125 | } |
126 | |
127 | static void |
128 | scale_changed_cb (GdkSurface *surface, |
129 | GParamSpec *pspec, |
130 | GtkNative *native) |
131 | { |
132 | _gtk_widget_scale_changed (GTK_WIDGET (native)); |
133 | } |
134 | |
135 | static void |
136 | verify_priv_unrealized (gpointer user_data) |
137 | { |
138 | GtkNativePrivate *priv = user_data; |
139 | |
140 | g_warn_if_fail (priv->update_handler_id == 0); |
141 | g_warn_if_fail (priv->layout_handler_id == 0); |
142 | g_warn_if_fail (priv->scale_changed_handler_id == 0); |
143 | |
144 | g_free (mem: priv); |
145 | } |
146 | |
147 | /** |
148 | * gtk_native_realize: |
149 | * @self: a `GtkNative` |
150 | * |
151 | * Realizes a `GtkNative`. |
152 | * |
153 | * This should only be used by subclasses. |
154 | */ |
155 | void |
156 | gtk_native_realize (GtkNative *self) |
157 | { |
158 | GdkSurface *surface; |
159 | GdkFrameClock *clock; |
160 | GtkNativePrivate *priv; |
161 | |
162 | g_return_if_fail (g_object_get_qdata (G_OBJECT (self), |
163 | quark_gtk_native_private) == NULL); |
164 | |
165 | surface = gtk_native_get_surface (self); |
166 | clock = gdk_surface_get_frame_clock (surface); |
167 | g_return_if_fail (clock != NULL); |
168 | |
169 | priv = g_new0 (GtkNativePrivate, 1); |
170 | priv->update_handler_id = g_signal_connect_after (clock, "update" , |
171 | G_CALLBACK (frame_clock_update_cb), |
172 | self); |
173 | priv->layout_handler_id = g_signal_connect (surface, "layout" , |
174 | G_CALLBACK (surface_layout_cb), |
175 | self); |
176 | |
177 | priv->scale_changed_handler_id = g_signal_connect (surface, "notify::scale-factor" , |
178 | G_CALLBACK (scale_changed_cb), |
179 | self); |
180 | |
181 | g_object_set_qdata_full (G_OBJECT (self), |
182 | quark: quark_gtk_native_private, |
183 | data: priv, |
184 | destroy: verify_priv_unrealized); |
185 | } |
186 | |
187 | /** |
188 | * gtk_native_unrealize: |
189 | * @self: a `GtkNative` |
190 | * |
191 | * Unrealizes a `GtkNative`. |
192 | * |
193 | * This should only be used by subclasses. |
194 | */ |
195 | void |
196 | gtk_native_unrealize (GtkNative *self) |
197 | { |
198 | GtkNativePrivate *priv; |
199 | GdkSurface *surface; |
200 | GdkFrameClock *clock; |
201 | |
202 | priv = g_object_get_qdata (G_OBJECT (self), quark: quark_gtk_native_private); |
203 | g_return_if_fail (priv != NULL); |
204 | |
205 | surface = gtk_native_get_surface (self); |
206 | clock = gdk_surface_get_frame_clock (surface); |
207 | g_return_if_fail (clock != NULL); |
208 | |
209 | g_clear_signal_handler (&priv->update_handler_id, clock); |
210 | g_clear_signal_handler (&priv->layout_handler_id, surface); |
211 | g_clear_signal_handler (&priv->scale_changed_handler_id, surface); |
212 | |
213 | g_object_set_qdata (G_OBJECT (self), quark: quark_gtk_native_private, NULL); |
214 | } |
215 | |
216 | /** |
217 | * gtk_native_get_surface: |
218 | * @self: a `GtkNative` |
219 | * |
220 | * Returns the surface of this `GtkNative`. |
221 | * |
222 | * Returns: (transfer none): the surface of @self |
223 | */ |
224 | GdkSurface * |
225 | gtk_native_get_surface (GtkNative *self) |
226 | { |
227 | g_return_val_if_fail (GTK_IS_NATIVE (self), NULL); |
228 | |
229 | return GTK_NATIVE_GET_IFACE (ptr: self)->get_surface (self); |
230 | } |
231 | |
232 | /** |
233 | * gtk_native_get_renderer: |
234 | * @self: a `GtkNative` |
235 | * |
236 | * Returns the renderer that is used for this `GtkNative`. |
237 | * |
238 | * Returns: (transfer none): the renderer for @self |
239 | */ |
240 | GskRenderer * |
241 | gtk_native_get_renderer (GtkNative *self) |
242 | { |
243 | g_return_val_if_fail (GTK_IS_NATIVE (self), NULL); |
244 | |
245 | return GTK_NATIVE_GET_IFACE (ptr: self)->get_renderer (self); |
246 | } |
247 | |
248 | /** |
249 | * gtk_native_get_surface_transform: |
250 | * @self: a `GtkNative` |
251 | * @x: (out): return location for the x coordinate |
252 | * @y: (out): return location for the y coordinate |
253 | * |
254 | * Retrieves the surface transform of @self. |
255 | * |
256 | * This is the translation from @self's surface coordinates into |
257 | * @self's widget coordinates. |
258 | */ |
259 | void |
260 | gtk_native_get_surface_transform (GtkNative *self, |
261 | double *x, |
262 | double *y) |
263 | { |
264 | g_return_if_fail (GTK_IS_NATIVE (self)); |
265 | g_return_if_fail (x != NULL); |
266 | g_return_if_fail (y != NULL); |
267 | |
268 | GTK_NATIVE_GET_IFACE (ptr: self)->get_surface_transform (self, x, y); |
269 | } |
270 | |
271 | /** |
272 | * gtk_native_get_for_surface: |
273 | * @surface: a `GdkSurface` |
274 | * |
275 | * Finds the `GtkNative` associated with the surface. |
276 | * |
277 | * Returns: (transfer none) (nullable): the `GtkNative` that is associated with @surface |
278 | */ |
279 | GtkNative * |
280 | gtk_native_get_for_surface (GdkSurface *surface) |
281 | { |
282 | GtkWidget *widget; |
283 | |
284 | widget = (GtkWidget *)gdk_surface_get_widget (self: surface); |
285 | |
286 | if (widget && GTK_IS_NATIVE (ptr: widget)) |
287 | return GTK_NATIVE (ptr: widget); |
288 | |
289 | return NULL; |
290 | } |
291 | |
292 | void |
293 | gtk_native_queue_relayout (GtkNative *self) |
294 | { |
295 | GtkWidget *widget = GTK_WIDGET (self); |
296 | GdkSurface *surface; |
297 | GdkFrameClock *clock; |
298 | |
299 | surface = gtk_widget_get_surface (widget); |
300 | clock = gtk_widget_get_frame_clock (widget); |
301 | if (clock == NULL) |
302 | return; |
303 | |
304 | gdk_frame_clock_request_phase (frame_clock: clock, phase: GDK_FRAME_CLOCK_PHASE_UPDATE); |
305 | gdk_surface_request_layout (surface); |
306 | } |
307 | |
308 | static void |
309 | corner_rect (const GtkCssValue *value, |
310 | cairo_rectangle_int_t *rect) |
311 | { |
312 | rect->width = _gtk_css_corner_value_get_x (corner: value, one_hundred_percent: 100); |
313 | rect->height = _gtk_css_corner_value_get_y (corner: value, one_hundred_percent: 100); |
314 | } |
315 | |
316 | static void |
317 | subtract_decoration_corners_from_region (cairo_region_t *region, |
318 | cairo_rectangle_int_t *extents, |
319 | const GtkCssStyle *style) |
320 | { |
321 | cairo_rectangle_int_t rect; |
322 | |
323 | corner_rect (value: style->border->border_top_left_radius, rect: &rect); |
324 | rect.x = extents->x; |
325 | rect.y = extents->y; |
326 | cairo_region_subtract_rectangle (dst: region, rectangle: &rect); |
327 | |
328 | corner_rect (value: style->border->border_top_right_radius, rect: &rect); |
329 | rect.x = extents->x + extents->width - rect.width; |
330 | rect.y = extents->y; |
331 | cairo_region_subtract_rectangle (dst: region, rectangle: &rect); |
332 | |
333 | corner_rect (value: style->border->border_bottom_left_radius, rect: &rect); |
334 | rect.x = extents->x; |
335 | rect.y = extents->y + extents->height - rect.height; |
336 | cairo_region_subtract_rectangle (dst: region, rectangle: &rect); |
337 | |
338 | corner_rect (value: style->border->border_bottom_right_radius, rect: &rect); |
339 | rect.x = extents->x + extents->width - rect.width; |
340 | rect.y = extents->y + extents->height - rect.height; |
341 | cairo_region_subtract_rectangle (dst: region, rectangle: &rect); |
342 | } |
343 | |
344 | static int |
345 | get_translucent_border_edge (const GtkCssValue *color, |
346 | const GtkCssValue *border_color, |
347 | const GtkCssValue *border_width) |
348 | { |
349 | if (border_color == NULL) |
350 | border_color = color; |
351 | |
352 | if (!gdk_rgba_is_opaque (rgba: gtk_css_color_value_get_rgba (color: border_color))) |
353 | return round (x: _gtk_css_number_value_get (number: border_width, one_hundred_percent: 100)); |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | static void |
359 | get_translucent_border_width (GtkWidget *widget, |
360 | GtkBorder *border) |
361 | { |
362 | GtkCssNode *css_node = gtk_widget_get_css_node (widget); |
363 | GtkCssStyle *style = gtk_css_node_get_style (cssnode: css_node); |
364 | |
365 | border->top = get_translucent_border_edge (color: style->core->color, |
366 | border_color: style->border->border_top_color, |
367 | border_width: style->border->border_top_width); |
368 | border->bottom = get_translucent_border_edge (color: style->core->color, |
369 | border_color: style->border->border_bottom_color, |
370 | border_width: style->border->border_bottom_width); |
371 | border->left = get_translucent_border_edge (color: style->core->color, |
372 | border_color: style->border->border_left_color, |
373 | border_width: style->border->border_left_width); |
374 | border->right = get_translucent_border_edge (color: style->core->color, |
375 | border_color: style->border->border_right_color, |
376 | border_width: style->border->border_right_width); |
377 | } |
378 | |
379 | static gboolean |
380 | get_opaque_rect (GtkWidget *widget, |
381 | const GtkCssStyle *style, |
382 | cairo_rectangle_int_t *rect) |
383 | { |
384 | gboolean is_opaque = gdk_rgba_is_opaque (rgba: gtk_css_color_value_get_rgba (color: style->background->background_color)); |
385 | |
386 | if (is_opaque && gtk_widget_get_opacity (widget) < 1.0) |
387 | is_opaque = FALSE; |
388 | |
389 | if (is_opaque) |
390 | { |
391 | const graphene_rect_t *border_rect; |
392 | GtkCssBoxes css_boxes; |
393 | GtkBorder border; |
394 | |
395 | gtk_css_boxes_init (boxes: &css_boxes, widget); |
396 | border_rect = gtk_css_boxes_get_border_rect (boxes: &css_boxes); |
397 | get_translucent_border_width (widget, border: &border); |
398 | |
399 | rect->x = border_rect->origin.x + border.left; |
400 | rect->y = border_rect->origin.y + border.top; |
401 | rect->width = border_rect->size.width - border.left - border.right; |
402 | rect->height = border_rect->size.height - border.top - border.bottom; |
403 | } |
404 | |
405 | return is_opaque; |
406 | } |
407 | |
408 | static void |
409 | get_shadow_width (GtkWidget *widget, |
410 | GtkBorder *shadow_width, |
411 | int resize_handle_size) |
412 | { |
413 | GtkCssNode *css_node = gtk_widget_get_css_node (widget); |
414 | const GtkCssStyle *style = gtk_css_node_get_style (cssnode: css_node); |
415 | |
416 | gtk_css_shadow_value_get_extents (shadow: style->background->box_shadow, border: shadow_width); |
417 | |
418 | shadow_width->left = MAX (shadow_width->left, resize_handle_size); |
419 | shadow_width->top = MAX (shadow_width->top, resize_handle_size); |
420 | shadow_width->bottom = MAX (shadow_width->bottom, resize_handle_size); |
421 | shadow_width->right = MAX (shadow_width->right, resize_handle_size); |
422 | } |
423 | |
424 | void |
425 | gtk_native_update_opaque_region (GtkNative *native, |
426 | GtkWidget *contents, |
427 | gboolean subtract_decoration_corners, |
428 | gboolean subtract_shadow, |
429 | int resize_handle_size) |
430 | { |
431 | cairo_rectangle_int_t rect; |
432 | cairo_region_t *opaque_region = NULL; |
433 | const GtkCssStyle *style; |
434 | GtkCssNode *css_node; |
435 | GdkSurface *surface; |
436 | GtkBorder shadow; |
437 | |
438 | g_return_if_fail (GTK_IS_NATIVE (native)); |
439 | g_return_if_fail (!contents || GTK_IS_WIDGET (contents)); |
440 | |
441 | if (contents == NULL) |
442 | contents = GTK_WIDGET (native); |
443 | |
444 | if (!_gtk_widget_get_realized (GTK_WIDGET (native)) || |
445 | !_gtk_widget_get_realized (widget: contents)) |
446 | return; |
447 | |
448 | css_node = gtk_widget_get_css_node (widget: contents); |
449 | |
450 | if (subtract_shadow) |
451 | get_shadow_width (widget: contents, shadow_width: &shadow, resize_handle_size); |
452 | else |
453 | shadow = (GtkBorder) {0, 0, 0, 0}; |
454 | |
455 | surface = gtk_native_get_surface (self: native); |
456 | style = gtk_css_node_get_style (cssnode: css_node); |
457 | |
458 | if (get_opaque_rect (widget: contents, style, rect: &rect)) |
459 | { |
460 | double native_x, native_y; |
461 | |
462 | gtk_native_get_surface_transform (self: native, x: &native_x, y: &native_y); |
463 | rect.x += native_x; |
464 | rect.y += native_y; |
465 | |
466 | if (contents != GTK_WIDGET (native)) |
467 | { |
468 | double contents_x, contents_y; |
469 | |
470 | gtk_widget_translate_coordinates (src_widget: contents, GTK_WIDGET (native), src_x: 0, src_y: 0, dest_x: &contents_x, dest_y: &contents_y); |
471 | rect.x += contents_x; |
472 | rect.y += contents_y; |
473 | } |
474 | |
475 | opaque_region = cairo_region_create_rectangle (rectangle: &rect); |
476 | |
477 | if (subtract_decoration_corners) |
478 | subtract_decoration_corners_from_region (region: opaque_region, extents: &rect, style); |
479 | } |
480 | |
481 | gdk_surface_set_opaque_region (surface, region: opaque_region); |
482 | |
483 | cairo_region_destroy (region: opaque_region); |
484 | } |
485 | |