[gnome-builder] egg: add EggSimpleLabel
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] egg: add EggSimpleLabel
- Date: Fri, 22 Apr 2016 06:50:38 +0000 (UTC)
commit 567c02b5ef84b44e3e0dc666c69635626e6470e9
Author: Christian Hergert <chergert redhat com>
Date: Thu Apr 21 23:46:40 2016 -0700
egg: add EggSimpleLabel
GtkLabel is a lovely piece of engineering, but teaching it to avoid size
requests under the requirements we have is much harder than just rendering
pango layouts ourself.
In particular, it is very important that we avoid a queue_resize() when
changing the contents of the label when possible. We detect this with
the width-chars property and knowning that our content is numeric.
We do perform queue_resize() in a variety of situations, such as the
length of the new string being larger than our width-chars, or when
we drop back under the width-chars size.
This does not currently support baseline due to the tab bar in Builder
not supporting baseline yet.
contrib/egg/Makefile.am | 2 +
contrib/egg/egg-simple-label.c | 390 ++++++++++++++++++++++++++++++++++++++++
contrib/egg/egg-simple-label.h | 50 +++++
3 files changed, 442 insertions(+), 0 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index dcfe4f5..2447d61 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -26,6 +26,7 @@ headers_DATA = \
egg-settings-flag-action.h \
egg-settings-sandwich.h \
egg-signal-group.h \
+ egg-simple-label.h \
egg-simple-popover.h \
egg-slider.h \
egg-state-machine-action.h \
@@ -54,6 +55,7 @@ libegg_private_la_SOURCES = \
egg-settings-flag-action.c \
egg-settings-sandwich.c \
egg-signal-group.c \
+ egg-simple-label.c \
egg-simple-popover.c \
egg-slider.c \
egg-state-machine-action.c \
diff --git a/contrib/egg/egg-simple-label.c b/contrib/egg/egg-simple-label.c
new file mode 100644
index 0000000..5908f56
--- /dev/null
+++ b/contrib/egg/egg-simple-label.c
@@ -0,0 +1,390 @@
+/* egg-simple-label.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <alloca.h>
+#include <string.h>
+
+#include "egg-simple-label.h"
+
+struct _EggSimpleLabel
+{
+ GtkWidget parent_instance;
+
+ gchar *label;
+ guint label_len;
+
+ gint width_chars;
+
+ PangoLayout *cached_layout;
+
+ gfloat xalign;
+
+ gint cached_width_request;
+ gint cached_height_request;
+ gint real_width;
+ gint real_height;
+};
+
+G_DEFINE_TYPE (EggSimpleLabel, egg_simple_label, GTK_TYPE_WIDGET)
+
+enum {
+ PROP_0,
+ PROP_LABEL,
+ PROP_WIDTH_CHARS,
+ PROP_XALIGN,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+egg_simple_label_calculate_size (EggSimpleLabel *self)
+{
+ PangoContext *context;
+ PangoLayout *layout;
+
+ g_assert (EGG_IS_SIMPLE_LABEL (self));
+
+ self->cached_height_request = -1;
+ self->cached_width_request = -1;
+
+ if (self->label == NULL && self->width_chars <= 0)
+ {
+ self->cached_height_request = 0;
+ self->cached_width_request = 0;
+ self->real_width = 0;
+ self->real_height = 0;
+ return;
+ }
+
+ if (NULL == (context = gtk_widget_get_pango_context (GTK_WIDGET (self))))
+ return;
+
+ g_clear_object (&self->cached_layout);
+
+ layout = pango_layout_new (context);
+
+ if (self->width_chars >= 0)
+ {
+ gchar *str = alloca (self->width_chars);
+ memset (str, '9', self->width_chars);
+ pango_layout_set_text (layout, str, self->width_chars);
+ }
+ else
+ {
+ pango_layout_set_text (layout, self->label, self->label_len);
+ }
+
+ pango_layout_get_pixel_size (layout,
+ &self->cached_width_request,
+ &self->cached_height_request);
+
+ if (self->label != NULL)
+ pango_layout_set_text (layout, self->label, self->label_len);
+ else
+ pango_layout_set_text (layout, "", 0);
+
+ pango_layout_get_pixel_size (layout, &self->real_width, &self->real_height);
+
+ if (self->real_width > self->cached_width_request)
+ self->cached_width_request = self->real_width;
+
+ if (self->real_height > self->cached_height_request)
+ self->cached_height_request = self->real_height;
+
+ self->cached_layout = layout;
+}
+
+static void
+egg_simple_label_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ EggSimpleLabel *self = (EggSimpleLabel *)widget;
+
+ g_assert (EGG_IS_SIMPLE_LABEL (self));
+
+ if (self->cached_width_request == -1)
+ egg_simple_label_calculate_size (self);
+
+ *min_width = *nat_width = self->cached_width_request;
+}
+
+static void
+egg_simple_label_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ EggSimpleLabel *self = (EggSimpleLabel *)widget;
+
+ g_assert (EGG_IS_SIMPLE_LABEL (self));
+
+ if (self->cached_height_request == -1)
+ egg_simple_label_calculate_size (self);
+
+ *min_height = *nat_height = self->cached_height_request;
+}
+
+static gboolean
+egg_simple_label_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ EggSimpleLabel *self = (EggSimpleLabel *)widget;
+ GtkAllocation alloc;
+ gdouble x;
+ gdouble y;
+
+ if (self->label == NULL)
+ return GDK_EVENT_PROPAGATE;
+
+ gtk_widget_get_allocation (widget, &alloc);
+
+ if (self->cached_width_request == -1 ||
+ self->cached_height_request == -1 ||
+ self->cached_layout == NULL)
+ egg_simple_label_calculate_size (self);
+
+ x = (alloc.width - self->real_width) * self->xalign;
+ y = (alloc.height - self->real_height) / 2;
+
+ /*
+ * We should support baseline here, but we don't actually
+ * get a real baseline yet where this is used in Builder,
+ * so I'm going to punt on it.
+ */
+
+ gtk_render_layout (gtk_widget_get_style_context (widget),
+ cr, x, y, self->cached_layout);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+egg_simple_label_destroy (GtkWidget *widget)
+{
+ EggSimpleLabel *self = (EggSimpleLabel *)widget;
+
+ g_clear_pointer (&self->label, g_free);
+ g_clear_object (&self->cached_layout);
+
+ GTK_WIDGET_CLASS (egg_simple_label_parent_class)->destroy (widget);
+}
+
+static void
+egg_simple_label_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggSimpleLabel *self = EGG_SIMPLE_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ g_value_set_string (value, self->label);
+ break;
+
+ case PROP_WIDTH_CHARS:
+ g_value_set_int (value, self->width_chars);
+
+ case PROP_XALIGN:
+ g_value_set_float (value, self->xalign);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_simple_label_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggSimpleLabel *self = EGG_SIMPLE_LABEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_LABEL:
+ egg_simple_label_set_label (self, g_value_get_string (value));
+ break;
+
+ case PROP_WIDTH_CHARS:
+ egg_simple_label_set_width_chars (self, g_value_get_int (value));
+ break;
+
+ case PROP_XALIGN:
+ egg_simple_label_set_xalign (self, g_value_get_float (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_simple_label_class_init (EggSimpleLabelClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = egg_simple_label_get_property;
+ object_class->set_property = egg_simple_label_set_property;
+
+ widget_class->destroy = egg_simple_label_destroy;
+ widget_class->draw = egg_simple_label_draw;
+ widget_class->get_preferred_width = egg_simple_label_get_preferred_width;
+ widget_class->get_preferred_height = egg_simple_label_get_preferred_height;
+
+ gtk_widget_class_set_css_name (widget_class, "label");
+
+ properties [PROP_LABEL] =
+ g_param_spec_string ("label",
+ NULL,
+ NULL,
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_WIDTH_CHARS] =
+ g_param_spec_int ("width-chars",
+ NULL,
+ NULL,
+ -1,
+ 1000,
+ -1,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_XALIGN] =
+ g_param_spec_float ("xalign",
+ NULL,
+ NULL,
+ 0.0,
+ 1.0,
+ 0.5,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+egg_simple_label_init (EggSimpleLabel *self)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+ self->width_chars = -1;
+ self->xalign = 0.5;
+}
+
+GtkWidget *
+egg_simple_label_new (const gchar *label)
+{
+ return g_object_new (EGG_TYPE_SIMPLE_LABEL, NULL);
+}
+
+const gchar *
+egg_simple_label_get_label (EggSimpleLabel *self)
+{
+ g_return_val_if_fail (EGG_IS_SIMPLE_LABEL (self), NULL);
+
+ return self->label;
+}
+
+void
+egg_simple_label_set_label (EggSimpleLabel *self,
+ const gchar *label)
+{
+ g_return_if_fail (EGG_IS_SIMPLE_LABEL (self));
+
+ if (g_strcmp0 (label, self->label) != 0)
+ {
+ guint last_len = self->label_len;
+
+ g_free (self->label);
+
+ self->label = g_strdup (label);
+ self->label_len = label ? strlen (label) : 0;
+
+ self->cached_width_request = -1;
+ self->cached_height_request = -1;
+
+ /*
+ * If width chars is not set, then we always have to calculate the size
+ * change. If we are growing larger, we also might have to relcalculate
+ * if the new length is larger than our precalculated length. If we are
+ * shrinking from an overgrow position, we also have to resize.
+ *
+ * But in *most* cases, we can avoid the resize altogether. This is
+ * a necessity in the situations where this widget is valuable (such
+ * as the cursor coordinate label in Builder).
+ */
+ if ((self->width_chars < 0) ||
+ ((self->label_len > self->width_chars) && (last_len != self->label_len)) ||
+ ((last_len > self->width_chars) && (self->label_len <= self->width_chars)))
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LABEL]);
+ }
+}
+
+gint
+egg_simple_label_get_width_chars (EggSimpleLabel *self)
+{
+ g_return_val_if_fail (EGG_IS_SIMPLE_LABEL (self), -1);
+
+ return self->width_chars;
+}
+
+void
+egg_simple_label_set_width_chars (EggSimpleLabel *self,
+ gint width_chars)
+{
+ g_return_if_fail (EGG_IS_SIMPLE_LABEL (self));
+ g_return_if_fail (width_chars >= -1);
+ g_return_if_fail (width_chars <= 100);
+
+ if (self->width_chars != width_chars)
+ {
+ self->width_chars = width_chars;
+ self->cached_width_request = -1;
+ self->cached_height_request = -1;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WIDTH_CHARS]);
+ }
+}
+
+gfloat
+egg_simple_label_get_xalign (EggSimpleLabel *self)
+{
+ g_return_val_if_fail (EGG_IS_SIMPLE_LABEL (self), 0.0);
+
+ return self->xalign;
+}
+
+void
+egg_simple_label_set_xalign (EggSimpleLabel *self,
+ gfloat xalign)
+{
+ if (self->xalign != xalign)
+ {
+ self->xalign = xalign;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_XALIGN]);
+ }
+}
diff --git a/contrib/egg/egg-simple-label.h b/contrib/egg/egg-simple-label.h
new file mode 100644
index 0000000..25e6974
--- /dev/null
+++ b/contrib/egg/egg-simple-label.h
@@ -0,0 +1,50 @@
+/* egg-simple-label.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_SIMPLE_LABEL_H
+#define EGG_SIMPLE_LABEL_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * This widget has one very simple purpose. Allow updating a simple
+ * amount of text without causing resizes to propagate up the widget
+ * hierarchy. Therefore, it only supports a very minimal amount of
+ * features. The label text, and the width-chars to use for sizing.
+ */
+
+#define EGG_TYPE_SIMPLE_LABEL (egg_simple_label_get_type())
+
+G_DECLARE_FINAL_TYPE (EggSimpleLabel, egg_simple_label, EGG, SIMPLE_LABEL, GtkWidget)
+
+GtkWidget *egg_simple_label_new (const gchar *label);
+const gchar *egg_simple_label_get_label (EggSimpleLabel *self);
+void egg_simple_label_set_label (EggSimpleLabel *self,
+ const gchar *label);
+gint egg_simple_label_get_width_chars (EggSimpleLabel *self);
+void egg_simple_label_set_width_chars (EggSimpleLabel *self,
+ gint width_chars);
+gfloat egg_simple_label_get_xalign (EggSimpleLabel *self);
+void egg_simple_label_set_xalign (EggSimpleLabel *self,
+ gfloat xalign);
+
+G_END_DECLS
+
+#endif /* EGG_SIMPLE_LABEL_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]