1 /*
2  * Copyright (C) 2021 Purism SPC
3  *
4  * SPDX-License-Identifier: LGPL-2.1+
5  *
6  * Author: Alexander Mikhaylenko <alexander.mikhaylenko@puri.sm>
7  */
8 
9 #include "config.h"
10 #include "adw-fading-label-private.h"
11 
12 #include <glib/gi18n-lib.h>
13 #include "adw-bidi-private.h"
14 
15 /**
16  * PRIVATE:adw-fading-label
17  * @short_description: A helper object for #AdwTab
18  * @title: AdwFadingLabel
19  * @stability: Private
20  *
21  * The AdwFadingLabel widget allows to ellipsize a label with a fading effect.
22  *
23  * Since: 1.0
24  */
25 
26 #define FADE_WIDTH 18
27 
28 struct _AdwFadingLabel
29 {
30   GtkWidget parent_instance;
31 
32   GtkWidget *label;
33   float align;
34 
35   GskGLShader *shader;
36   gboolean shader_compiled;
37 };
38 
39 G_DEFINE_TYPE (AdwFadingLabel, adw_fading_label, GTK_TYPE_WIDGET)
40 
41 enum {
42   PROP_0,
43   PROP_LABEL,
44   PROP_ALIGN,
45   LAST_PROP
46 };
47 
48 static GParamSpec *props[LAST_PROP];
49 
50 static gboolean
is_rtl(AdwFadingLabel * self)51 is_rtl (AdwFadingLabel *self)
52 {
53   PangoDirection pango_direction = PANGO_DIRECTION_NEUTRAL;
54   const char *label = adw_fading_label_get_label (self);
55 
56   if (label)
57     pango_direction = adw_find_base_dir (label, -1);
58 
59   if (pango_direction == PANGO_DIRECTION_RTL)
60     return TRUE;
61 
62   if (pango_direction == PANGO_DIRECTION_LTR)
63     return FALSE;
64 
65   return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
66 }
67 
68 static void
ensure_shader(AdwFadingLabel * self)69 ensure_shader (AdwFadingLabel *self)
70 {
71   GtkNative *native;
72   GskRenderer *renderer;
73   g_autoptr (GError) error = NULL;
74 
75   if (self->shader)
76     return;
77 
78   self->shader = gsk_gl_shader_new_from_resource ("/org/gnome/Adwaita/glsl/fade.glsl");
79 
80   native = gtk_widget_get_native (GTK_WIDGET (self));
81   renderer = gtk_native_get_renderer (native);
82 
83   self->shader_compiled = gsk_gl_shader_compile (self->shader, renderer, &error);
84 
85   if (error) {
86     /* If shaders aren't supported, the error doesn't matter and we just
87      * silently fall back */
88     if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
89       g_critical ("Couldn't compile shader: %s\n", error->message);
90   }
91 }
92 
93 static void
adw_fading_label_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * min,int * nat,int * min_baseline,int * nat_baseline)94 adw_fading_label_measure (GtkWidget      *widget,
95                           GtkOrientation  orientation,
96                           int             for_size,
97                           int             *min,
98                           int             *nat,
99                           int             *min_baseline,
100                           int             *nat_baseline)
101 {
102   AdwFadingLabel *self = ADW_FADING_LABEL (widget);
103 
104   gtk_widget_measure (self->label, orientation, for_size,
105                       min, nat, min_baseline, nat_baseline);
106 
107   if (orientation == GTK_ORIENTATION_HORIZONTAL && min)
108     *min = 0;
109 }
110 
111 static void
adw_fading_label_size_allocate(GtkWidget * widget,int width,int height,int baseline)112 adw_fading_label_size_allocate (GtkWidget *widget,
113                                 int        width,
114                                 int        height,
115                                 int        baseline)
116 {
117   AdwFadingLabel *self = ADW_FADING_LABEL (widget);
118   float align = is_rtl (self) ? 1 - self->align : self->align;
119   int child_width;
120   float offset;
121   GskTransform *transform;
122 
123   gtk_widget_measure (self->label, GTK_ORIENTATION_HORIZONTAL, height,
124                       NULL, &child_width, NULL, NULL);
125 
126   offset = (width - child_width) * align;
127   transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (offset, 0));
128 
129   gtk_widget_allocate (self->label, child_width, height, baseline, transform);
130 }
131 
132 static void
adw_fading_label_snapshot(GtkWidget * widget,GtkSnapshot * snapshot)133 adw_fading_label_snapshot (GtkWidget   *widget,
134                            GtkSnapshot *snapshot)
135 {
136   AdwFadingLabel *self = ADW_FADING_LABEL (widget);
137   float align = is_rtl (self) ? 1 - self->align : self->align;
138   int width = gtk_widget_get_width (widget);
139   int clipped_size;
140   GtkSnapshot *child_snapshot;
141   g_autoptr (GskRenderNode) node = NULL;
142   graphene_rect_t bounds;
143 
144   if (width <= 0)
145     return;
146 
147   clipped_size = gtk_widget_get_allocated_width (self->label) - width;
148 
149   if (clipped_size <= 0) {
150     gtk_widget_snapshot_child (widget, self->label, snapshot);
151 
152     return;
153   }
154 
155   child_snapshot = gtk_snapshot_new ();
156   gtk_widget_snapshot_child (widget, self->label, child_snapshot);
157   node = gtk_snapshot_free_to_node (child_snapshot);
158 
159   gsk_render_node_get_bounds (node, &bounds);
160   bounds.origin.x = 0;
161   bounds.size.width = width;
162 
163   ensure_shader (self);
164 
165   if (self->shader_compiled) {
166     gtk_snapshot_push_gl_shader (snapshot, self->shader, &bounds,
167                                  gsk_gl_shader_format_args (self->shader,
168                                                             "offsetLeft", 0.0f,
169                                                             "offsetRight", 0.0f,
170                                                             "strengthLeft", align > 0 ? 1.0f : 0.0f,
171                                                             "strengthRight", align < 1 ? 1.0f : 0.0f,
172                                                             NULL));
173   } else {
174     gtk_snapshot_push_clip (snapshot, &bounds);
175   }
176 
177   gtk_snapshot_append_node (snapshot, node);
178 
179   if (self->shader_compiled)
180     gtk_snapshot_gl_shader_pop_texture (snapshot);
181 
182   gtk_snapshot_pop (snapshot);
183 }
184 
185 static void
adw_fading_label_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)186 adw_fading_label_get_property (GObject    *object,
187                                guint       prop_id,
188                                GValue     *value,
189                                GParamSpec *pspec)
190 {
191   AdwFadingLabel *self = ADW_FADING_LABEL (object);
192 
193   switch (prop_id) {
194   case PROP_LABEL:
195     g_value_set_string (value, adw_fading_label_get_label (self));
196     break;
197 
198   case PROP_ALIGN:
199     g_value_set_float (value, adw_fading_label_get_align (self));
200     break;
201 
202   default:
203     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
204   }
205 }
206 
207 static void
adw_fading_label_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)208 adw_fading_label_set_property (GObject      *object,
209                                guint         prop_id,
210                                const GValue *value,
211                                GParamSpec   *pspec)
212 {
213   AdwFadingLabel *self = ADW_FADING_LABEL (object);
214 
215   switch (prop_id) {
216   case PROP_LABEL:
217     adw_fading_label_set_label (self, g_value_get_string (value));
218     break;
219 
220   case PROP_ALIGN:
221     adw_fading_label_set_align (self, g_value_get_float (value));
222     break;
223 
224   default:
225     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
226   }
227 }
228 
229 static void
adw_fading_label_dispose(GObject * object)230 adw_fading_label_dispose (GObject *object)
231 {
232   AdwFadingLabel *self = ADW_FADING_LABEL (object);
233 
234   g_clear_object (&self->shader);
235   g_clear_pointer (&self->label, gtk_widget_unparent);
236 
237   G_OBJECT_CLASS (adw_fading_label_parent_class)->dispose (object);
238 }
239 
240 static void
adw_fading_label_class_init(AdwFadingLabelClass * klass)241 adw_fading_label_class_init (AdwFadingLabelClass *klass)
242 {
243   GObjectClass *object_class = G_OBJECT_CLASS (klass);
244   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
245 
246   object_class->get_property = adw_fading_label_get_property;
247   object_class->set_property = adw_fading_label_set_property;
248   object_class->dispose = adw_fading_label_dispose;
249 
250   widget_class->measure = adw_fading_label_measure;
251   widget_class->size_allocate = adw_fading_label_size_allocate;
252   widget_class->snapshot = adw_fading_label_snapshot;
253 
254   props[PROP_LABEL] =
255     g_param_spec_string ("label",
256                          "Label",
257                          "Label",
258                          NULL,
259                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
260 
261   props[PROP_ALIGN] =
262     g_param_spec_float ("align",
263                         "Align",
264                         "Align",
265                         0.0, 1.0, 0.0,
266                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
267 
268   g_object_class_install_properties (object_class, LAST_PROP, props);
269 }
270 
271 static void
adw_fading_label_init(AdwFadingLabel * self)272 adw_fading_label_init (AdwFadingLabel *self)
273 {
274   self->label = gtk_label_new (NULL);
275   gtk_label_set_single_line_mode (GTK_LABEL (self->label), TRUE);
276 
277   gtk_widget_set_parent (self->label, GTK_WIDGET (self));
278 }
279 
280 const char *
adw_fading_label_get_label(AdwFadingLabel * self)281 adw_fading_label_get_label (AdwFadingLabel *self)
282 {
283   g_return_val_if_fail (ADW_IS_FADING_LABEL (self), NULL);
284 
285   return gtk_label_get_label (GTK_LABEL (self->label));
286 }
287 
288 void
adw_fading_label_set_label(AdwFadingLabel * self,const char * label)289 adw_fading_label_set_label (AdwFadingLabel *self,
290                             const char     *label)
291 {
292   g_return_if_fail (ADW_IS_FADING_LABEL (self));
293 
294   if (!g_strcmp0 (label, adw_fading_label_get_label (self)))
295     return;
296 
297   gtk_label_set_label (GTK_LABEL (self->label), label);
298 
299   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LABEL]);
300 }
301 
302 float
adw_fading_label_get_align(AdwFadingLabel * self)303 adw_fading_label_get_align (AdwFadingLabel *self)
304 {
305   g_return_val_if_fail (ADW_IS_FADING_LABEL (self), 0.0f);
306 
307   return self->align;
308 }
309 
310 void
adw_fading_label_set_align(AdwFadingLabel * self,float align)311 adw_fading_label_set_align (AdwFadingLabel *self,
312                             float           align)
313 {
314   g_return_if_fail (ADW_IS_FADING_LABEL (self));
315 
316   align = CLAMP (align, 0.0, 1.0);
317 
318   if (self->align == align)
319     return;
320 
321   self->align = align;
322 
323   gtk_widget_queue_allocate (GTK_WIDGET (self));
324 
325   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALIGN]);
326 }
327