1 /*
2 * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1+
5 */
6
7 #include "config.h"
8 #include <glib/gi18n-lib.h>
9
10 #include "hdy-shadow-helper-private.h"
11 #include "hdy-style-private.h"
12
13 #include <math.h>
14
15 /**
16 * PRIVATE:hdy-shadow-helper
17 * @short_description: Shadow helper used in #HdyLeaflet
18 * @title: HdyShadowHelper
19 * @See_also: #HdyLeaflet
20 * @stability: Private
21 *
22 * A helper class for drawing #HdyLeaflet transition shadow.
23 *
24 * Since: 0.0.12
25 */
26
27 struct _HdyShadowHelper
28 {
29 GObject parent_instance;
30
31 GtkWidget *widget;
32 gchar *css_path;
33 GtkCssProvider *provider;
34
35 gboolean is_cache_valid;
36
37 cairo_pattern_t *dimming_pattern;
38 cairo_pattern_t *shadow_pattern;
39 cairo_pattern_t *border_pattern;
40 gint shadow_size;
41 gint border_size;
42
43 GtkPanDirection last_direction;
44 gint last_width;
45 gint last_height;
46 gint last_scale;
47 };
48
49 G_DEFINE_TYPE (HdyShadowHelper, hdy_shadow_helper, G_TYPE_OBJECT);
50
51 enum {
52 PROP_0,
53 PROP_WIDGET,
54 PROP_CSS_PATH,
55 LAST_PROP,
56 };
57
58 static GParamSpec *props[LAST_PROP];
59
60
61 static GtkStyleContext *
create_context(HdyShadowHelper * self,const gchar * name,GtkPanDirection direction)62 create_context (HdyShadowHelper *self,
63 const gchar *name,
64 GtkPanDirection direction)
65 {
66 g_autoptr(GtkWidgetPath) path = NULL;
67 GtkStyleContext *context;
68 gint pos;
69 const gchar *direction_name;
70 GEnumClass *enum_class;
71
72 enum_class = g_type_class_ref (GTK_TYPE_PAN_DIRECTION);
73 direction_name = g_enum_get_value (enum_class, direction)->value_nick;
74
75 path = gtk_widget_path_copy (gtk_widget_get_path (self->widget));
76
77 pos = gtk_widget_path_append_type (path, GTK_TYPE_WIDGET);
78 gtk_widget_path_iter_set_object_name (path, pos, name);
79
80 gtk_widget_path_iter_add_class (path, pos, direction_name);
81
82 context = gtk_style_context_new ();
83 gtk_style_context_set_path (context, path);
84 gtk_style_context_set_parent (context,
85 gtk_widget_get_style_context (self->widget));
86
87 gtk_style_context_add_provider (context,
88 GTK_STYLE_PROVIDER (self->provider),
89 HDY_STYLE_PROVIDER_PRIORITY);
90
91 g_type_class_unref (enum_class);
92
93 return context;
94 }
95
96 static gint
get_element_size(GtkStyleContext * context,GtkPanDirection direction)97 get_element_size (GtkStyleContext *context,
98 GtkPanDirection direction)
99 {
100 gint width, height;
101
102 gtk_style_context_get (context,
103 gtk_style_context_get_state (context),
104 "min-width", &width,
105 "min-height", &height,
106 NULL);
107
108 switch (direction) {
109 case GTK_PAN_DIRECTION_LEFT:
110 case GTK_PAN_DIRECTION_RIGHT:
111 return width;
112 case GTK_PAN_DIRECTION_UP:
113 case GTK_PAN_DIRECTION_DOWN:
114 return height;
115 default:
116 g_assert_not_reached ();
117 }
118
119 return 0;
120 }
121
122 static cairo_pattern_t *
create_element_pattern(GtkStyleContext * context,gint width,gint height)123 create_element_pattern (GtkStyleContext *context,
124 gint width,
125 gint height)
126 {
127 cairo_surface_t *surface;
128 cairo_t *cr;
129 cairo_pattern_t *pattern;
130
131 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
132 cr = cairo_create (surface);
133
134 gtk_render_background (context, cr, 0, 0, width, height);
135 gtk_render_frame (context, cr, 0, 0, width, height);
136
137 pattern = cairo_pattern_create_for_surface (surface);
138 cairo_destroy (cr);
139 cairo_surface_destroy (surface);
140
141 return pattern;
142 }
143
144 static void
cache_shadow(HdyShadowHelper * self,gint width,gint height,GtkPanDirection direction)145 cache_shadow (HdyShadowHelper *self,
146 gint width,
147 gint height,
148 GtkPanDirection direction)
149 {
150 g_autoptr(GtkStyleContext) dim_context = NULL;
151 g_autoptr(GtkStyleContext) shadow_context = NULL;
152 g_autoptr(GtkStyleContext) border_context = NULL;
153 gint shadow_size, border_size, scale;
154
155 scale = gtk_widget_get_scale_factor (self->widget);
156
157 if (self->last_direction == direction &&
158 self->last_width == width &&
159 self->last_height == height &&
160 self->last_scale == scale &&
161 self->is_cache_valid)
162 return;
163
164 hdy_shadow_helper_clear_cache (self);
165
166 dim_context = create_context (self, "dimming", direction);
167 shadow_context = create_context (self, "shadow", direction);
168 border_context = create_context (self, "border", direction);
169
170 shadow_size = get_element_size (shadow_context, direction);
171 border_size = get_element_size (border_context, direction);
172
173 self->dimming_pattern = create_element_pattern (dim_context, width, height);
174 if (direction == GTK_PAN_DIRECTION_LEFT || direction == GTK_PAN_DIRECTION_RIGHT) {
175 self->shadow_pattern = create_element_pattern (shadow_context, shadow_size, height);
176 self->border_pattern = create_element_pattern (border_context, border_size, height);
177 } else {
178 self->shadow_pattern = create_element_pattern (shadow_context, width, shadow_size);
179 self->border_pattern = create_element_pattern (border_context, width, border_size);
180 }
181
182 self->border_size = border_size;
183 self->shadow_size = shadow_size;
184
185 self->is_cache_valid = TRUE;
186 self->last_direction = direction;
187 self->last_width = width;
188 self->last_height = height;
189 self->last_scale = scale;
190 }
191
192 static void
hdy_shadow_helper_constructed(GObject * object)193 hdy_shadow_helper_constructed (GObject *object)
194 {
195 HdyShadowHelper *self = HDY_SHADOW_HELPER (object);
196
197 self->provider = gtk_css_provider_new ();
198 gtk_css_provider_load_from_resource (self->provider,
199 "/sm/puri/handy/style/hdy-leaflet.css");
200
201 G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->constructed (object);
202 }
203
204 static void
hdy_shadow_helper_dispose(GObject * object)205 hdy_shadow_helper_dispose (GObject *object)
206 {
207 HdyShadowHelper *self = HDY_SHADOW_HELPER (object);
208
209 hdy_shadow_helper_clear_cache (self);
210
211 if (self->widget)
212 g_clear_object (&self->widget);
213
214 G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->dispose (object);
215 }
216 static void
hdy_shadow_helper_finalize(GObject * object)217 hdy_shadow_helper_finalize (GObject *object)
218 {
219 HdyShadowHelper *self = HDY_SHADOW_HELPER (object);
220
221 if (self->css_path)
222 g_free (self->css_path);
223 g_object_unref (self->provider);
224
225 G_OBJECT_CLASS (hdy_shadow_helper_parent_class)->finalize (object);
226 }
227
228 static void
hdy_shadow_helper_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)229 hdy_shadow_helper_get_property (GObject *object,
230 guint prop_id,
231 GValue *value,
232 GParamSpec *pspec)
233 {
234 HdyShadowHelper *self = HDY_SHADOW_HELPER (object);
235
236 switch (prop_id) {
237 case PROP_WIDGET:
238 g_value_set_object (value, self->widget);
239 break;
240
241 case PROP_CSS_PATH:
242 g_value_set_string (value, self->css_path);
243 break;
244
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
247 }
248 }
249
250 static void
hdy_shadow_helper_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)251 hdy_shadow_helper_set_property (GObject *object,
252 guint prop_id,
253 const GValue *value,
254 GParamSpec *pspec)
255 {
256 HdyShadowHelper *self = HDY_SHADOW_HELPER (object);
257
258 switch (prop_id) {
259 case PROP_WIDGET:
260 self->widget = GTK_WIDGET (g_object_ref (g_value_get_object (value)));
261 break;
262
263 case PROP_CSS_PATH:
264 if (self->css_path)
265 g_clear_pointer (&self->css_path, g_free);
266 self->css_path = g_strdup (g_value_get_string (value));
267 break;
268
269 default:
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
271 }
272 }
273
274 static void
hdy_shadow_helper_class_init(HdyShadowHelperClass * klass)275 hdy_shadow_helper_class_init (HdyShadowHelperClass *klass)
276 {
277 GObjectClass *object_class = G_OBJECT_CLASS (klass);
278
279 object_class->constructed = hdy_shadow_helper_constructed;
280 object_class->dispose = hdy_shadow_helper_dispose;
281 object_class->finalize = hdy_shadow_helper_finalize;
282 object_class->get_property = hdy_shadow_helper_get_property;
283 object_class->set_property = hdy_shadow_helper_set_property;
284
285 /**
286 * HdyShadowHelper:widget:
287 *
288 * The widget the shadow will be drawn for. Must not be %NULL
289 *
290 * Since: 0.0.11
291 */
292 props[PROP_WIDGET] =
293 g_param_spec_object ("widget",
294 _("Widget"),
295 _("The widget the shadow will be drawn for"),
296 GTK_TYPE_WIDGET,
297 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
298
299 /**
300 * HdyShadowHelper:css-path:
301 *
302 * The CSS resource path to be used for the shadow. Must not be %NULL.
303 *
304 * Since: 0.0.11
305 */
306 props[PROP_CSS_PATH] =
307 g_param_spec_string ("css-path",
308 _("CSS Path"),
309 _("The CSS resource path to be used for the shadow"),
310 NULL,
311 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
312
313 g_object_class_install_properties (object_class, LAST_PROP, props);
314 }
315
316 static void
hdy_shadow_helper_init(HdyShadowHelper * self)317 hdy_shadow_helper_init (HdyShadowHelper *self)
318 {
319 }
320
321 /**
322 * hdy_shadow_helper_new:
323 *
324 * Creates a new #HdyShadowHelper object.
325 *
326 * Returns: The newly created #HdyShadowHelper object
327 *
328 * Since: 0.0.12
329 */
330 HdyShadowHelper *
hdy_shadow_helper_new(GtkWidget * widget,const gchar * css_path)331 hdy_shadow_helper_new (GtkWidget *widget,
332 const gchar *css_path)
333 {
334 return g_object_new (HDY_TYPE_SHADOW_HELPER,
335 "widget", widget,
336 "css-path", css_path,
337 NULL);
338 }
339
340 /**
341 * hdy_shadow_helper_clear_cache:
342 * @self: a #HdyShadowHelper
343 *
344 * Clears shadow cache. This should be used after a transition is done.
345 *
346 * Since: 0.0.12
347 */
348 void
hdy_shadow_helper_clear_cache(HdyShadowHelper * self)349 hdy_shadow_helper_clear_cache (HdyShadowHelper *self)
350 {
351 if (!self->is_cache_valid)
352 return;
353
354 cairo_pattern_destroy (self->dimming_pattern);
355 cairo_pattern_destroy (self->shadow_pattern);
356 cairo_pattern_destroy (self->border_pattern);
357 self->border_size = 0;
358 self->shadow_size = 0;
359
360 self->last_direction = 0;
361 self->last_width = 0;
362 self->last_height = 0;
363 self->last_scale = 0;
364
365 self->is_cache_valid = FALSE;
366 }
367
368 /**
369 * hdy_shadow_helper_draw_shadow:
370 * @self: a #HdyShadowHelper
371 * @cr: a Cairo context to draw to
372 * @width: the width of the shadow rectangle
373 * @height: the height of the shadow rectangle
374 * @progress: transition progress, changes from 0 to 1
375 * @direction: shadow direction
376 *
377 * Draws a transition shadow. For caching to work, @width, @height and
378 * @direction shouldn't change between calls.
379 *
380 * Since: 0.0.12
381 */
382 void
hdy_shadow_helper_draw_shadow(HdyShadowHelper * self,cairo_t * cr,gint width,gint height,gdouble progress,GtkPanDirection direction)383 hdy_shadow_helper_draw_shadow (HdyShadowHelper *self,
384 cairo_t *cr,
385 gint width,
386 gint height,
387 gdouble progress,
388 GtkPanDirection direction)
389 {
390 gdouble remaining_distance, shadow_opacity;
391 gint shadow_size, border_size, distance;
392
393 cache_shadow (self, width, height, direction);
394
395 shadow_size = self->shadow_size;
396 border_size = self->border_size;
397
398 switch (direction) {
399 case GTK_PAN_DIRECTION_LEFT:
400 case GTK_PAN_DIRECTION_RIGHT:
401 distance = width;
402 break;
403 case GTK_PAN_DIRECTION_UP:
404 case GTK_PAN_DIRECTION_DOWN:
405 distance = height;
406 break;
407 default:
408 g_assert_not_reached ();
409 }
410
411 remaining_distance = (1 - progress) * (gdouble) distance;
412 shadow_opacity = 1;
413 if (remaining_distance < shadow_size)
414 shadow_opacity = (remaining_distance / shadow_size);
415
416 cairo_save (cr);
417
418 cairo_save (cr);
419 cairo_set_operator (cr, CAIRO_OPERATOR_ATOP);
420 cairo_set_source (cr, self->dimming_pattern);
421 cairo_paint_with_alpha (cr, 1 - progress);
422 cairo_restore (cr);
423
424 switch (direction) {
425 case GTK_PAN_DIRECTION_RIGHT:
426 cairo_translate (cr, width - shadow_size, 0);
427 break;
428 case GTK_PAN_DIRECTION_DOWN:
429 cairo_translate (cr, 0, height - shadow_size);
430 break;
431 case GTK_PAN_DIRECTION_LEFT:
432 case GTK_PAN_DIRECTION_UP:
433 break;
434 default:
435 g_assert_not_reached ();
436 }
437
438 cairo_save (cr);
439 cairo_set_operator (cr, CAIRO_OPERATOR_ATOP);
440 cairo_set_source (cr, self->shadow_pattern);
441 cairo_paint_with_alpha (cr, shadow_opacity);
442 cairo_restore (cr);
443
444 switch (direction) {
445 case GTK_PAN_DIRECTION_RIGHT:
446 cairo_translate (cr, shadow_size - border_size, 0);
447 break;
448 case GTK_PAN_DIRECTION_DOWN:
449 cairo_translate (cr, 0, shadow_size - border_size);
450 break;
451 case GTK_PAN_DIRECTION_LEFT:
452 case GTK_PAN_DIRECTION_UP:
453 break;
454 default:
455 g_assert_not_reached ();
456 }
457
458 cairo_save (cr);
459 cairo_set_operator (cr, CAIRO_OPERATOR_ATOP);
460 cairo_set_source (cr, self->border_pattern);
461 cairo_paint (cr);
462 cairo_restore (cr);
463
464 cairo_restore (cr);
465 }
466