[libadwaita/wip/cdavis/spin-row] Add AdwSpinRow
- From: Christopher Davis <christopherdavis src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/cdavis/spin-row] Add AdwSpinRow
- Date: Fri, 22 Jul 2022 20:48:15 +0000 (UTC)
commit f5ce3b94f8447c7f9c5d998e13c36af1550b3608
Author: Christopher Davis <christopherdavis gnome org>
Date: Mon Jul 11 17:47:34 2022 -0400
Add AdwSpinRow
demo/pages/lists/adw-demo-page-lists.ui | 14 +
src/adw-spin-row.c | 618 ++++++++++++++++++++++++++++++++
src/adw-spin-row.h | 99 +++++
src/adw-spin-row.ui | 105 ++++++
src/adwaita.gresources.xml | 1 +
src/adwaita.h | 1 +
src/meson.build | 2 +
src/stylesheet/widgets/_lists.scss | 80 ++++-
8 files changed, 903 insertions(+), 17 deletions(-)
---
diff --git a/demo/pages/lists/adw-demo-page-lists.ui b/demo/pages/lists/adw-demo-page-lists.ui
index 03780e56..406e43de 100644
--- a/demo/pages/lists/adw-demo-page-lists.ui
+++ b/demo/pages/lists/adw-demo-page-lists.ui
@@ -105,6 +105,20 @@
<property name="title" translatable="yes">Password Entry</property>
</object>
</child>
+ <child>
+ <object class="AdwSpinRow">
+ <property name="title" translatable="yes">Spin Row</property>
+ <property name="adjustment">
+ <object class="GtkAdjustment">
+ <property name="lower">1998</property>
+ <property name="upper">2022</property>
+ <property name="value">1998</property>
+ <property name="page-increment">10</property>
+ <property name="step-increment">1</property>
+ </object>
+ </property>
+ </object>
+ </child>
</object>
</child>
<child>
diff --git a/src/adw-spin-row.c b/src/adw-spin-row.c
new file mode 100644
index 00000000..58d623c7
--- /dev/null
+++ b/src/adw-spin-row.c
@@ -0,0 +1,618 @@
+/*
+ * Copyright 2022 Christopher Davis <christopherdavis gnome org>
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <math.h>
+
+#include "adw-spin-row.h"
+
+#include "adw-macros-private.h"
+#include "adw-widget-utils-private.h"
+
+#define MAX_DIGITS 20
+
+/**
+ * AdwSpinRow
+ */
+
+struct _AdwSpinRow
+{
+ AdwPreferencesRow parent_instance;
+
+ GtkWidget *header;
+ GtkWidget *title;
+ GtkWidget *subtitle;
+ GtkWidget *prefixes;
+ GtkWidget *suffixes;
+ GtkWidget *title_box;
+
+ GtkWidget *spin_button;
+};
+
+static void adw_spin_row_editable_init (GtkEditableInterface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (AdwSpinRow, adw_spin_row, ADW_TYPE_PREFERENCES_ROW,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, adw_spin_row_editable_init))
+
+enum {
+ PROP_0,
+ PROP_ADJUSTMENT,
+ PROP_CLIMB_RATE,
+ PROP_DIGITS,
+ PROP_NUMERIC,
+ PROP_SNAP_TO_TICKS,
+ PROP_UPDATE_POLICY,
+ PROP_VALUE,
+ PROP_WRAP,
+ PROP_LAST_PROP,
+};
+
+static GParamSpec *props[PROP_LAST_PROP];
+
+enum {
+ SIGNAL_INPUT,
+ SIGNAL_OUTPUT,
+ SIGNAL_VALUE_CHANGED,
+ SIGNAL_WRAPPED,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static gboolean
+boolean_handled_accumulator (GSignalInvocationHint *ihint,
+ GValue *return_accu,
+ const GValue *handler_return,
+ gpointer dummy)
+{
+ gboolean continue_emission;
+ gboolean signal_handled;
+
+ signal_handled = g_value_get_boolean (handler_return);
+ g_value_set_boolean (return_accu, signal_handled);
+ continue_emission = !signal_handled;
+
+ return continue_emission;
+}
+
+static gboolean
+string_is_not_empty (AdwSpinRow *self,
+ const char *string)
+{
+ return string && string[0];
+}
+
+static void
+pressed_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ AdwSpinRow *self)
+{
+ GtkWidget *picked;
+
+ picked = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT);
+
+ if (picked != GTK_WIDGET (self) &&
+ picked != self->header &&
+ picked != GTK_WIDGET (self->prefixes) &&
+ picked != GTK_WIDGET (self->suffixes)) {
+ gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+
+ return;
+ }
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->spin_button));
+
+ gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+spin_button_state_flags_changed_cb (AdwSpinRow *self)
+{
+ GtkStateFlags flags = gtk_widget_get_state_flags (self->spin_button);
+
+ if (flags & GTK_STATE_FLAG_FOCUS_WITHIN) {
+ gtk_widget_add_css_class (GTK_WIDGET (self), "focused");
+ } else {
+ gtk_widget_remove_css_class (GTK_WIDGET (self), "focused");
+ }
+}
+
+static gboolean
+spin_button_keynav_failed_cb (AdwSpinRow *self,
+ GtkDirectionType direction)
+{
+ if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
+ return gtk_widget_child_focus (GTK_WIDGET (self), direction);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static int
+spin_button_input_cb (AdwSpinRow *self,
+ double *new_value)
+{
+ int return_value;
+
+ g_signal_emit (self, signals[SIGNAL_INPUT], 0, &new_value, &return_value);
+
+ return return_value;
+}
+
+static gboolean
+spin_button_output_cb (AdwSpinRow *self)
+{
+ gboolean return_value;
+
+ g_signal_emit (self, signals[SIGNAL_OUTPUT], 0, &return_value);
+
+ return return_value;
+}
+
+static void
+spin_button_value_changed_cb (AdwSpinRow *self)
+{
+ g_signal_emit (self, signals[SIGNAL_VALUE_CHANGED], 0);
+}
+
+static void
+spin_button_wrapped_cb (AdwSpinRow *self)
+{
+ g_signal_emit (self, signals[SIGNAL_WRAPPED], 0);
+}
+
+static gboolean
+adw_spin_row_grab_focus (GtkWidget *widget)
+{
+ AdwSpinRow *self = ADW_SPIN_ROW (widget);
+
+ return gtk_widget_grab_focus (self->spin_button);
+}
+
+static void
+adw_spin_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwSpinRow *self = ADW_SPIN_ROW (object);
+
+ if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
+ return;
+
+ switch (prop_id) {
+ case PROP_ADJUSTMENT:
+ g_value_set_object (value, adw_spin_row_get_adjustment (self));
+ break;
+ case PROP_CLIMB_RATE:
+ g_value_set_double (value, adw_spin_row_get_climb_rate (self));
+ break;
+ case PROP_DIGITS:
+ g_value_set_uint (value, adw_spin_row_get_digits (self));
+ break;
+ case PROP_NUMERIC:
+ g_value_set_boolean (value, adw_spin_row_get_numeric (self));
+ break;
+ case PROP_SNAP_TO_TICKS:
+ g_value_set_boolean (value, adw_spin_row_get_snap_to_ticks (self));
+ break;
+ case PROP_UPDATE_POLICY:
+ g_value_set_enum (value, adw_spin_row_get_update_policy (self));
+ break;
+ case PROP_VALUE:
+ g_value_set_double (value, adw_spin_row_get_value (self));
+ break;
+ case PROP_WRAP:
+ g_value_set_boolean (value, adw_spin_row_get_wrap (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_spin_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwSpinRow *self = ADW_SPIN_ROW (object);
+
+ if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
+ return;
+
+ switch (prop_id) {
+ case PROP_ADJUSTMENT:
+ adw_spin_row_set_adjustment (self, g_value_get_object (value));
+ break;
+ case PROP_CLIMB_RATE:
+ adw_spin_row_set_climb_rate (self, g_value_get_double (value));
+ break;
+ case PROP_DIGITS:
+ adw_spin_row_set_digits (self, g_value_get_uint (value));
+ break;
+ case PROP_NUMERIC:
+ adw_spin_row_set_numeric (self, g_value_get_boolean (value));
+ break;
+ case PROP_SNAP_TO_TICKS:
+ adw_spin_row_set_snap_to_ticks (self, g_value_get_boolean (value));
+ break;
+ case PROP_UPDATE_POLICY:
+ adw_spin_row_set_update_policy (self, g_value_get_enum (value));
+ break;
+ case PROP_VALUE:
+ adw_spin_row_set_value (self, g_value_get_double (value));
+ break;
+ case PROP_WRAP:
+ adw_spin_row_set_wrap (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_spin_row_class_init (AdwSpinRowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = adw_spin_row_get_property;
+ object_class->set_property = adw_spin_row_set_property;
+
+ widget_class->focus = adw_widget_focus_child;
+ widget_class->grab_focus = adw_spin_row_grab_focus;
+
+ props[PROP_ADJUSTMENT] =
+ g_param_spec_object ("adjustment", NULL, NULL,
+ GTK_TYPE_ADJUSTMENT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_CLIMB_RATE] =
+ g_param_spec_double ("climb-rate", NULL, NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_DIGITS] =
+ g_param_spec_uint ("digits", NULL, NULL,
+ 0, MAX_DIGITS, 0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_NUMERIC] =
+ g_param_spec_boolean ("numeric", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_SNAP_TO_TICKS] =
+ g_param_spec_boolean ("snap-to-ticks", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_UPDATE_POLICY] =
+ g_param_spec_enum ("update-policy", NULL, NULL,
+ GTK_TYPE_SPIN_BUTTON_UPDATE_POLICY,
+ GTK_UPDATE_ALWAYS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_VALUE] =
+ g_param_spec_double ("value", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ props[PROP_WRAP] =
+ g_param_spec_boolean ("wrap", NULL, NULL,
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
+
+ gtk_editable_install_properties (object_class, PROP_LAST_PROP);
+
+ signals[SIGNAL_INPUT] =
+ g_signal_new ("input",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_INT, 1,
+ G_TYPE_POINTER);
+
+ signals[SIGNAL_OUTPUT] =
+ g_signal_new ("output",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ boolean_handled_accumulator, NULL,
+ NULL,
+ G_TYPE_BOOLEAN, 0);
+
+ signals[SIGNAL_VALUE_CHANGED] =
+ g_signal_new ("value-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_WRAPPED] =
+ g_signal_new ("wrapped",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/Adwaita/ui/adw-spin-row.ui");
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, header);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, prefixes);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, suffixes);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, title_box);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, title);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, subtitle);
+ gtk_widget_class_bind_template_child (widget_class, AdwSpinRow, spin_button);
+
+ gtk_widget_class_bind_template_callback (widget_class, string_is_not_empty);
+ gtk_widget_class_bind_template_callback (widget_class, pressed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_state_flags_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_keynav_failed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_input_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_output_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_value_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, spin_button_wrapped_cb);
+
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TEXT_BOX);
+}
+
+static void
+adw_spin_row_init (AdwSpinRow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+ gtk_editable_init_delegate (GTK_EDITABLE (self));
+}
+
+static GtkEditable *
+adw_spin_row_editable_get_delegate (GtkEditable *editable)
+{
+ AdwSpinRow *self = ADW_SPIN_ROW (editable);
+ return GTK_EDITABLE (self->spin_button);
+}
+
+void
+adw_spin_row_editable_init (GtkEditableInterface *iface)
+{
+ iface->get_delegate = adw_spin_row_editable_get_delegate;
+}
+
+GtkWidget *
+adw_spin_row_new (GtkAdjustment *adjustment,
+ double climb_rate,
+ guint digits)
+{
+ AdwSpinRow *self = g_object_new (ADW_TYPE_SPIN_ROW, NULL);
+
+ adw_spin_row_configure (self, adjustment, climb_rate, digits);
+
+ return GTK_WIDGET (self);
+}
+
+GtkWidget *
+adw_spin_row_new_with_range (double min,
+ double max,
+ double step)
+{
+
+ AdwSpinRow *self = g_object_new (ADW_TYPE_SPIN_ROW, NULL);
+ GtkAdjustment *adjustment;
+ int digits;
+
+ g_return_val_if_fail (min <= max, NULL);
+ g_return_val_if_fail (step != 0.0, NULL);
+
+ adjustment = gtk_adjustment_new (min, min, max, step, 10 * step, 0);
+
+ if (fabs (step) >= 1.0 || step == 0.0) {
+ digits = 0;
+ } else {
+ digits = abs ((int) floor (log10 (fabs (step))));
+ if (digits > MAX_DIGITS)
+ digits = MAX_DIGITS;
+ }
+
+ adw_spin_row_configure (self, adjustment, step, digits);
+
+ adw_spin_row_set_numeric (self, TRUE);
+
+ return GTK_WIDGET (self);
+}
+
+void
+adw_spin_row_configure (AdwSpinRow *self,
+ GtkAdjustment *adjustment,
+ double climb_rate,
+ guint digits)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_spin_row_set_adjustment (self, adjustment);
+ adw_spin_row_set_climb_rate (self, climb_rate);
+ adw_spin_row_set_digits (self, digits);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+GtkAdjustment *
+adw_spin_row_get_adjustment (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), NULL);
+
+ return gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_adjustment (AdwSpinRow *self,
+ GtkAdjustment *adjustment)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (adjustment == adw_spin_row_get_adjustment (self))
+ return;
+
+ gtk_spin_button_set_adjustment (GTK_SPIN_BUTTON (self->spin_button), adjustment);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ADJUSTMENT]);
+}
+
+double
+adw_spin_row_get_climb_rate (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), 0.0);
+
+ return gtk_spin_button_get_climb_rate (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_climb_rate (AdwSpinRow *self,
+ double climb_rate)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (climb_rate == adw_spin_row_get_climb_rate (self))
+ return;
+
+ gtk_spin_button_set_climb_rate (GTK_SPIN_BUTTON (self->spin_button), climb_rate);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CLIMB_RATE]);
+}
+
+guint
+adw_spin_row_get_digits (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), 0);
+
+ return gtk_spin_button_get_digits (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_digits (AdwSpinRow *self,
+ guint digits)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (digits == adw_spin_row_get_digits (self))
+ return;
+
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (self->spin_button), digits);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DIGITS]);
+}
+
+gboolean
+adw_spin_row_get_numeric (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), FALSE);
+
+ return gtk_spin_button_get_numeric (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_numeric (AdwSpinRow *self,
+ gboolean numeric)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (numeric == adw_spin_row_get_numeric (self))
+ return;
+
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (self->spin_button), numeric);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NUMERIC]);
+}
+
+gboolean
+adw_spin_row_get_snap_to_ticks (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), FALSE);
+
+ return gtk_spin_button_get_snap_to_ticks (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_snap_to_ticks (AdwSpinRow *self,
+ gboolean snap_to_ticks)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (snap_to_ticks == adw_spin_row_get_snap_to_ticks (self))
+ return;
+
+ gtk_spin_button_set_snap_to_ticks (GTK_SPIN_BUTTON (self->spin_button), snap_to_ticks);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SNAP_TO_TICKS]);
+}
+
+GtkSpinButtonUpdatePolicy
+adw_spin_row_get_update_policy (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), GTK_UPDATE_ALWAYS);
+
+ return gtk_spin_button_get_update_policy (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_update_policy (AdwSpinRow *self,
+ GtkSpinButtonUpdatePolicy policy)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (policy == adw_spin_row_get_update_policy (self))
+ return;
+
+ gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (self->spin_button), policy);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_UPDATE_POLICY]);
+}
+
+double
+adw_spin_row_get_value (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), 0.0);
+
+ return gtk_spin_button_get_value (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_value (AdwSpinRow *self,
+ double value)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (value == adw_spin_row_get_value (self))
+ return;
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->spin_button), value);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VALUE]);
+}
+
+gboolean
+adw_spin_row_get_wrap (AdwSpinRow *self)
+{
+ g_return_val_if_fail (ADW_IS_SPIN_ROW (self), FALSE);
+
+ return gtk_spin_button_get_wrap (GTK_SPIN_BUTTON (self->spin_button));
+}
+
+void
+adw_spin_row_set_wrap (AdwSpinRow *self,
+ gboolean wrap)
+{
+ g_return_if_fail (ADW_IS_SPIN_ROW (self));
+
+ if (wrap == adw_spin_row_get_wrap (self))
+ return;
+
+ gtk_spin_button_set_wrap (GTK_SPIN_BUTTON (self->spin_button), wrap);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_WRAP]);
+}
diff --git a/src/adw-spin-row.h b/src/adw-spin-row.h
new file mode 100644
index 00000000..6b4d4552
--- /dev/null
+++ b/src/adw-spin-row.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2022 Christopher Davis <christopherdavis gnome org>
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-version.h"
+
+#include <gtk/gtk.h>
+
+#include "adw-preferences-row.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_SPIN_ROW (adw_spin_row_get_type ())
+
+ADW_AVAILABLE_IN_1_2
+G_DECLARE_FINAL_TYPE (AdwSpinRow, adw_spin_row, ADW, SPIN_ROW, AdwPreferencesRow)
+
+ADW_AVAILABLE_IN_1_2
+GtkWidget *adw_spin_row_new (GtkAdjustment *adjustment,
+ double climb_rate,
+ guint digits) G_GNUC_WARN_UNUSED_RESULT;
+ADW_AVAILABLE_IN_1_2
+GtkWidget *adw_spin_row_new_with_range (double min,
+ double max,
+ double step) G_GNUC_WARN_UNUSED_RESULT;
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_configure (AdwSpinRow *self,
+ GtkAdjustment *adjustment,
+ double climb_rate,
+ guint digits);
+
+ADW_AVAILABLE_IN_1_2
+GtkAdjustment *adw_spin_row_get_adjustment (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_adjustment (AdwSpinRow *self,
+ GtkAdjustment *adjustment);
+
+ADW_AVAILABLE_IN_1_2
+double adw_spin_row_get_climb_rate (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_climb_rate (AdwSpinRow *self,
+ double climb_rate);
+
+ADW_AVAILABLE_IN_1_2
+guint adw_spin_row_get_digits (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_digits (AdwSpinRow *self,
+ guint digits);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_spin_row_get_numeric (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_numeric (AdwSpinRow *self,
+ gboolean numeric);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_spin_row_get_snap_to_ticks (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_snap_to_ticks (AdwSpinRow *self,
+ gboolean snap_to_ticks);
+
+ADW_AVAILABLE_IN_1_2
+GtkSpinButtonUpdatePolicy adw_spin_row_get_update_policy (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_update_policy (AdwSpinRow *self,
+ GtkSpinButtonUpdatePolicy policy);
+
+ADW_AVAILABLE_IN_1_2
+double adw_spin_row_get_value (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_value (AdwSpinRow *self,
+ double value);
+
+ADW_AVAILABLE_IN_1_2
+gboolean adw_spin_row_get_wrap (AdwSpinRow *self);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_set_wrap (AdwSpinRow *self,
+ gboolean wrap);
+
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_add_prefix (AdwSpinRow *self,
+ GtkWidget *widget);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_add_suffix (AdwSpinRow *self,
+ GtkWidget *widget);
+ADW_AVAILABLE_IN_1_2
+void adw_spin_row_remove (AdwSpinRow *self,
+ GtkWidget *widget);
+
+G_END_DECLS
+
diff --git a/src/adw-spin-row.ui b/src/adw-spin-row.ui
new file mode 100644
index 00000000..01258e97
--- /dev/null
+++ b/src/adw-spin-row.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="libadwaita">
+ <requires lib="gtk" version="4.0"/>
+ <template class="AdwSpinRow" parent="AdwPreferencesRow">
+ <accessibility>
+ <relation name="labelled-by">title</relation>
+ </accessibility>
+ <property name="activatable">True</property>
+ <child>
+ <object class="GtkGestureClick">
+ <signal name="pressed" handler="pressed_cb"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="header">
+ <property name="valign">center</property>
+ <style>
+ <class name="header"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="prefixes">
+ <property name="visible">False</property>
+ <style>
+ <class name="prefixes"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="title_box">
+ <property name="orientation">vertical</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <style>
+ <class name="title"/>
+ </style>
+ <child>
+ <object class="GtkLabel" id="title">
+ <binding name="visible">
+ <closure function="string_is_not_empty" type="gboolean">
+ <lookup name="label">title</lookup>
+ </closure>
+ </binding>
+ <property name="ellipsize">none</property>
+ <property name="label" bind-source="AdwSpinRow" bind-property="title"
bind-flags="sync-create"/>
+ <property name="lines">0</property>
+ <property name="mnemonic-widget">AdwSpinRow</property>
+ <property name="use-underline" bind-source="AdwSpinRow" bind-property="use-underline"
bind-flags="sync-create"/>
+ <property name="selectable" bind-source="AdwSpinRow" bind-property="title-selectable"
bind-flags="sync-create"/>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="xalign">0</property>
+ <property name="use-markup" bind-source="AdwSpinRow" bind-property="use-markup"
bind-flags="sync-create"/>
+ <style>
+ <class name="title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="subtitle">
+ <binding name="visible">
+ <closure function="string_is_not_empty" type="gboolean">
+ <lookup name="label">subtitle</lookup>
+ </closure>
+ </binding>
+ <property name="ellipsize">none</property>
+ <property name="lines">0</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ <property name="xalign">0</property>
+ <property name="use-markup" bind-source="AdwSpinRow" bind-property="use-markup"
bind-flags="sync-create"/>
+ <style>
+ <class name="subtitle"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_button">
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <signal name="state-flags-changed" handler="spin_button_state_flags_changed_cb" swapped="yes"/>
+ <signal name="keynav-failed" handler="spin_button_keynav_failed_cb" swapped="yes"/>
+ <signal name="input" handler="spin_button_input_cb" swapped="yes"/>
+ <signal name="output" handler="spin_button_output_cb" swapped="yes"/>
+ <signal name="value-changed" handler="spin_button_value_changed_cb" swapped="yes"/>
+ <signal name="wrapped" handler="spin_button_wrapped_cb" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="suffixes">
+ <property name="visible">False</property>
+ <style>
+ <class name="suffixes"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="spin"/>
+ </style>
+ </template>
+</interface>
diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml
index 58a0d7c4..df251529 100644
--- a/src/adwaita.gresources.xml
+++ b/src/adwaita.gresources.xml
@@ -21,6 +21,7 @@
<file preprocess="xml-stripblanks">adw-preferences-group.ui</file>
<file preprocess="xml-stripblanks">adw-preferences-page.ui</file>
<file preprocess="xml-stripblanks">adw-preferences-window.ui</file>
+ <file preprocess="xml-stripblanks">adw-spin-row.ui</file>
<file preprocess="xml-stripblanks">adw-status-page.ui</file>
<file preprocess="xml-stripblanks">adw-tab.ui</file>
<file preprocess="xml-stripblanks">adw-tab-bar.ui</file>
diff --git a/src/adwaita.h b/src/adwaita.h
index 509a9740..8f03dcbb 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -55,6 +55,7 @@ G_BEGIN_DECLS
#include "adw-preferences-page.h"
#include "adw-preferences-row.h"
#include "adw-preferences-window.h"
+#include "adw-spin-row.h"
#include "adw-split-button.h"
#include "adw-spring-animation.h"
#include "adw-spring-params.h"
diff --git a/src/meson.build b/src/meson.build
index b2c47498..be03752c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -115,6 +115,7 @@ src_headers = [
'adw-preferences-page.h',
'adw-preferences-row.h',
'adw-preferences-window.h',
+ 'adw-spin-row.h',
'adw-split-button.h',
'adw-spring-animation.h',
'adw-spring-params.h',
@@ -179,6 +180,7 @@ src_sources = [
'adw-preferences-page.c',
'adw-preferences-row.c',
'adw-preferences-window.c',
+ 'adw-spin-row.c',
'adw-split-button.c',
'adw-spring-animation.c',
'adw-spring-params.c',
diff --git a/src/stylesheet/widgets/_lists.scss b/src/stylesheet/widgets/_lists.scss
index 5cc2c6f8..582da82e 100644
--- a/src/stylesheet/widgets/_lists.scss
+++ b/src/stylesheet/widgets/_lists.scss
@@ -127,30 +127,76 @@ row {
row.entry {
@include focus-ring($focus-state: '.focused', $offset: -1px);
- &:not(:selected).activatable.focused:hover,
- &:not(:selected).activatable.focused:active {
- background-color: transparent;
- }
+ @at-root %entry_row & {
+ &:not(:selected).activatable.focused:hover,
+ &:not(:selected).activatable.focused:active {
+ background-color: transparent;
+ }
- .edit-icon, .indicator {
- min-width: 24px;
- min-height: 24px;
- padding: 5px;
- }
+ .edit-icon, .indicator {
+ min-width: 24px;
+ min-height: 24px;
+ padding: 5px;
+ }
+
+ .edit-icon:disabled {
+ opacity: $strong_disabled_opacity;
+ }
+
+ .indicator {
+ opacity: $dimmer_opacity;
+ }
- .edit-icon:disabled {
- opacity: $strong_disabled_opacity;
+ &.monospace {
+ font-family: inherit;
+
+ text {
+ font-family: monospace;
+ }
+ }
}
- .indicator {
- opacity: $dimmer_opacity;
+ @each $e_type, $e_color in (error, $error_color),
+ (warning, $warning_color),
+ (success, $success_color) {
+ &.#{$e_type} {
+ @include focus-ring($focus-state: '.focused', $offset: -1px, $fc: gtkalpha(currentColor,
$focus_border_opacity));
+
+ text {
+ > selection:focus-within { background-color: gtkalpha($e_color, .2); }
+
+ > cursor-handle > contents { background-color: currentColor; }
+ }
+ }
}
+}
- &.monospace {
- font-family: inherit;
+/***************
+ * AdwSpinRow *
+ ***************/
+
+row.spin {
+ @include focus-ring($focus-state: '.focused', $offset: -1px);
- text {
- font-family: monospace;
+ @extend %entry_row;
+
+ spinbutton {
+ background: none;
+ border-spacing: 6px;
+ box-shadow: none;
+
+ &, &:focus {
+ outline: none;
+ }
+
+ >
button.image-button.up:not(.flat):not(.raised):not(.suggested-action):not(.destructive-action):not(.opaque):last-child,
+ >
button.image-button.down:not(.flat):not(.raised):not(.suggested-action):not(.destructive-action):not(.opaque)
{
+ &, &:dir(ltr):last-child, &:dir(rtl):first-child {
+ @extend %button_basic;
+ @extend %circular_button;
+
+ border: none;
+ }
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]