gui/fading-label: Rewrite using PangoLayout directly

Instead of an internal label widget, store the text and the PangoLayout
directly, and only render them. Cache as much as possible to avoid
font reshaping.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/765>
This commit is contained in:
Georges Basile Stavracas Neto
2026-05-19 19:25:25 -03:00
parent 601acef508
commit f40c2cb0e9
3 changed files with 146 additions and 84 deletions
+4 -4
View File
@@ -54,7 +54,7 @@ template $GcalEventWidget: Widget {
} }
Inscription { Inscription {
text: bind summary_label.label; text: bind summary_label.text;
hexpand: true; hexpand: true;
vexpand: true; vexpand: true;
xalign: 0.0; xalign: 0.0;
@@ -68,13 +68,13 @@ template $GcalEventWidget: Widget {
spacing: 4; spacing: 4;
$GcalFadingLabel summary_label { $GcalFadingLabel summary_label {
label: bind template.event as <$GcalEvent>.summary; text: bind template.event as <$GcalEvent>.summary;
hexpand: true;
vexpand: true;
} }
Label timestamp_label { Label timestamp_label {
halign: end; halign: end;
hexpand: true;
vexpand: true;
styles [ styles [
"dimmed", "dimmed",
+140 -78
View File
@@ -8,6 +8,8 @@
/* Copied from libadwaita 1.8 and adapted for use in gnome-calendar. */ /* Copied from libadwaita 1.8 and adapted for use in gnome-calendar. */
#define G_LOG_DOMAIN "GcalFadingLabel"
#include "config.h" #include "config.h"
#include "gcal-fading-label.h" #include "gcal-fading-label.h"
@@ -20,7 +22,16 @@ struct _GcalFadingLabel
{ {
GtkWidget parent_instance; GtkWidget parent_instance;
GtkWidget *label; PangoLayout *layout;
GskRenderNode *cached_text_node;
PangoRectangle cached_extents;
gboolean cached_extents_valid;
int cached_ascent;
int cached_descent;
char *text;
gfloat align; gfloat align;
}; };
@@ -28,7 +39,7 @@ G_DEFINE_FINAL_TYPE (GcalFadingLabel, gcal_fading_label, GTK_TYPE_WIDGET)
enum { enum {
PROP_0, PROP_0,
PROP_LABEL, PROP_TEXT,
PROP_ALIGN, PROP_ALIGN,
LAST_PROP LAST_PROP
}; };
@@ -39,10 +50,9 @@ static gboolean
is_rtl (GcalFadingLabel *self) is_rtl (GcalFadingLabel *self)
{ {
PangoDirection pango_direction = PANGO_DIRECTION_NEUTRAL; PangoDirection pango_direction = PANGO_DIRECTION_NEUTRAL;
const gchar *label = gcal_fading_label_get_label (self);
if (label) if (self->text)
pango_direction = gcal_find_base_dir (label, -1); pango_direction = gcal_find_base_dir (self->text, -1);
if (pango_direction == PANGO_DIRECTION_RTL) if (pango_direction == PANGO_DIRECTION_RTL)
return TRUE; return TRUE;
@@ -53,6 +63,46 @@ is_rtl (GcalFadingLabel *self)
return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
} }
static int
get_line_pixels (GcalFadingLabel *self,
int *baseline)
{
if (self->cached_ascent == -1 || self->cached_descent == -1)
{
PangoFontMetrics *metrics;
PangoContext *context;
context = gtk_widget_get_pango_context (GTK_WIDGET (self));
metrics = pango_context_get_metrics (context, NULL, NULL);
self->cached_ascent = pango_font_metrics_get_ascent (metrics);
self->cached_descent = pango_font_metrics_get_descent (metrics);
g_clear_pointer (&metrics, pango_font_metrics_unref);
}
if (baseline)
*baseline = self->cached_ascent;
return self->cached_ascent + self->cached_descent;
}
static void
gcal_fading_label_css_changed (GtkWidget *widget,
GtkCssStyleChange *change)
{
GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
GTK_WIDGET_CLASS (gcal_fading_label_parent_class)->css_changed (widget, change);
self->cached_ascent = -1;
self->cached_descent = -1;
self->cached_extents_valid = FALSE;
g_clear_pointer (&self->cached_text_node, gsk_render_node_unref);
gtk_widget_queue_draw (widget);
}
static void static void
gcal_fading_label_measure (GtkWidget *widget, gcal_fading_label_measure (GtkWidget *widget,
GtkOrientation orientation, GtkOrientation orientation,
@@ -64,39 +114,34 @@ gcal_fading_label_measure (GtkWidget *widget,
{ {
GcalFadingLabel *self = GCAL_FADING_LABEL (widget); GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
gtk_widget_measure (self->label, switch (orientation)
orientation, {
for_size, case GTK_ORIENTATION_VERTICAL:
min, nat, {
min_baseline, nat_baseline); int line_pixels, baseline;
if (orientation == GTK_ORIENTATION_HORIZONTAL && min) line_pixels = get_line_pixels (self, &baseline);
*min = 0;
}
static void *min = line_pixels;
gcal_fading_label_size_allocate (GtkWidget *widget, *nat = line_pixels;
gint width, }
gint height, break;
gint baseline)
{
GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
GskTransform *transform;
gfloat align = is_rtl (self) ? 1 - self->align : self->align;
gint child_width;
gfloat offset;
gtk_widget_measure (self->label, case GTK_ORIENTATION_HORIZONTAL:
GTK_ORIENTATION_HORIZONTAL, *min = 0;
height, *nat = 0;
NULL, break;
&child_width,
NULL, NULL);
offset = (width - child_width) * align; default:
transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (offset, 0)); g_assert_not_reached ();
}
gtk_widget_allocate (self->label, child_width, height, baseline, transform); *min = PANGO_PIXELS_CEIL (*min);
*nat = PANGO_PIXELS_CEIL (*nat);
if (*min_baseline > 0)
*min_baseline = PANGO_PIXELS_CEIL (*min_baseline);
if (*nat_baseline > 0)
*nat_baseline = PANGO_PIXELS_CEIL (*nat_baseline);
} }
static void static void
@@ -104,41 +149,49 @@ gcal_fading_label_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot) GtkSnapshot *snapshot)
{ {
GcalFadingLabel *self = GCAL_FADING_LABEL (widget); GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
g_autoptr (GskRenderNode) node = NULL; float align;
g_autoptr (GtkSnapshot) child_snapshot = NULL; int height;
graphene_rect_t bounds; int width;
gfloat align = is_rtl (self) ? 1 - self->align : self->align;
gint width = gtk_widget_get_width (widget); width = gtk_widget_get_width (widget);
gint clipped_size; height = gtk_widget_get_height (widget);
if (width <= 0) if (width <= 0)
return; return;
clipped_size = gtk_widget_get_width (self->label) - width; if (!self->cached_extents_valid)
if (clipped_size <= 0)
{ {
gtk_widget_snapshot_child (widget, self->label, snapshot); pango_layout_get_pixel_extents (self->layout, NULL, &self->cached_extents);
self->cached_extents_valid = TRUE;
}
if (!self->cached_text_node)
{
g_autoptr (GtkSnapshot) text_snapshot = NULL;
GdkRGBA foreground_color;
gtk_widget_get_color (widget, &foreground_color);
text_snapshot = gtk_snapshot_new ();
gtk_snapshot_append_layout (text_snapshot, self->layout, &foreground_color);
self->cached_text_node = gtk_snapshot_free_to_node (g_steal_pointer (&text_snapshot));
}
if (self->cached_extents.x + self->cached_extents.width <= width)
{
gtk_snapshot_append_node (snapshot, self->cached_text_node);
return; return;
} }
child_snapshot = gtk_snapshot_new (); align = is_rtl (self) ? 1 - self->align : self->align;
gtk_widget_snapshot_child (widget, self->label, child_snapshot);
node = gtk_snapshot_free_to_node (g_steal_pointer (&child_snapshot));
gsk_render_node_get_bounds (node, &bounds);
bounds.origin.x = 0;
bounds.origin.y = floor (bounds.origin.y);
bounds.size.width = width;
bounds.size.height = ceil (bounds.size.height) + 1;
gtk_snapshot_push_mask (snapshot, GSK_MASK_MODE_INVERTED_ALPHA); gtk_snapshot_push_mask (snapshot, GSK_MASK_MODE_INVERTED_ALPHA);
if (align > 0) if (align > 0)
{ {
gtk_snapshot_append_linear_gradient (snapshot, gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, bounds.origin.y, &GRAPHENE_RECT_INIT (0, 0, FADE_WIDTH, height),
FADE_WIDTH, bounds.size.height),
&GRAPHENE_POINT_INIT (0, 0), &GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (FADE_WIDTH, 0), &GRAPHENE_POINT_INIT (FADE_WIDTH, 0),
(GskColorStop[2]) { (GskColorStop[2]) {
@@ -151,8 +204,7 @@ gcal_fading_label_snapshot (GtkWidget *widget,
if (align < 1) if (align < 1)
{ {
gtk_snapshot_append_linear_gradient (snapshot, gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (width - FADE_WIDTH, bounds.origin.y, &GRAPHENE_RECT_INIT (width - FADE_WIDTH, 0, FADE_WIDTH, height),
FADE_WIDTH, bounds.size.height),
&GRAPHENE_POINT_INIT (width, 0), &GRAPHENE_POINT_INIT (width, 0),
&GRAPHENE_POINT_INIT (width - FADE_WIDTH, 0), &GRAPHENE_POINT_INIT (width - FADE_WIDTH, 0),
(GskColorStop[2]) { (GskColorStop[2]) {
@@ -164,8 +216,8 @@ gcal_fading_label_snapshot (GtkWidget *widget,
gtk_snapshot_pop (snapshot); gtk_snapshot_pop (snapshot);
gtk_snapshot_push_clip (snapshot, &bounds); gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_append_node (snapshot, node); gtk_snapshot_append_node (snapshot, self->cached_text_node);
gtk_snapshot_pop (snapshot); gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot); gtk_snapshot_pop (snapshot);
@@ -180,8 +232,8 @@ gcal_fading_label_get_property (GObject *object,
GcalFadingLabel *self = GCAL_FADING_LABEL (object); GcalFadingLabel *self = GCAL_FADING_LABEL (object);
switch (prop_id) { switch (prop_id) {
case PROP_LABEL: case PROP_TEXT:
g_value_set_string (value, gcal_fading_label_get_label (self)); g_value_set_string (value, self->text);
break; break;
case PROP_ALIGN: case PROP_ALIGN:
@@ -202,8 +254,8 @@ gcal_fading_label_set_property (GObject *object,
GcalFadingLabel *self = GCAL_FADING_LABEL (object); GcalFadingLabel *self = GCAL_FADING_LABEL (object);
switch (prop_id) { switch (prop_id) {
case PROP_LABEL: case PROP_TEXT:
gcal_fading_label_set_label (self, g_value_get_string (value)); gcal_fading_label_set_text (self, g_value_get_string (value));
break; break;
case PROP_ALIGN: case PROP_ALIGN:
@@ -220,7 +272,8 @@ gcal_fading_label_dispose (GObject *object)
{ {
GcalFadingLabel *self = GCAL_FADING_LABEL (object); GcalFadingLabel *self = GCAL_FADING_LABEL (object);
g_clear_pointer (&self->label, gtk_widget_unparent); g_clear_object (&self->layout);
g_clear_pointer (&self->cached_text_node, gsk_render_node_unref);
G_OBJECT_CLASS (gcal_fading_label_parent_class)->dispose (object); G_OBJECT_CLASS (gcal_fading_label_parent_class)->dispose (object);
} }
@@ -235,12 +288,12 @@ gcal_fading_label_class_init (GcalFadingLabelClass *klass)
object_class->set_property = gcal_fading_label_set_property; object_class->set_property = gcal_fading_label_set_property;
object_class->dispose = gcal_fading_label_dispose; object_class->dispose = gcal_fading_label_dispose;
widget_class->css_changed = gcal_fading_label_css_changed;
widget_class->measure = gcal_fading_label_measure; widget_class->measure = gcal_fading_label_measure;
widget_class->size_allocate = gcal_fading_label_size_allocate;
widget_class->snapshot = gcal_fading_label_snapshot; widget_class->snapshot = gcal_fading_label_snapshot;
props[PROP_LABEL] = props[PROP_TEXT] =
g_param_spec_string ("label", NULL, NULL, g_param_spec_string ("text", NULL, NULL,
NULL, NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
@@ -255,32 +308,41 @@ gcal_fading_label_class_init (GcalFadingLabelClass *klass)
static void static void
gcal_fading_label_init (GcalFadingLabel *self) gcal_fading_label_init (GcalFadingLabel *self)
{ {
self->label = gtk_label_new (NULL); self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), NULL);
gtk_label_set_single_line_mode (GTK_LABEL (self->label), TRUE); self->cached_ascent = -1;
self->cached_descent = -1;
gtk_widget_set_parent (self->label, GTK_WIDGET (self)); self->cached_extents_valid = FALSE;
} }
const gchar * const gchar *
gcal_fading_label_get_label (GcalFadingLabel *self) gcal_fading_label_get_text (GcalFadingLabel *self)
{ {
g_return_val_if_fail (GCAL_IS_FADING_LABEL (self), NULL); g_return_val_if_fail (GCAL_IS_FADING_LABEL (self), NULL);
return gtk_label_get_label (GTK_LABEL (self->label)); return self->text;
} }
void void
gcal_fading_label_set_label (GcalFadingLabel *self, gcal_fading_label_set_text (GcalFadingLabel *self,
const gchar *label) const gchar *text)
{ {
g_return_if_fail (GCAL_IS_FADING_LABEL (self)); g_return_if_fail (GCAL_IS_FADING_LABEL (self));
if (!g_strcmp0 (label, gcal_fading_label_get_label (self))) if (!g_set_str (&self->text, text))
return; return;
gtk_label_set_label (GTK_LABEL (self->label), label); pango_layout_set_text (self->layout,
self->text ? self->text : "",
-1);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LABEL]); self->cached_ascent = -1;
self->cached_descent = -1;
self->cached_extents_valid = FALSE;
g_clear_pointer (&self->cached_text_node, gsk_render_node_unref);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
} }
float float
@@ -304,7 +366,7 @@ gcal_fading_label_set_align (GcalFadingLabel *self,
self->align = align; self->align = align;
gtk_widget_queue_allocate (GTK_WIDGET (self)); gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALIGN]); g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ALIGN]);
} }
+2 -2
View File
@@ -18,8 +18,8 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GcalFadingLabel, gcal_fading_label, GCAL, FADING_LABEL, GtkWidget) G_DECLARE_FINAL_TYPE (GcalFadingLabel, gcal_fading_label, GCAL, FADING_LABEL, GtkWidget)
const gchar *gcal_fading_label_get_label (GcalFadingLabel *self); const gchar *gcal_fading_label_get_text (GcalFadingLabel *self);
void gcal_fading_label_set_label (GcalFadingLabel *self, void gcal_fading_label_set_text (GcalFadingLabel *self,
const gchar *label); const gchar *label);
gfloat gcal_fading_label_get_align (GcalFadingLabel *self); gfloat gcal_fading_label_get_align (GcalFadingLabel *self);
void gcal_fading_label_set_align (GcalFadingLabel *self, void gcal_fading_label_set_align (GcalFadingLabel *self,