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 {
text: bind summary_label.label;
text: bind summary_label.text;
hexpand: true;
vexpand: true;
xalign: 0.0;
@@ -68,13 +68,13 @@ template $GcalEventWidget: Widget {
spacing: 4;
$GcalFadingLabel summary_label {
label: bind template.event as <$GcalEvent>.summary;
text: bind template.event as <$GcalEvent>.summary;
hexpand: true;
vexpand: true;
}
Label timestamp_label {
halign: end;
hexpand: true;
vexpand: true;
styles [
"dimmed",
+140 -78
View File
@@ -8,6 +8,8 @@
/* Copied from libadwaita 1.8 and adapted for use in gnome-calendar. */
#define G_LOG_DOMAIN "GcalFadingLabel"
#include "config.h"
#include "gcal-fading-label.h"
@@ -20,7 +22,16 @@ struct _GcalFadingLabel
{
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;
};
@@ -28,7 +39,7 @@ G_DEFINE_FINAL_TYPE (GcalFadingLabel, gcal_fading_label, GTK_TYPE_WIDGET)
enum {
PROP_0,
PROP_LABEL,
PROP_TEXT,
PROP_ALIGN,
LAST_PROP
};
@@ -39,10 +50,9 @@ static gboolean
is_rtl (GcalFadingLabel *self)
{
PangoDirection pango_direction = PANGO_DIRECTION_NEUTRAL;
const gchar *label = gcal_fading_label_get_label (self);
if (label)
pango_direction = gcal_find_base_dir (label, -1);
if (self->text)
pango_direction = gcal_find_base_dir (self->text, -1);
if (pango_direction == PANGO_DIRECTION_RTL)
return TRUE;
@@ -53,6 +63,46 @@ is_rtl (GcalFadingLabel *self)
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
gcal_fading_label_measure (GtkWidget *widget,
GtkOrientation orientation,
@@ -64,39 +114,34 @@ gcal_fading_label_measure (GtkWidget *widget,
{
GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
gtk_widget_measure (self->label,
orientation,
for_size,
min, nat,
min_baseline, nat_baseline);
switch (orientation)
{
case GTK_ORIENTATION_VERTICAL:
{
int line_pixels, baseline;
if (orientation == GTK_ORIENTATION_HORIZONTAL && min)
*min = 0;
}
line_pixels = get_line_pixels (self, &baseline);
static void
gcal_fading_label_size_allocate (GtkWidget *widget,
gint width,
gint height,
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;
*min = line_pixels;
*nat = line_pixels;
}
break;
gtk_widget_measure (self->label,
GTK_ORIENTATION_HORIZONTAL,
height,
NULL,
&child_width,
NULL, NULL);
case GTK_ORIENTATION_HORIZONTAL:
*min = 0;
*nat = 0;
break;
offset = (width - child_width) * align;
transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (offset, 0));
default:
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
@@ -104,41 +149,49 @@ gcal_fading_label_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GcalFadingLabel *self = GCAL_FADING_LABEL (widget);
g_autoptr (GskRenderNode) node = NULL;
g_autoptr (GtkSnapshot) child_snapshot = NULL;
graphene_rect_t bounds;
gfloat align = is_rtl (self) ? 1 - self->align : self->align;
gint width = gtk_widget_get_width (widget);
gint clipped_size;
float align;
int height;
int width;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
if (width <= 0)
return;
clipped_size = gtk_widget_get_width (self->label) - width;
if (clipped_size <= 0)
if (!self->cached_extents_valid)
{
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;
}
child_snapshot = gtk_snapshot_new ();
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;
align = is_rtl (self) ? 1 - self->align : self->align;
gtk_snapshot_push_mask (snapshot, GSK_MASK_MODE_INVERTED_ALPHA);
if (align > 0)
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, bounds.origin.y,
FADE_WIDTH, bounds.size.height),
&GRAPHENE_RECT_INIT (0, 0, FADE_WIDTH, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (FADE_WIDTH, 0),
(GskColorStop[2]) {
@@ -151,8 +204,7 @@ gcal_fading_label_snapshot (GtkWidget *widget,
if (align < 1)
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (width - FADE_WIDTH, bounds.origin.y,
FADE_WIDTH, bounds.size.height),
&GRAPHENE_RECT_INIT (width - FADE_WIDTH, 0, FADE_WIDTH, height),
&GRAPHENE_POINT_INIT (width, 0),
&GRAPHENE_POINT_INIT (width - FADE_WIDTH, 0),
(GskColorStop[2]) {
@@ -164,8 +216,8 @@ gcal_fading_label_snapshot (GtkWidget *widget,
gtk_snapshot_pop (snapshot);
gtk_snapshot_push_clip (snapshot, &bounds);
gtk_snapshot_append_node (snapshot, node);
gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_append_node (snapshot, self->cached_text_node);
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);
switch (prop_id) {
case PROP_LABEL:
g_value_set_string (value, gcal_fading_label_get_label (self));
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_ALIGN:
@@ -202,8 +254,8 @@ gcal_fading_label_set_property (GObject *object,
GcalFadingLabel *self = GCAL_FADING_LABEL (object);
switch (prop_id) {
case PROP_LABEL:
gcal_fading_label_set_label (self, g_value_get_string (value));
case PROP_TEXT:
gcal_fading_label_set_text (self, g_value_get_string (value));
break;
case PROP_ALIGN:
@@ -220,7 +272,8 @@ gcal_fading_label_dispose (GObject *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);
}
@@ -235,12 +288,12 @@ gcal_fading_label_class_init (GcalFadingLabelClass *klass)
object_class->set_property = gcal_fading_label_set_property;
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->size_allocate = gcal_fading_label_size_allocate;
widget_class->snapshot = gcal_fading_label_snapshot;
props[PROP_LABEL] =
g_param_spec_string ("label", NULL, NULL,
props[PROP_TEXT] =
g_param_spec_string ("text", NULL, NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
@@ -255,32 +308,41 @@ gcal_fading_label_class_init (GcalFadingLabelClass *klass)
static void
gcal_fading_label_init (GcalFadingLabel *self)
{
self->label = gtk_label_new (NULL);
gtk_label_set_single_line_mode (GTK_LABEL (self->label), TRUE);
gtk_widget_set_parent (self->label, GTK_WIDGET (self));
self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), NULL);
self->cached_ascent = -1;
self->cached_descent = -1;
self->cached_extents_valid = FALSE;
}
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);
return gtk_label_get_label (GTK_LABEL (self->label));
return self->text;
}
void
gcal_fading_label_set_label (GcalFadingLabel *self,
const gchar *label)
gcal_fading_label_set_text (GcalFadingLabel *self,
const gchar *text)
{
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;
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
@@ -304,7 +366,7 @@ gcal_fading_label_set_align (GcalFadingLabel *self,
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]);
}
+2 -2
View File
@@ -18,8 +18,8 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (GcalFadingLabel, gcal_fading_label, GCAL, FADING_LABEL, GtkWidget)
const gchar *gcal_fading_label_get_label (GcalFadingLabel *self);
void gcal_fading_label_set_label (GcalFadingLabel *self,
const gchar *gcal_fading_label_get_text (GcalFadingLabel *self);
void gcal_fading_label_set_text (GcalFadingLabel *self,
const gchar *label);
gfloat gcal_fading_label_get_align (GcalFadingLabel *self);
void gcal_fading_label_set_align (GcalFadingLabel *self,