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