[gtk/range-selector] Add a quick demo for a range selector
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/range-selector] Add a quick demo for a range selector
- Date: Sat, 18 Jul 2020 22:25:50 +0000 (UTC)
commit 6e029c1877ca173cb3b99f904fb8eea6dbbc7cd0
Author: Matthias Clasen <mclasen redhat com>
Date: Sat Jul 18 18:23:57 2020 -0400
Add a quick demo for a range selector
This is a widget that looks like a GtkScale,
except that it has two sliders instead of one.
It lets you select a subrange of a given range.
Only horizontal for now, and reusing the GtkScale
styling.
tests/meson.build | 1 +
tests/testrange.c | 659 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 660 insertions(+)
---
diff --git a/tests/meson.build b/tests/meson.build
index 4618df6b97..2e6e66f44a 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,5 +1,6 @@
gtk_tests = [
# testname, optional extra sources
+ ['testrange'],
['testdropdown'],
['rendernode'],
['rendernode-create-tests'],
diff --git a/tests/testrange.c b/tests/testrange.c
new file mode 100644
index 0000000000..59e9b6e74a
--- /dev/null
+++ b/tests/testrange.c
@@ -0,0 +1,659 @@
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE (DemoSlider, demo_slider, DEMO, SLIDER, GtkWidget)
+
+struct _DemoSlider
+{
+ GtkWidget parent_instance;
+};
+
+struct _DemoSliderClass
+{
+ GtkWidgetClass parent_class;
+};
+
+G_DECLARE_FINAL_TYPE (DemoHighlight, demo_highlight, DEMO, HIGHLIGHT, GtkWidget)
+
+struct _DemoHighlight
+{
+ GtkWidget parent_instance;
+};
+
+struct _DemoHighlightClass
+{
+ GtkWidgetClass parent_class;
+};
+
+G_DECLARE_FINAL_TYPE (DemoTrough, demo_trough, DEMO, TROUGH, GtkWidget)
+
+struct _DemoTrough
+{
+ GtkWidget parent_instance;
+};
+
+struct _DemoTroughClass
+{
+ GtkWidgetClass parent_class;
+};
+
+G_DECLARE_FINAL_TYPE (DemoWidget, demo_widget, DEMO, WIDGET, GtkWidget)
+
+struct _DemoWidget
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *trough;
+ GtkWidget *highlight;
+ GtkWidget *min_slider;
+ GtkWidget *max_slider;
+
+ GtkWidget *grab_location;
+
+ double range_min;
+ double range_max;
+ double min_value;
+ double max_value;
+
+ gboolean shift;
+};
+
+enum
+{
+ PROP_RANGE_MIN = 1,
+ PROP_RANGE_MAX,
+ PROP_MIN_VALUE,
+ PROP_MAX_VALUE,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL };
+
+struct _DemoWidgetClass
+{
+ GtkWidgetClass parent_class;
+};
+
+G_DEFINE_TYPE (DemoSlider, demo_slider, GTK_TYPE_WIDGET)
+
+static void
+demo_slider_init (DemoSlider *slider)
+{
+}
+
+static void
+demo_slider_class_init (DemoSliderClass *class)
+{
+ gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "slider");
+}
+
+static GtkWidget *
+demo_slider_new (void)
+{
+ return g_object_new (demo_slider_get_type (), NULL);
+}
+
+G_DEFINE_TYPE (DemoHighlight, demo_highlight, GTK_TYPE_WIDGET)
+
+static void
+demo_highlight_init (DemoHighlight *highlight)
+{
+}
+
+static void
+demo_highlight_class_init (DemoHighlightClass *class)
+{
+ gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (class), "highlight");
+}
+
+static GtkWidget *
+demo_highlight_new (void)
+{
+ return g_object_new (demo_highlight_get_type (), NULL);
+}
+
+G_DEFINE_TYPE (DemoTrough, demo_trough, GTK_TYPE_WIDGET)
+
+static void
+demo_trough_init (DemoTrough *trough)
+{
+}
+
+static void
+demo_trough_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ DemoWidget *demo = DEMO_WIDGET (gtk_widget_get_parent (widget));
+ int min1, nat1;
+ int min2, nat2;
+ int min3, nat3;
+
+ gtk_widget_measure (demo->min_slider,
+ orientation, -1,
+ &min1, &nat1,
+ NULL, NULL);
+
+ gtk_widget_measure (demo->max_slider,
+ orientation, -1,
+ &min2, &nat2,
+ NULL, NULL);
+
+ gtk_widget_measure (demo->highlight,
+ orientation, for_size,
+ &min3, &nat3,
+ NULL, NULL);
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ *minimum = MAX (min1 + min2, min3);
+ *natural = MAX (nat1 + nat2, nat3);
+ }
+ else
+ {
+ *minimum = MAX (MAX (min1, min2), min3);
+ *natural = MAX (MAX (nat1, nat2), min3);
+ }
+}
+
+static void
+allocate_slider (DemoWidget *demo,
+ GtkWidget *slider,
+ int x)
+{
+ int width, height;
+ int trough_width, trough_height;
+ int y;
+
+ gtk_widget_measure (slider,
+ GTK_ORIENTATION_HORIZONTAL, -1,
+ &width, NULL,
+ NULL, NULL);
+ gtk_widget_measure (slider,
+ GTK_ORIENTATION_VERTICAL, -1,
+ &height, NULL,
+ NULL, NULL);
+
+ trough_width = gtk_widget_get_width (demo->trough);
+ trough_height = gtk_widget_get_height (demo->trough);
+
+ y = floor ((trough_height - height) / 2);
+
+ gtk_widget_size_allocate (slider,
+ &(GtkAllocation) { x, y, width, height},
+ -1);
+}
+
+static void
+demo_trough_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ DemoWidget *demo = DEMO_WIDGET (gtk_widget_get_parent (widget));
+ int min, max;
+
+ min = floor (width * (demo->min_value - demo->range_min) / (demo->range_max - demo->range_min));
+ max = floor (width * (demo->max_value - demo->range_min) / (demo->range_max - demo->range_min));
+
+ allocate_slider (demo, demo->min_slider, min);
+ allocate_slider (demo, demo->max_slider, max);
+
+ gtk_widget_size_allocate (demo->highlight,
+ &(GtkAllocation) { min, 0, max - min, height},
+ -1);
+}
+
+static void
+demo_trough_class_init (DemoTroughClass *class)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ widget_class->measure = demo_trough_measure;
+ widget_class->size_allocate = demo_trough_size_allocate;
+
+ gtk_widget_class_set_css_name (widget_class, "trough");
+}
+
+static GtkWidget *
+demo_trough_new (void)
+{
+ return g_object_new (demo_trough_get_type (), NULL);
+}
+
+G_DEFINE_TYPE (DemoWidget, demo_widget, GTK_TYPE_WIDGET)
+
+static void
+click_gesture_pressed (GtkGestureClick *gesture,
+ guint n_press,
+ double x,
+ double y,
+ DemoWidget *demo)
+{
+ guint button;
+ GdkModifierType state;
+
+ demo->grab_location = gtk_widget_pick (GTK_WIDGET (demo), x, y, 0);
+
+ button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+ state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture));
+
+ demo->shift = (button == GDK_BUTTON_PRIMARY && ((state & GDK_SHIFT_MASK) != 0)) ||
+ button == GDK_BUTTON_SECONDARY;
+}
+
+static void
+click_gesture_released (GtkGestureClick *gesture,
+ guint n_press,
+ double x,
+ double y,
+ DemoWidget *demo)
+{
+ demo->grab_location = NULL;
+}
+
+static void
+drag_gesture_begin (GtkGestureDrag *gesture,
+ double offset_x,
+ double offset_y,
+ DemoWidget *demo)
+{
+ if (demo->grab_location == demo->min_slider ||
+ demo->grab_location == demo->max_slider)
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+drag_gesture_update (GtkGestureDrag *gesture,
+ double offset_x,
+ double offset_y,
+ DemoWidget *demo)
+{
+ double start_x, start_y;
+ int width;
+ double value;
+ double size;
+
+ gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
+
+ width = gtk_widget_get_width (GTK_WIDGET (demo));
+
+ value = ((start_x + offset_x) / width) * (demo->range_max - demo->range_min) + demo->range_min;
+ value = CLAMP (value, demo->range_min, demo->range_max);
+ size = demo->max_value - demo->min_value;
+
+ if (demo->shift)
+ {
+ if (demo->grab_location == demo->min_slider)
+ {
+ demo->max_value = MIN (demo->range_max, value + size);
+ demo->min_value = demo->max_value - size;
+ }
+ else if (demo->grab_location == demo->max_slider)
+ {
+ demo->min_value = MAX (demo->range_min, value - size);
+ demo->max_value = demo->min_value + size;
+ }
+ }
+ else
+ {
+ if (demo->grab_location == demo->min_slider)
+ {
+ demo->min_value = value;
+ demo->max_value = MAX (demo->max_value, value);
+ }
+ else if (demo->grab_location == demo->max_slider)
+ {
+ demo->min_value = MIN (demo->min_value, value);
+ demo->max_value = value;
+ }
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MIN_VALUE]);
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MAX_VALUE]);
+
+ gtk_widget_queue_allocate (GTK_WIDGET (demo));
+}
+
+static void
+demo_widget_init (DemoWidget *demo)
+{
+ GtkGesture *click_gesture, *drag_gesture;
+
+ demo->range_min = 0;
+ demo->range_max = 0;
+ demo->min_value = 0;
+ demo->max_value = 0;
+
+ demo->trough = demo_trough_new ();
+ gtk_widget_set_parent (demo->trough, GTK_WIDGET (demo));
+ demo->highlight = demo_highlight_new ();
+ gtk_widget_set_parent (demo->highlight, demo->trough);
+ demo->min_slider = demo_slider_new ();
+ gtk_widget_set_parent (demo->min_slider, demo->trough);
+ demo->max_slider = demo_slider_new ();
+ gtk_widget_set_parent (demo->max_slider, demo->trough);
+
+ drag_gesture = gtk_gesture_drag_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag_gesture), 0);
+ g_signal_connect (drag_gesture, "drag-begin", G_CALLBACK (drag_gesture_begin), demo);
+ g_signal_connect (drag_gesture, "drag-update", G_CALLBACK (drag_gesture_update), demo);
+ gtk_widget_add_controller (GTK_WIDGET (demo), GTK_EVENT_CONTROLLER (drag_gesture));
+
+ click_gesture = gtk_gesture_click_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click_gesture), 0);
+ g_signal_connect (click_gesture, "pressed", G_CALLBACK (click_gesture_pressed), demo);
+ g_signal_connect (click_gesture, "released", G_CALLBACK (click_gesture_released), demo);
+ gtk_widget_add_controller (GTK_WIDGET (demo), GTK_EVENT_CONTROLLER (click_gesture));
+
+ gtk_gesture_group (click_gesture, drag_gesture);
+}
+
+static void
+demo_widget_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ DemoWidget *demo = DEMO_WIDGET (widget);
+
+ gtk_widget_measure (demo->trough,
+ orientation, -1,
+ minimum, natural,
+ NULL, NULL);
+}
+
+static void
+demo_widget_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ DemoWidget *demo = DEMO_WIDGET (widget);
+ int min_height;
+
+ gtk_widget_measure (demo->trough,
+ GTK_ORIENTATION_VERTICAL, -1,
+ &min_height, NULL,
+ NULL, NULL);
+
+ gtk_widget_size_allocate (demo->trough,
+ &(GtkAllocation) { 0, 0, width, min_height},
+ -1);
+}
+
+static void
+demo_widget_dispose (GObject *object)
+{
+ DemoWidget *demo = DEMO_WIDGET (object);
+
+ g_clear_pointer (&demo->trough, gtk_widget_unparent);
+
+ G_OBJECT_CLASS (demo_widget_parent_class)->dispose (object);
+}
+
+static void
+demo_widget_set_range (DemoWidget *demo,
+ double range_min,
+ double range_max)
+{
+ double value;
+
+ g_return_if_fail (range_min <= range_max);
+
+ g_object_freeze_notify (G_OBJECT (demo));
+
+ if (demo->range_min != range_min)
+ {
+ demo->range_min = range_min;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_RANGE_MIN]);
+ }
+
+ if (demo->range_max != range_max)
+ {
+ demo->range_max = range_max;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_RANGE_MAX]);
+ }
+
+ value = CLAMP (demo->min_value, range_min, range_max);
+ if (demo->min_value != value)
+ {
+ demo->min_value = value;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MIN_VALUE]);
+ }
+
+ value = CLAMP (demo->max_value, range_min, range_max);
+ if (demo->max_value != value)
+ {
+ demo->max_value = value;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MAX_VALUE]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (demo));
+}
+
+static void
+demo_widget_set_values (DemoWidget *demo,
+ double min_value,
+ double max_value)
+{
+ double value;
+
+ g_return_if_fail (min_value <= max_value);
+
+ g_object_freeze_notify (G_OBJECT (demo));
+
+ value = CLAMP (min_value, demo->range_min, demo->range_max);
+ if (demo->min_value != value)
+ {
+ demo->min_value = value;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MIN_VALUE]);
+ }
+
+ value = CLAMP (max_value, demo->range_min, demo->range_max);
+ if (demo->max_value != value)
+ {
+ demo->max_value = value;
+ g_object_notify_by_pspec (G_OBJECT (demo), properties[PROP_MAX_VALUE]);
+ }
+
+ g_object_thaw_notify (G_OBJECT (demo));
+}
+
+static void
+demo_widget_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DemoWidget *demo = DEMO_WIDGET (object);
+ double v;
+
+ switch (prop_id)
+ {
+ case PROP_RANGE_MIN:
+ v = g_value_get_double (value);
+ demo_widget_set_range (demo, v, MAX (v, demo->range_max));
+ break;
+
+ case PROP_RANGE_MAX:
+ v = g_value_get_double (value);
+ demo_widget_set_range (demo, MIN (v, demo->range_min), v);
+ break;
+
+ case PROP_MIN_VALUE:
+ v = g_value_get_double (value);
+ demo_widget_set_values (demo, v, MAX (v, demo->max_value));
+ break;
+
+ case PROP_MAX_VALUE:
+ v = g_value_get_double (value);
+ demo_widget_set_values (demo, MIN (v, demo->min_value), v);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+demo_widget_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DemoWidget *demo = DEMO_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_RANGE_MIN:
+ g_value_set_double (value, demo->range_min);
+ break;
+
+ case PROP_RANGE_MAX:
+ g_value_set_double (value, demo->range_max);
+ break;
+
+ case PROP_MIN_VALUE:
+ g_value_set_double (value, demo->min_value);
+ break;
+
+ case PROP_MAX_VALUE:
+ g_value_set_double (value, demo->max_value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+demo_widget_class_init (DemoWidgetClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->dispose = demo_widget_dispose;
+ object_class->set_property = demo_widget_set_property;
+ object_class->get_property = demo_widget_get_property;
+
+ widget_class->measure = demo_widget_measure;
+ widget_class->size_allocate = demo_widget_size_allocate;
+
+ properties[PROP_RANGE_MIN] = g_param_spec_double ("range-min",
+ "Range Min",
+ "Range Min",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_RANGE_MAX] = g_param_spec_double ("range-max",
+ "Range Max",
+ "Range Max",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_MIN_VALUE] = g_param_spec_double ("min-value",
+ "Min Value",
+ "Min Value",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+ properties[PROP_MAX_VALUE] = g_param_spec_double ("max-value",
+ "Max Value",
+ "Max Value",
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ gtk_widget_class_set_css_name (widget_class, "scale");
+}
+
+static GtkWidget *
+demo_widget_new (void)
+{
+ return g_object_new (demo_widget_get_type (), NULL);
+}
+
+static void
+update_range_label (GObject *object,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DemoWidget *demo = DEMO_WIDGET (object);
+
+ if (pspec->name == g_intern_static_string ("range-min") ||
+ pspec->name == g_intern_static_string ("range-max"))
+ {
+ char *text;
+
+ text = g_strdup_printf ("Allowed values: [%.1f, %.1f]\n", demo->range_min, demo->range_max);
+ gtk_label_set_label (GTK_LABEL (data), text);
+ g_free (text);
+ }
+}
+
+static void
+update_values_label (GObject *object,
+ GParamSpec *pspec,
+ gpointer data)
+{
+ DemoWidget *demo = DEMO_WIDGET (object);
+
+ if (pspec->name == g_intern_static_string ("min-value") ||
+ pspec->name == g_intern_static_string ("max-value"))
+ {
+ char *text;
+
+ text = g_strdup_printf ("Selected range: [%.1f, %.1f]\n", demo->min_value, demo->max_value);
+ gtk_label_set_label (GTK_LABEL (data), text);
+ g_free (text);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWindow *window;
+ GtkWidget *box;
+ GtkWidget *demo;
+ GtkWidget *label;
+
+ gtk_init ();
+
+ window = GTK_WINDOW (gtk_window_new ());
+ gtk_window_set_title (window, "Pick a range");
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+ gtk_window_set_child (window, box);
+
+ demo = demo_widget_new ();
+ gtk_widget_set_halign (demo, GTK_ALIGN_FILL);
+ gtk_widget_set_valign (demo, GTK_ALIGN_CENTER);
+ gtk_widget_set_hexpand (demo, TRUE);
+
+ gtk_box_append (GTK_BOX (box), demo);
+
+ label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (label), 0);
+ g_signal_connect (demo, "notify", G_CALLBACK (update_range_label), label);
+ gtk_box_append (GTK_BOX (box), label);
+
+ label = gtk_label_new ("");
+ gtk_label_set_xalign (GTK_LABEL (label), 0);
+ g_signal_connect (demo, "notify", G_CALLBACK (update_values_label), label);
+ gtk_box_append (GTK_BOX (box), label);
+
+ demo_widget_set_range (DEMO_WIDGET (demo), 0, 1000);
+ demo_widget_set_values (DEMO_WIDGET (demo), 100, 500);
+
+ gtk_window_present (window);
+
+ while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
+ g_main_context_iteration (NULL, TRUE);
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]