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
36typedef struct _GtkNativePrivate
37{
38 gulong update_handler_id;
39 gulong layout_handler_id;
40 gulong scale_changed_handler_id;
41} GtkNativePrivate;
42
43static 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
65G_DEFINE_INTERFACE (GtkNative, gtk_native, GTK_TYPE_WIDGET)
66
67static GskRenderer *
68gtk_native_default_get_renderer (GtkNative *self)
69{
70 return NULL;
71}
72
73static void
74gtk_native_default_get_surface_transform (GtkNative *self,
75 double *x,
76 double *y)
77{
78 *x = 0;
79 *y = 0;
80}
81
82static void
83gtk_native_default_layout (GtkNative *self,
84 int width,
85 int height)
86{
87}
88
89static void
90gtk_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
99static void
100frame_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
107static void
108gtk_native_layout (GtkNative *self,
109 int width,
110 int height)
111{
112 GTK_NATIVE_GET_IFACE (ptr: self)->layout (self, width, height);
113}
114
115static void
116surface_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
127static void
128scale_changed_cb (GdkSurface *surface,
129 GParamSpec *pspec,
130 GtkNative *native)
131{
132 _gtk_widget_scale_changed (GTK_WIDGET (native));
133}
134
135static void
136verify_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 */
155void
156gtk_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 */
195void
196gtk_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 */
224GdkSurface *
225gtk_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 */
240GskRenderer *
241gtk_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 */
259void
260gtk_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 */
279GtkNative *
280gtk_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
292void
293gtk_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
308static void
309corner_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
316static void
317subtract_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
344static int
345get_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
358static void
359get_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
379static gboolean
380get_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
408static void
409get_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
424void
425gtk_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

source code of gtk/gtk/gtknative.c