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 
9 #include "adw-gizmo-private.h"
10 #include "adw-shadow-helper-private.h"
11 
12 /**
13  * PRIVATE:adwshadowhelper
14  * @short_description: Shadow helper used in #AdwLeaflet
15  * @title: AdwShadowHelper
16  * @See_also: #AdwLeaflet
17  * @stability: Private
18  *
19  * A helper class for drawing #AdwLeaflet transition shadow.
20  *
21  * Since: 1.0
22  */
23 
24 struct _AdwShadowHelper
25 {
26   GObject parent_instance;
27 
28   GtkWidget *widget;
29 
30   GtkWidget *dimming;
31   GtkWidget *shadow;
32   GtkWidget *border;
33   GtkWidget *outline;
34 };
35 
36 G_DEFINE_TYPE (AdwShadowHelper, adw_shadow_helper, G_TYPE_OBJECT);
37 
38 enum {
39   PROP_0,
40   PROP_WIDGET,
41   LAST_PROP,
42 };
43 
44 static GParamSpec *props[LAST_PROP];
45 
46 static void
adw_shadow_helper_constructed(GObject * object)47 adw_shadow_helper_constructed (GObject *object)
48 {
49   AdwShadowHelper *self = ADW_SHADOW_HELPER (object);
50 
51   self->dimming = adw_gizmo_new ("dimming", NULL, NULL, NULL, NULL, NULL, NULL);
52   self->shadow = adw_gizmo_new ("shadow", NULL, NULL, NULL, NULL, NULL, NULL);
53   self->border = adw_gizmo_new ("border", NULL, NULL, NULL, NULL, NULL, NULL);
54   self->outline = adw_gizmo_new ("outline", NULL, NULL, NULL, NULL, NULL, NULL);
55 
56   gtk_widget_set_child_visible (self->dimming, FALSE);
57   gtk_widget_set_child_visible (self->shadow, FALSE);
58   gtk_widget_set_child_visible (self->border, FALSE);
59   gtk_widget_set_child_visible (self->outline, FALSE);
60 
61   gtk_widget_set_can_target (self->dimming, FALSE);
62   gtk_widget_set_can_target (self->shadow, FALSE);
63   gtk_widget_set_can_target (self->border, FALSE);
64   gtk_widget_set_can_target (self->outline, FALSE);
65 
66   gtk_widget_set_parent (self->dimming, self->widget);
67   gtk_widget_set_parent (self->shadow, self->widget);
68   gtk_widget_set_parent (self->border, self->widget);
69   gtk_widget_set_parent (self->outline, self->widget);
70 
71   G_OBJECT_CLASS (adw_shadow_helper_parent_class)->constructed (object);
72 }
73 
74 static void
adw_shadow_helper_dispose(GObject * object)75 adw_shadow_helper_dispose (GObject *object)
76 {
77   AdwShadowHelper *self = ADW_SHADOW_HELPER (object);
78 
79   g_clear_pointer (&self->dimming, gtk_widget_unparent);
80   g_clear_pointer (&self->shadow, gtk_widget_unparent);
81   g_clear_pointer (&self->border, gtk_widget_unparent);
82   g_clear_pointer (&self->outline, gtk_widget_unparent);
83   g_clear_object (&self->widget);
84 
85   G_OBJECT_CLASS (adw_shadow_helper_parent_class)->dispose (object);
86 }
87 
88 static void
adw_shadow_helper_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)89 adw_shadow_helper_get_property (GObject    *object,
90                                 guint       prop_id,
91                                 GValue     *value,
92                                 GParamSpec *pspec)
93 {
94   AdwShadowHelper *self = ADW_SHADOW_HELPER (object);
95 
96   switch (prop_id) {
97   case PROP_WIDGET:
98     g_value_set_object (value, self->widget);
99     break;
100 
101   default:
102     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
103   }
104 }
105 
106 static void
adw_shadow_helper_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)107 adw_shadow_helper_set_property (GObject      *object,
108                                 guint         prop_id,
109                                 const GValue *value,
110                                 GParamSpec   *pspec)
111 {
112   AdwShadowHelper *self = ADW_SHADOW_HELPER (object);
113 
114   switch (prop_id) {
115   case PROP_WIDGET:
116     self->widget = GTK_WIDGET (g_object_ref (g_value_get_object (value)));
117     break;
118 
119   default:
120     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
121   }
122 }
123 
124 static void
adw_shadow_helper_class_init(AdwShadowHelperClass * klass)125 adw_shadow_helper_class_init (AdwShadowHelperClass *klass)
126 {
127   GObjectClass *object_class = G_OBJECT_CLASS (klass);
128 
129   object_class->constructed = adw_shadow_helper_constructed;
130   object_class->dispose = adw_shadow_helper_dispose;
131   object_class->get_property = adw_shadow_helper_get_property;
132   object_class->set_property = adw_shadow_helper_set_property;
133 
134   /**
135    * AdwShadowHelper:widget:
136    *
137    * The widget the shadow will be drawn for. Must not be %NULL
138    *
139    * Since: 1.0
140    */
141   props[PROP_WIDGET] =
142     g_param_spec_object ("widget",
143                          "Widget",
144                          "The widget the shadow will be drawn for",
145                          GTK_TYPE_WIDGET,
146                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
147 
148   g_object_class_install_properties (object_class, LAST_PROP, props);
149 }
150 
151 static void
adw_shadow_helper_init(AdwShadowHelper * self)152 adw_shadow_helper_init (AdwShadowHelper *self)
153 {
154 }
155 
156 /**
157  * adw_shadow_helper_new:
158  *
159  * Creates a new #AdwShadowHelper object.
160  *
161  * Returns: The newly created #AdwShadowHelper object
162  *
163  * Since: 1.0
164  */
165 AdwShadowHelper *
adw_shadow_helper_new(GtkWidget * widget)166 adw_shadow_helper_new (GtkWidget *widget)
167 {
168   return g_object_new (ADW_TYPE_SHADOW_HELPER,
169                        "widget", widget,
170                        NULL);
171 }
172 
173 static void
set_style_classes(AdwShadowHelper * self,GtkPanDirection direction)174 set_style_classes (AdwShadowHelper *self,
175                    GtkPanDirection  direction)
176 {
177   const char *classes[2];
178 
179   switch (direction) {
180   case GTK_PAN_DIRECTION_LEFT:
181     classes[0] = "left";
182     break;
183   case GTK_PAN_DIRECTION_RIGHT:
184     classes[0] = "right";
185     break;
186   case GTK_PAN_DIRECTION_UP:
187     classes[0] = "up";
188     break;
189   case GTK_PAN_DIRECTION_DOWN:
190     classes[0] = "down";
191     break;
192   default:
193     g_assert_not_reached ();
194   }
195   classes[1] = NULL;
196 
197   gtk_widget_set_css_classes (self->dimming, classes);
198   gtk_widget_set_css_classes (self->shadow, classes);
199   gtk_widget_set_css_classes (self->border, classes);
200   gtk_widget_set_css_classes (self->outline, classes);
201 }
202 
203 void
adw_shadow_helper_size_allocate(AdwShadowHelper * self,int width,int height,int baseline,int x,int y,double progress,GtkPanDirection direction)204 adw_shadow_helper_size_allocate (AdwShadowHelper *self,
205                                  int              width,
206                                  int              height,
207                                  int              baseline,
208                                  int              x,
209                                  int              y,
210                                  double           progress,
211                                  GtkPanDirection  direction)
212 {
213   double distance, remaining_distance;
214   double shadow_opacity;
215   int shadow_size, border_size, outline_size;
216   GtkOrientation orientation;
217 
218   set_style_classes (self, direction);
219 
220   gtk_widget_allocate (self->dimming, width, height, baseline,
221                        gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
222 
223   switch (direction) {
224   case GTK_PAN_DIRECTION_LEFT:
225   case GTK_PAN_DIRECTION_RIGHT:
226     distance = width;
227     orientation = GTK_ORIENTATION_HORIZONTAL;
228     break;
229   case GTK_PAN_DIRECTION_UP:
230   case GTK_PAN_DIRECTION_DOWN:
231     distance = height;
232     orientation = GTK_ORIENTATION_VERTICAL;
233     break;
234   default:
235     g_assert_not_reached ();
236   }
237 
238   gtk_widget_set_child_visible (self->dimming, progress < 1);
239   gtk_widget_set_child_visible (self->shadow, progress < 1);
240   gtk_widget_set_child_visible (self->border, progress < 1);
241   gtk_widget_set_child_visible (self->outline, progress < 1);
242 
243   gtk_widget_measure (self->shadow, orientation, -1, &shadow_size, NULL, NULL, NULL);
244   gtk_widget_measure (self->border, orientation, -1, &border_size, NULL, NULL, NULL);
245   gtk_widget_measure (self->outline, orientation, -1, &outline_size, NULL, NULL, NULL);
246 
247   remaining_distance = (1 - progress) * (double) distance;
248   if (remaining_distance < shadow_size)
249     shadow_opacity = (remaining_distance / shadow_size);
250   else
251     shadow_opacity = 1;
252 
253   gtk_widget_set_opacity (self->dimming, 1 - progress);
254   gtk_widget_set_opacity (self->shadow, shadow_opacity);
255 
256   switch (direction) {
257   case GTK_PAN_DIRECTION_LEFT:
258     gtk_widget_allocate (self->shadow, shadow_size, MAX (height, shadow_size), baseline,
259                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
260     gtk_widget_allocate (self->border, border_size, MAX (height, border_size), baseline,
261                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
262     gtk_widget_allocate (self->outline, outline_size, MAX (height, outline_size), baseline,
263                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x - outline_size, y)));
264     break;
265   case GTK_PAN_DIRECTION_RIGHT:
266     gtk_widget_allocate (self->shadow, shadow_size, MAX (height, shadow_size), baseline,
267                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x + width - shadow_size, y)));
268     gtk_widget_allocate (self->border, border_size, MAX (height, border_size), baseline,
269                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x + width - border_size, y)));
270     gtk_widget_allocate (self->outline, outline_size, MAX (height, outline_size), baseline,
271                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x + width, y)));
272     break;
273   case GTK_PAN_DIRECTION_UP:
274     gtk_widget_allocate (self->shadow, MAX (width, shadow_size), shadow_size, baseline,
275                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
276     gtk_widget_allocate (self->border, MAX (width, border_size), border_size, baseline,
277                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
278     gtk_widget_allocate (self->outline, MAX (width, outline_size), outline_size, baseline,
279                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y - outline_size)));
280     break;
281   case GTK_PAN_DIRECTION_DOWN:
282     gtk_widget_allocate (self->shadow, MAX (width, shadow_size), shadow_size, baseline,
283                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y + height - shadow_size)));
284     gtk_widget_allocate (self->border, MAX (width, border_size), border_size, baseline,
285                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y + height - border_size)));
286     gtk_widget_allocate (self->border, MAX (width, outline_size), outline_size, baseline,
287                          gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y + height)));
288     break;
289   default:
290     g_assert_not_reached ();
291   }
292 }
293 
294 void
adw_shadow_helper_snapshot(AdwShadowHelper * self,GtkSnapshot * snapshot)295 adw_shadow_helper_snapshot (AdwShadowHelper *self,
296                             GtkSnapshot     *snapshot)
297 {
298   if (!gtk_widget_get_child_visible (self->dimming))
299     return;
300 
301   gtk_widget_snapshot_child (self->widget, self->dimming, snapshot);
302   gtk_widget_snapshot_child (self->widget, self->shadow, snapshot);
303   gtk_widget_snapshot_child (self->widget, self->border, snapshot);
304   gtk_widget_snapshot_child (self->widget, self->outline, snapshot);
305 }
306