[libadwaita/wip/exalm/spring: 38/38] s
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/spring: 38/38] s
- Date: Wed, 14 Apr 2021 07:42:28 +0000 (UTC)
commit 6a8a3ef3caa325cc917f6cffc98ce203a98fa966
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Fri Jan 8 18:26:26 2021 +0500
s
demo/adw-demo-window.c | 2 +
demo/adw-spring-animation-private.h | 59 +++
demo/adw-spring-animation.c | 320 ++++++++++++++
demo/adwaita-demo.gresources.xml | 8 +
.../scalable/actions/page-spring-symbolic.svg | 46 ++
.../scalable/actions/spring-graph-symbolic.svg | 55 +++
.../actions/spring-interactive-symbolic.svg | 81 ++++
.../icons/scalable/actions/spring-run-symbolic.svg | 67 +++
demo/meson.build | 12 +-
demo/pages/spring/adw-demo-adjustment-row.c | 145 ++++++
demo/pages/spring/adw-demo-adjustment-row.h | 11 +
demo/pages/spring/adw-demo-adjustment-row.ui | 61 +++
demo/pages/spring/adw-demo-page-spring.c | 193 ++++++++
demo/pages/spring/adw-demo-page-spring.h | 11 +
demo/pages/spring/adw-demo-page-spring.ui | 154 +++++++
demo/pages/spring/adw-demo-spring-basic.c | 487 +++++++++++++++++++++
demo/pages/spring/adw-demo-spring-basic.h | 13 +
demo/pages/spring/adw-demo-spring-basic.ui | 274 ++++++++++++
demo/pages/spring/adw-demo-spring-interactive.c | 292 ++++++++++++
demo/pages/spring/adw-demo-spring-interactive.h | 13 +
demo/pages/spring/adw-demo-spring-interactive.ui | 42 ++
demo/pages/spring/adw-demo-spring-preset.c | 174 ++++++++
demo/pages/spring/adw-demo-spring-preset.h | 17 +
demo/pages/spring/adw-demo-transform-layout.c | 190 ++++++++
demo/pages/spring/adw-demo-transform-layout.h | 18 +
demo/style.css | 81 ++++
26 files changed, 2825 insertions(+), 1 deletion(-)
---
diff --git a/demo/adw-demo-window.c b/demo/adw-demo-window.c
index c75b5eb..696aed8 100644
--- a/demo/adw-demo-window.c
+++ b/demo/adw-demo-window.c
@@ -6,6 +6,7 @@
#include "pages/controls/adw-demo-page-controls.h"
#include "pages/lists/adw-demo-page-lists.h"
+#include "pages/spring/adw-demo-page-spring.h"
#include "pages/stub/adw-demo-page-stub.h"
struct _AdwDemoWindow
@@ -95,6 +96,7 @@ adw_demo_window_init (AdwDemoWindow *self)
ADD_STUB (list, _("Feedback"));
ADD_STUB (list, _("Status Page"));
ADD_STUB (list, _("Avatars"));
+ ADD_PAGE (list, _("Spring Animations"), "page-spring-symbolic", ADW_TYPE_DEMO_PAGE_SPRING);
ADD_STUB (list, _("Menus"));
ADD_STUB (list, _("Preferences"));
ADD_STUB (list, _("Keyboard Shortcuts"));
diff --git a/demo/adw-spring-animation-private.h b/demo/adw-spring-animation-private.h
new file mode 100644
index 0000000..09ced3b
--- /dev/null
+++ b/demo/adw-spring-animation-private.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_SPRING_ANIMATION (adw_spring_animation_get_type())
+
+typedef void (*AdwAnimationValueCallback) (gdouble value,
+ gpointer user_data);
+typedef void (*AdwAnimationDoneCallback) (gpointer user_data);
+
+typedef struct _AdwSpringAnimation AdwSpringAnimation;
+
+GType adw_spring_animation_get_type (void) G_GNUC_CONST;
+
+AdwSpringAnimation *adw_spring_animation_new (GtkWidget *widget,
+ gdouble from,
+ gdouble to,
+ gdouble velocity,
+ gdouble damping,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble epsilon,
+ AdwAnimationValueCallback value_cb,
+ AdwAnimationDoneCallback done_cb,
+ gpointer user_data);
+
+AdwSpringAnimation *adw_spring_animation_new_with_damping_ratio (GtkWidget *widget,
+ gdouble from,
+ gdouble to,
+ gdouble velocity,
+ gdouble damping_ratio,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble epsilon,
+ AdwAnimationValueCallback value_cb,
+ AdwAnimationDoneCallback done_cb,
+ gpointer user_data);
+
+AdwSpringAnimation *adw_spring_animation_ref (AdwSpringAnimation *self);
+void adw_spring_animation_unref (AdwSpringAnimation *self);
+
+void adw_spring_animation_start (AdwSpringAnimation *self);
+void adw_spring_animation_stop (AdwSpringAnimation *self);
+
+gdouble adw_spring_animation_get_value (AdwSpringAnimation *self);
+
+gdouble adw_spring_animation_get_estimated_duration (AdwSpringAnimation *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (AdwSpringAnimation, adw_spring_animation_unref)
+
+G_END_DECLS
diff --git a/demo/adw-spring-animation.c b/demo/adw-spring-animation.c
new file mode 100644
index 0000000..f0a1fa7
--- /dev/null
+++ b/demo/adw-spring-animation.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ * Copyright (C) 2021 Manuel Genovés <manuel genoves gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "adw-spring-animation-private.h"
+
+#include <adwaita.h>
+#include <math.h>
+
+#define DELTA 0.001
+
+G_DEFINE_BOXED_TYPE (AdwSpringAnimation, adw_spring_animation, adw_spring_animation_ref,
adw_spring_animation_unref)
+
+struct _AdwSpringAnimation
+{
+ gatomicrefcount ref_count;
+
+ GtkWidget *widget;
+
+ gdouble value;
+
+ gdouble value_from;
+ gdouble value_to;
+
+ gdouble velocity;
+ gdouble damping;
+ gdouble mass;
+ gdouble stiffness;
+ gdouble epsilon;
+
+ gdouble estimated_duration;
+
+ gint64 start_time; /* ms */
+ guint tick_cb_id;
+
+ AdwAnimationValueCallback value_cb;
+ AdwAnimationDoneCallback done_cb;
+ gpointer user_data;
+};
+
+static void
+set_value (AdwSpringAnimation *self,
+ gdouble value)
+{
+ self->value = value;
+ self->value_cb (value, self->user_data);
+}
+
+/* Based on RBBSpringAnimation from RBBAnimation, MIT license.
+ * https://github.com/robb/RBBAnimation/blob/master/RBBAnimation/RBBSpringAnimation.m
+ */
+static gdouble
+oscillate (AdwSpringAnimation *self,
+ gdouble t)
+{
+ gdouble b = self->damping;
+ gdouble m = self->mass;
+ gdouble k = self->stiffness;
+ gdouble v0 = self->velocity;
+
+ gdouble beta = b / (2 * m);
+ gdouble omega0 = sqrt (k / m);
+
+ gdouble x0 = -1;
+
+ gdouble envelope = exp (-beta * t);
+
+ /*
+ * Solutions of the form C1*e^(lambda1*x) + C2*e^(lambda2*x)
+ * for the differential equation m*ẍ+b*ẋ+kx = 0
+ */
+
+ if (beta < omega0) { /* Underdamped */
+ gdouble omega1 = sqrt ((omega0 * omega0) - (beta * beta));
+
+ return -x0 + envelope * (x0 * cos (omega1 * t) + ((beta * x0 + v0) / omega1) * sin (omega1 * t));
+ }
+
+ if (beta > omega0) { /* Overdamped */
+ gdouble omega2 = sqrt ((beta * beta) - (omega0 * omega0));
+
+ return -x0 + envelope * (x0 * cosh (omega2 * t) + ((beta * x0 + v0) / omega2) * sinh (omega2 * t));
+ }
+
+ /* Critically damped */
+ return -x0 + envelope * (x0 + (beta * x0 + v0) * t);
+}
+
+static gdouble
+estimate_duration (AdwSpringAnimation *self)
+{
+ gdouble beta = self->damping / (2 * self->mass);
+ gdouble omega0;
+ gdouble x0, y0;
+ gdouble x1, y1;
+ gdouble m;
+
+ if (beta <= 0)
+ return INFINITY;
+
+ omega0 = sqrt (self->stiffness / self->mass);
+
+ /*
+ * As first ansatz for the overdamped solution,
+ * and general estimation for the oscillating ones
+ * we took the value of the envelope when its < epsilon
+ */
+ x0 = -log (self->epsilon) / beta;
+
+ if (beta <= omega0)
+ return x0;
+
+ /*
+ * Since the overdamped solution decays way slower than the envelope
+ * we need to use the value of the oscillation itself.
+ * Newton's root finding method is a good candidate in this particular case:
+ * https://en.wikipedia.org/wiki/Newton%27s_method
+ */
+ y0 = oscillate (self, x0);
+ m = (oscillate (self, x0 + DELTA) - y0) / DELTA;
+
+ x1 = (1 - y0 + m * x0) / m;
+ y1 = oscillate (self, x1);
+
+ while (ABS (1 - y1) > self->epsilon) {
+ x0 = x1;
+ y0 = y1;
+
+ m = (oscillate (self, x0 + DELTA) - y0) / DELTA;
+
+ x1 = (1 - y0 + m * x0) / m;
+ y1 = oscillate (self, x1);
+ }
+
+ return x1;
+}
+
+static inline gdouble
+lerp (gdouble a, gdouble b, gdouble t)
+{
+ return a * (1.0 - t) + b * t;
+}
+
+static gboolean
+tick_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ AdwSpringAnimation *self)
+{
+ gint64 frame_time = gdk_frame_clock_get_frame_time (frame_clock) / 1000;
+ gdouble t = (gdouble) (frame_time - self->start_time) / 1000;
+
+ if (t >= self->estimated_duration) {
+ self->tick_cb_id = 0;
+
+ set_value (self, self->value_to);
+
+ g_signal_handlers_disconnect_by_func (self->widget, adw_spring_animation_stop, self);
+
+ self->done_cb (self->user_data);
+
+ return G_SOURCE_REMOVE;
+ }
+
+ set_value (self, lerp (self->value_from, self->value_to, oscillate (self, t)));
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+adw_spring_animation_free (AdwSpringAnimation *self)
+{
+ adw_spring_animation_stop (self);
+
+ g_slice_free (AdwSpringAnimation, self);
+}
+
+AdwSpringAnimation *
+adw_spring_animation_new (GtkWidget *widget,
+ gdouble from,
+ gdouble to,
+ gdouble velocity,
+ gdouble damping,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble epsilon,
+ AdwAnimationValueCallback value_cb,
+ AdwAnimationDoneCallback done_cb,
+ gpointer user_data)
+{
+ AdwSpringAnimation *self;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+ g_return_val_if_fail (damping > 0, NULL);
+ g_return_val_if_fail (mass > 0, NULL);
+ g_return_val_if_fail (stiffness > 0, NULL);
+ g_return_val_if_fail (value_cb != NULL, NULL);
+ g_return_val_if_fail (done_cb != NULL, NULL);
+
+ self = g_slice_new0 (AdwSpringAnimation);
+
+ g_atomic_ref_count_init (&self->ref_count);
+
+ self->widget = widget;
+ self->value_from = from;
+ self->value_to = to;
+ self->velocity = velocity / (to - from);
+ self->damping = damping;
+ self->mass = mass;
+ self->stiffness = stiffness;
+ self->epsilon = epsilon;
+
+ self->value_cb = value_cb;
+ self->done_cb = done_cb;
+ self->user_data = user_data;
+
+ self->value = from;
+
+ self->estimated_duration = estimate_duration (self);
+
+ return self;
+}
+
+AdwSpringAnimation *
+adw_spring_animation_new_with_damping_ratio (GtkWidget *widget,
+ gdouble from,
+ gdouble to,
+ gdouble velocity,
+ gdouble damping_ratio,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble epsilon,
+ AdwAnimationValueCallback value_cb,
+ AdwAnimationDoneCallback done_cb,
+ gpointer user_data)
+{
+ gdouble critical_damping = 2 * sqrt (mass * stiffness);
+ gdouble damping = damping_ratio * critical_damping;
+
+ return adw_spring_animation_new (widget, from, to, velocity, damping, mass,
+ stiffness, epsilon, value_cb, done_cb, user_data);
+}
+
+AdwSpringAnimation *
+adw_spring_animation_ref (AdwSpringAnimation *self)
+{
+ g_return_val_if_fail (self != NULL, NULL);
+
+ g_atomic_ref_count_inc (&self->ref_count);
+
+ return self;
+}
+
+void
+adw_spring_animation_unref (AdwSpringAnimation *self)
+{
+ g_return_if_fail (self != NULL);
+
+ if (g_atomic_ref_count_dec (&self->ref_count))
+ adw_spring_animation_free (self);
+}
+
+void
+adw_spring_animation_start (AdwSpringAnimation *self)
+{
+ g_return_if_fail (self != NULL);
+
+ if (!adw_get_enable_animations (self->widget) ||
+ !gtk_widget_get_mapped (self->widget) ||
+ ABS (self->value_from - self->value_to) < self->epsilon) {
+ set_value (self, self->value_to);
+
+ self->done_cb (self->user_data);
+
+ return;
+ }
+
+ self->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (self->widget)) / 1000;
+
+ if (self->tick_cb_id)
+ return;
+
+ g_signal_connect_swapped (self->widget, "unmap",
+ G_CALLBACK (adw_spring_animation_stop), self);
+ self->tick_cb_id = gtk_widget_add_tick_callback (self->widget, (GtkTickCallback) tick_cb, self, NULL);
+}
+
+void
+adw_spring_animation_stop (AdwSpringAnimation *self)
+{
+ g_return_if_fail (self != NULL);
+
+ if (!self->tick_cb_id)
+ return;
+
+ gtk_widget_remove_tick_callback (self->widget, self->tick_cb_id);
+ self->tick_cb_id = 0;
+
+ g_signal_handlers_disconnect_by_func (self->widget, adw_spring_animation_stop, self);
+
+ self->done_cb (self->user_data);
+}
+
+gdouble
+adw_spring_animation_get_value (AdwSpringAnimation *self)
+{
+ g_return_val_if_fail (self != NULL, 0.0);
+
+ return self->value;
+}
+
+gdouble
+adw_spring_animation_get_estimated_duration (AdwSpringAnimation *self)
+{
+ g_return_val_if_fail (self != NULL, 0.0);
+
+ return self->estimated_duration;
+}
diff --git a/demo/adwaita-demo.gresources.xml b/demo/adwaita-demo.gresources.xml
index a03c6e5..3605859 100644
--- a/demo/adwaita-demo.gresources.xml
+++ b/demo/adwaita-demo.gresources.xml
@@ -13,10 +13,18 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/media-playback-start-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/open-link-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/page-lists-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/page-spring-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/spring-interactive-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/spring-graph-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/actions/spring-run-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/star-outline-thick-symbolic.svg</file>
<file preprocess="xml-stripblanks">pages/controls/adw-demo-page-controls.ui</file>
<file preprocess="xml-stripblanks">pages/lists/adw-demo-page-lists.ui</file>
+ <file preprocess="xml-stripblanks">pages/spring/adw-demo-adjustment-row.ui</file>
+ <file preprocess="xml-stripblanks">pages/spring/adw-demo-page-spring.ui</file>
+ <file preprocess="xml-stripblanks">pages/spring/adw-demo-spring-basic.ui</file>
+ <file preprocess="xml-stripblanks">pages/spring/adw-demo-spring-interactive.ui</file>
<file preprocess="xml-stripblanks">pages/stub/adw-demo-page-stub.ui</file>
<file preprocess="xml-stripblanks">adw-demo-page.ui</file>
diff --git a/demo/icons/scalable/actions/page-spring-symbolic.svg
b/demo/icons/scalable/actions/page-spring-symbolic.svg
new file mode 100644
index 0000000..82a72e4
--- /dev/null
+++ b/demo/icons/scalable/actions/page-spring-symbolic.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ width="16px"
+ height="16px"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg13">
+ <metadata
+ id="metadata19">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs17" />
+ <ellipse
+ style="opacity:0.35;fill:#2e3436"
+ id="path8195"
+ cx="3"
+ cy="13"
+ rx="3"
+ ry="3" />
+ <circle
+ style="opacity:0.5;fill:#2e3436"
+ id="ellipse8197"
+ cx="6"
+ cy="10"
+ r="4" />
+ <circle
+ style="fill:#2e3436"
+ id="ellipse8199"
+ cx="10"
+ cy="6"
+ r="5" />
+</svg>
diff --git a/demo/icons/scalable/actions/spring-graph-symbolic.svg
b/demo/icons/scalable/actions/spring-graph-symbolic.svg
new file mode 100644
index 0000000..0ee440c
--- /dev/null
+++ b/demo/icons/scalable/actions/spring-graph-symbolic.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ viewBox="0 0 6.3499999 6.3500002"
+ version="1.1"
+ id="svg59656"
+ sodipodi:docname="spring-graph-symbolic.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1016"
+ id="namedview842"
+ showgrid="false"
+ inkscape:zoom="13.366944"
+ inkscape:cx="22.135598"
+ inkscape:cy="17.974655"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg59656" />
+ <defs
+ id="defs59650" />
+ <metadata
+ id="metadata59653">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <path
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect
:none;fill:#241f31;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1"
+ d="M 3.1757812 0.52929688 C 2.8907242 0.52929688 2.6351672 0.62281489 2.4394531 0.78125 C 2.243739
0.93968511 2.1061129 1.1533792 2 1.3886719 C 1.7877743 1.8592573 1.6910188 2.4338546 1.5917969 2.9960938 C
1.4925749 3.5583329 1.3907762 4.1076956 1.2226562 4.4804688 C 1.1385963 4.6668553 1.039789 4.8050324
0.93164062 4.8925781 C 0.82349227 4.9801239 0.70726879 5.0273437 0.52929688 5.0273438 L 0 5.0273438 L 0
5.5566406 L 0.52929688 5.5566406 C 0.81435904 5.5566406 1.0699058 5.4631216 1.265625 5.3046875 C 1.4613442
5.1462534 1.5989614 4.9325588 1.7050781 4.6972656 C 1.9173116 4.2266793 2.0140591 3.6501307 2.1132812
3.0878906 C 2.2125034 2.5256505 2.3143074 1.9782427 2.4824219 1.6054688 C 2.5664791 1.4190818 2.6633408
1.2809041 2.7714844 1.1933594 C 2.8796279 1.1058147 2.9978175 1.0585937 3.1757812 1.0585938 C 3.353745
1.0585938 3.4699818 1.1058148 3.578125 1.1933594 C 3.6862682 1.2809039 3.7850845 1.4190821 3.8691406
1.6054688 C 4.037253 1.978242 4.1390676 2.5256518 4.2382812 3.08
78906 C 4.3374949 3.6501295 4.4323192 4.2266808 4.6445312 4.6972656 C 4.7506373 4.932558 4.8882781 5.1462507
5.0839844 5.3046875 C 5.2796907 5.4631243 5.535263 5.5566406 5.8203125 5.5566406 L 6.3496094 5.5566406 L
6.3496094 5.0273438 L 5.8203125 5.0273438 C 5.642361 5.0273438 5.5261047 4.9801211 5.4179688 4.8925781 C
5.3098328 4.8050351 5.2110052 4.6668561 5.1269531 4.4804688 C 4.958849 4.1076941 4.8570264 3.5583341
4.7578125 2.9960938 C 4.6585986 2.4338534 4.5618331 1.859258 4.3496094 1.3886719 C 4.2434975 1.1533788
4.1058702 0.93968528 3.9101562 0.78125 C 3.7144423 0.62281472 3.4608383 0.52929688 3.1757812 0.52929688 z "
+ id="path858" />
+</svg>
diff --git a/demo/icons/scalable/actions/spring-interactive-symbolic.svg
b/demo/icons/scalable/actions/spring-interactive-symbolic.svg
new file mode 100644
index 0000000..0cdd569
--- /dev/null
+++ b/demo/icons/scalable/actions/spring-interactive-symbolic.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ width="16.000002"
+ viewBox="0 0 16.000002 16.000006"
+ version="1.1"
+ id="svg7384"
+ height="16.000006">
+ <metadata
+ id="metadata90">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title
+ id="title9167">Gnome Symbolic Icon Theme</title>
+ <defs
+ id="defs7386">
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient7212">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop7214" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="translate(-261.0002,-177)"
+ style="display:inline"
+ id="layer9" />
+ <g
+ transform="translate(-19.999998,-544)"
+ id="layer1" />
+ <g
+ transform="translate(-19.999998,-544)"
+ style="display:inline"
+ id="layer10" />
+ <g
+ transform="translate(-19.999998,-544)"
+ id="g6387" />
+ <g
+ transform="translate(-19.999998,-544)"
+ id="layer11">
+ <path
+ d="M 28.0219,544 25,547.0148 l 6,10e-6 z"
+ id="path74740"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ <path
+ d="M 27.03125,546.03125 V 555 557.96875 H 29 V 555 546.03125 Z"
+ id="rect74742"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ <path
+ d="M 28.0219,560 25,557.00343 l 6,-10e-6 z"
+ id="path74756"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ <path
+ d="M 20,551.98724 22.99999,555.0057 23,549.01253 Z"
+ id="path74760"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ <path
+ d="m 22.03125,551 v 2 c 3.98964,0.007 7.97922,0.0322 11.96875,0 v -2 c -3.9883,0.10306 -7.97827,0.014
-11.96875,0 z"
+ id="rect74762"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ <path
+ d="M 36,551.98724 33.00001,555.0057 33,549.01253 Z"
+ id="path74764"
+
style="opacity:1;vector-effect:none;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
/>
+ </g>
+</svg>
diff --git a/demo/icons/scalable/actions/spring-run-symbolic.svg
b/demo/icons/scalable/actions/spring-run-symbolic.svg
new file mode 100644
index 0000000..43cfde1
--- /dev/null
+++ b/demo/icons/scalable/actions/spring-run-symbolic.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="24"
+ height="24"
+ viewBox="0 0 6.3499999 6.3500002"
+ version="1.1"
+ id="svg59656"
+ sodipodi:docname="spring-run-symbolic.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1016"
+ id="namedview7"
+ showgrid="true"
+ inkscape:zoom="12.572452"
+ inkscape:cx="0.391593"
+ inkscape:cy="15.302609"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg59656"
+ inkscape:document-rotation="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid853" />
+ </sodipodi:namedview>
+ <defs
+ id="defs59650" />
+ <metadata
+ id="metadata59653">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="g3032"
+ inkscape:label="media-playback-start"
+ transform="matrix(0.26458334,0,0,0.26458334,-10.318838,-127.79467)">
+ <path
+ sodipodi:nodetypes="ccccccsccccc"
+ inkscape:connector-curvature="0"
+ id="path3030"
+ d="m 46.00004,488.00319 v 13.99371 h 1.26818 0.131191 c 0.244769,0.001 0.486675,-0.0542
0.699686,-0.17493 l 9.795594,-5.59748 c 0.434783,-0.24054 0.655955,-0.7325 0.655955,-1.22445 0,-0.49194
-0.221172,-0.98391 -0.655955,-1.22445 l -9.795594,-5.59748 c -0.213011,-0.12076 -0.454917,-0.17676
-0.699686,-0.17492 H 47.26822 Z"
+
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:'Bitstream
Vera Sans';-inkscape-font-specification:'Bitstream Vera
Sans';text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-anchor:start;overflow:visible;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:2;enable-background:accumulate"
/>
+ </g>
+</svg>
diff --git a/demo/meson.build b/demo/meson.build
index 679cdff..8a64476 100644
--- a/demo/meson.build
+++ b/demo/meson.build
@@ -12,19 +12,29 @@ adwaita_demo_sources = [
'pages/controls/adw-demo-page-controls.c',
'pages/lists/adw-demo-page-lists.c',
+ 'pages/spring/adw-demo-adjustment-row.c',
+ 'pages/spring/adw-demo-page-spring.c',
+ 'pages/spring/adw-demo-spring-basic.c',
+ 'pages/spring/adw-demo-spring-interactive.c',
+ 'pages/spring/adw-demo-spring-preset.c',
+ 'pages/spring/adw-demo-transform-layout.c',
'pages/stub/adw-demo-page-stub.c',
'adwaita-demo.c',
'adw-demo-page.c',
'adw-demo-page-info.c',
'adw-demo-window.c',
+ 'adw-spring-animation.c',
libadwaita_generated_headers,
]
adwaita_demo = executable('adwaita-@0@-demo'.format(apiversion),
adwaita_demo_sources,
- dependencies: libadwaita_dep,
+ dependencies: [
+ libadwaita_dep,
+ cc.find_library('m', required: false),
+ ],
gui_app: true,
install: true,
)
diff --git a/demo/pages/spring/adw-demo-adjustment-row.c b/demo/pages/spring/adw-demo-adjustment-row.c
new file mode 100644
index 0000000..4a9ccdc
--- /dev/null
+++ b/demo/pages/spring/adw-demo-adjustment-row.c
@@ -0,0 +1,145 @@
+#include "adw-demo-adjustment-row.h"
+
+#include <glib/gi18n.h>
+
+struct _AdwDemoAdjustmentRow
+{
+ AdwBin parent_instance;
+
+ gchar *title;
+ guint digits;
+ GtkAdjustment *adjustment;
+};
+
+G_DEFINE_TYPE (AdwDemoAdjustmentRow, adw_demo_adjustment_row, ADW_TYPE_BIN);
+
+enum {
+ PROP_0,
+ PROP_TITLE,
+ PROP_DIGITS,
+ PROP_ADJUSTMENT,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static inline void
+set_string (gchar **dest,
+ const gchar *source)
+{
+ if (*dest)
+ g_free (*dest);
+
+ *dest = g_strdup (source);
+}
+
+static void
+adw_demo_adjustment_row_dispose (GObject *object)
+{
+ AdwDemoAdjustmentRow *self = ADW_DEMO_ADJUSTMENT_ROW (object);
+
+ g_clear_object (&self->adjustment);
+
+ G_OBJECT_CLASS (adw_demo_adjustment_row_parent_class)->dispose (object);
+}
+
+static void
+adw_demo_adjustment_row_finalize (GObject *object)
+{
+ AdwDemoAdjustmentRow *self = ADW_DEMO_ADJUSTMENT_ROW (object);
+
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (adw_demo_adjustment_row_parent_class)->finalize (object);
+}
+
+static void
+adw_demo_adjustment_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoAdjustmentRow *self = ADW_DEMO_ADJUSTMENT_ROW (object);
+
+ switch (prop_id) {
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+ case PROP_DIGITS:
+ g_value_set_uint (value, self->digits);
+ break;
+ case PROP_ADJUSTMENT:
+ g_value_set_object (value, self->adjustment);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_adjustment_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoAdjustmentRow *self = ADW_DEMO_ADJUSTMENT_ROW (object);
+
+ switch (prop_id) {
+ case PROP_TITLE:
+ set_string (&self->title, g_value_get_string (value));
+ break;
+ case PROP_DIGITS:
+ self->digits = g_value_get_uint (value);
+ break;
+ case PROP_ADJUSTMENT:
+ g_set_object (&self->adjustment, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_adjustment_row_class_init (AdwDemoAdjustmentRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = adw_demo_adjustment_row_dispose;
+ object_class->finalize = adw_demo_adjustment_row_finalize;
+ object_class->get_property = adw_demo_adjustment_row_get_property;
+ object_class->set_property = adw_demo_adjustment_row_set_property;
+
+ props[PROP_TITLE] =
+ g_param_spec_string ("title",
+ _("Title"),
+ _("Title"),
+ NULL,
+ G_PARAM_READWRITE);
+
+ props[PROP_DIGITS] =
+ g_param_spec_uint ("digits",
+ _("Digits"),
+ _("Digits"),
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_ADJUSTMENT] =
+ g_param_spec_object ("adjustment",
+ _("Adjustment"),
+ _("Adjustment"),
+ GTK_TYPE_ADJUSTMENT,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita/Demo/pages/spring/adw-demo-adjustment-row.ui");
+}
+
+static void
+adw_demo_adjustment_row_init (AdwDemoAdjustmentRow *self)
+{
+ self->digits = 0;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/demo/pages/spring/adw-demo-adjustment-row.h b/demo/pages/spring/adw-demo-adjustment-row.h
new file mode 100644
index 0000000..36f2fe9
--- /dev/null
+++ b/demo/pages/spring/adw-demo-adjustment-row.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_ADJUSTMENT_ROW (adw_demo_adjustment_row_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoAdjustmentRow, adw_demo_adjustment_row, ADW, DEMO_ADJUSTMENT_ROW, AdwBin)
+
+G_END_DECLS
diff --git a/demo/pages/spring/adw-demo-adjustment-row.ui b/demo/pages/spring/adw-demo-adjustment-row.ui
new file mode 100644
index 0000000..1027405
--- /dev/null
+++ b/demo/pages/spring/adw-demo-adjustment-row.ui
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <template class="AdwDemoAdjustmentRow" parent="AdwBin">
+ <property name="hexpand-set">True</property>
+ <child>
+ <object class="GtkGrid">
+ <property name="column-spacing">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">6</property>
+ <style>
+ <class name="header"/>
+ </style>
+ <child>
+ <object class="GtkLabel">
+ <property name="margin-start">12</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="ellipsize">end</property>
+ <property name="xalign">0</property>
+ <binding name="label">
+ <lookup name="title">AdwDemoAdjustmentRow</lookup>
+ </binding>
+ <style>
+ <class name="title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton">
+ <property name="valign">center</property>
+ <property name="margin-end">12</property>
+ <binding name="adjustment">
+ <lookup name="adjustment">AdwDemoAdjustmentRow</lookup>
+ </binding>
+ <binding name="digits">
+ <lookup name="digits">AdwDemoAdjustmentRow</lookup>
+ </binding>
+ <layout>
+ <property name="column">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScale">
+ <property name="valign">center</property>
+ <binding name="adjustment">
+ <lookup name="adjustment">AdwDemoAdjustmentRow</lookup>
+ </binding>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demo/pages/spring/adw-demo-page-spring.c b/demo/pages/spring/adw-demo-page-spring.c
new file mode 100644
index 0000000..436c101
--- /dev/null
+++ b/demo/pages/spring/adw-demo-page-spring.c
@@ -0,0 +1,193 @@
+#include "adw-demo-page-spring.h"
+
+#include <glib/gi18n.h>
+
+#include "adw-demo-adjustment-row.h"
+#include "adw-demo-spring-basic.h"
+#include "adw-demo-spring-interactive.h"
+#include "adw-demo-spring-preset.h"
+#include "adw-spring-animation-private.h"
+
+struct _AdwDemoPageSpring
+{
+ AdwDemoPage parent_instance;
+
+ AdwDemoSpringBasic *basic_view;
+ AdwDemoSpringInteractive *interactive_view;
+
+ GListStore *presets;
+ AdwComboRow *presets_row;
+
+ gdouble damping;
+ gdouble mass;
+ gdouble stiffness;
+ gdouble precision;
+};
+
+G_DEFINE_TYPE (AdwDemoPageSpring, adw_demo_page_spring, ADW_TYPE_DEMO_PAGE)
+
+enum {
+ PROP_0,
+ PROP_DAMPING,
+ PROP_MASS,
+ PROP_STIFFNESS,
+ PROP_PRECISION,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void
+reset (AdwDemoPageSpring *self)
+{
+ adw_demo_spring_basic_reset (self->basic_view);
+ adw_demo_spring_interactive_reset (self->interactive_view);
+}
+
+static void
+apply_preset (AdwDemoPageSpring *self,
+ AdwDemoSpringPreset *preset)
+{
+ gdouble damping, mass, stiffness, precision;
+
+ g_object_get (preset,
+ "damping", &damping,
+ "mass", &mass,
+ "stiffness", &stiffness,
+ "precision", &precision,
+ NULL);
+
+ g_object_set (self,
+ "damping", damping,
+ "mass", mass,
+ "stiffness", stiffness,
+ "precision", precision,
+ NULL);
+}
+
+static void
+preset_cb (AdwDemoPageSpring *self)
+{
+ apply_preset (self, adw_combo_row_get_selected_item (self->presets_row));
+}
+
+static void
+adw_demo_page_spring_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoPageSpring *self = ADW_DEMO_PAGE_SPRING (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ g_value_set_double (value, self->damping);
+ break;
+ case PROP_MASS:
+ g_value_set_double (value, self->mass);
+ break;
+ case PROP_STIFFNESS:
+ g_value_set_double (value, self->stiffness);
+ break;
+ case PROP_PRECISION:
+ g_value_set_double (value, self->precision);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_page_spring_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoPageSpring *self = ADW_DEMO_PAGE_SPRING (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ self->damping = g_value_get_double (value);
+ break;
+ case PROP_MASS:
+ self->mass = g_value_get_double (value);
+ break;
+ case PROP_STIFFNESS:
+ self->stiffness = g_value_get_double (value);
+ break;
+ case PROP_PRECISION:
+ self->precision = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_page_spring_class_init (AdwDemoPageSpringClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = adw_demo_page_spring_get_property;
+ object_class->set_property = adw_demo_page_spring_set_property;
+
+ props[PROP_DAMPING] =
+ g_param_spec_double ("damping",
+ _("Damping"),
+ _("Damping"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_MASS] =
+ g_param_spec_double ("mass",
+ _("Mass"),
+ _("Mass"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_STIFFNESS] =
+ g_param_spec_double ("stiffness",
+ _("Stiffness"),
+ _("Stiffness"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_PRECISION] =
+ g_param_spec_double ("precision",
+ _("Precision"),
+ _("Precision"),
+ 0, 1, 0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita/Demo/pages/spring/adw-demo-page-spring.ui");
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoPageSpring, basic_view);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoPageSpring, interactive_view);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoPageSpring, presets);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoPageSpring, presets_row);
+ gtk_widget_class_bind_template_callback (widget_class, reset);
+ gtk_widget_class_bind_template_callback (widget_class, preset_cb);
+}
+
+static void
+adw_demo_page_spring_init (AdwDemoPageSpring *self)
+{
+ g_type_ensure (ADW_TYPE_DEMO_ADJUSTMENT_ROW);
+ g_type_ensure (ADW_TYPE_DEMO_SPRING_BASIC);
+ g_type_ensure (ADW_TYPE_DEMO_SPRING_INTERACTIVE);
+ g_type_ensure (ADW_TYPE_DEMO_SPRING_PRESET);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (10, 1, 100, 0.001, _("Default (Core
Animation)")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (26, 1, 170, 0.001, _("Default
(react-spring)")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (14, 1, 120, 0.001, _("Gentle")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (12, 1, 180, 0.001, _("Wobbly")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (20, 1, 210, 0.001, _("Stiff")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (60, 1, 280, 0.001, _("Slow")));
+ g_list_store_append (self->presets, adw_demo_spring_preset_new (120, 1, 280, 0.001, _("Molasses")));
+
+ preset_cb (self);
+}
diff --git a/demo/pages/spring/adw-demo-page-spring.h b/demo/pages/spring/adw-demo-page-spring.h
new file mode 100644
index 0000000..28845d2
--- /dev/null
+++ b/demo/pages/spring/adw-demo-page-spring.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "adw-demo-page.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_PAGE_SPRING (adw_demo_page_spring_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoPageSpring, adw_demo_page_spring, ADW, DEMO_PAGE_SPRING, AdwDemoPage)
+
+G_END_DECLS
diff --git a/demo/pages/spring/adw-demo-page-spring.ui b/demo/pages/spring/adw-demo-page-spring.ui
new file mode 100644
index 0000000..03b77c7
--- /dev/null
+++ b/demo/pages/spring/adw-demo-page-spring.ui
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <template class="AdwDemoPageSpring" parent="AdwDemoPage">
+ <property name="title" translatable="yes">Spring Animations</property>
+ <property name="child">
+ <object class="AdwPreferencesPage">
+ <property name="vexpand">True</property>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkStackSwitcher">
+ <property name="stack">stack</property>
+ <property name="halign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="transition-type">crossfade</property>
+ <style>
+ <class name="content"/>
+ </style>
+ <child>
+ <object class="GtkStackPage">
+ <property name="title" translatable="yes">Basic</property>
+ <property name="child">
+ <object class="AdwDemoSpringBasic" id="basic_view">
+ <property name="damping" bind-source="AdwDemoPageSpring" bind-property="damping"
bind-flags="sync-create"/>
+ <property name="mass" bind-source="AdwDemoPageSpring" bind-property="mass"
bind-flags="sync-create"/>
+ <property name="stiffness" bind-source="AdwDemoPageSpring" bind-property="stiffness"
bind-flags="sync-create"/>
+ <property name="precision" bind-source="AdwDemoPageSpring" bind-property="precision"
bind-flags="sync-create"/>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="title" translatable="yes">Interactive</property>
+ <property name="child">
+ <object class="AdwDemoSpringInteractive" id="interactive_view">
+ <property name="damping" bind-source="AdwDemoPageSpring" bind-property="damping"
bind-flags="sync-create"/>
+ <property name="mass" bind-source="AdwDemoPageSpring" bind-property="mass"
bind-flags="sync-create"/>
+ <property name="stiffness" bind-source="AdwDemoPageSpring" bind-property="stiffness"
bind-flags="sync-create"/>
+ <property name="precision" bind-source="AdwDemoPageSpring" bind-property="precision"
bind-flags="sync-create"/>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="AdwComboRow" id="presets_row">
+ <property name="title" translatable="yes">Preset</property>
+ <signal name="notify::selected-item" handler="preset_cb" swapped="true"/>
+ <property name="model">
+ <object class="GListStore" id="presets"/>
+ </property>
+ <property name="expression">
+ <lookup name="name" type="AdwDemoSpringPreset"/>
+ </property>`
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="AdwPreferencesGroup">
+ <child>
+ <object class="GtkFlowBox">
+ <property name="selection-mode">none</property>
+ <property name="min-children-per-line">1</property>
+ <property name="max-children-per-line">2</property>
+ <property name="row-spacing">1</property>
+ <property name="column-spacing">1</property>
+ <property name="overflow">hidden</property>
+ <style>
+ <class name="content"/>
+ </style>
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="title" translatable="yes">Damping</property>
+ <property name="adjustment">damping</property>
+ <property name="digits">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="title" translatable="yes">Mass</property>
+ <property name="adjustment">mass</property>
+ <property name="digits">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="title" translatable="yes">Stiffness</property>
+ <property name="adjustment">stiffness</property>
+ <property name="digits">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="title" translatable="yes">Precision</property>
+ <property name="adjustment">precision</property>
+ <property name="digits">4</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+ <object class="GtkAdjustment" id="damping">
+ <property name="lower">0</property>
+ <property name="upper">200</property>
+ <property name="value" bind-source="AdwDemoPageSpring" bind-property="damping"
bind-flags="bidirectional"/>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <signal name="value-changed" handler="reset" swapped="true"/>
+ </object>
+ <object class="GtkAdjustment" id="mass">
+ <property name="lower">0</property>
+ <property name="upper">10</property>
+ <property name="value" bind-source="AdwDemoPageSpring" bind-property="mass" bind-flags="bidirectional"/>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <signal name="value-changed" handler="reset" swapped="true"/>
+ </object>
+ <object class="GtkAdjustment" id="stiffness">
+ <property name="lower">0</property>
+ <property name="upper">400</property>
+ <property name="value" bind-source="AdwDemoPageSpring" bind-property="stiffness"
bind-flags="bidirectional"/>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <signal name="value-changed" handler="reset" swapped="true"/>
+ </object>
+ <object class="GtkAdjustment" id="precision">
+ <property name="lower">0.0001</property>
+ <property name="upper">0.01</property>
+ <property name="value" bind-source="AdwDemoPageSpring" bind-property="precision"
bind-flags="bidirectional"/>
+ <property name="step-increment">0.001</property>
+ <property name="page-increment">0.1</property>
+ <signal name="value-changed" handler="reset" swapped="true"/>
+ </object>
+</interface>
diff --git a/demo/pages/spring/adw-demo-spring-basic.c b/demo/pages/spring/adw-demo-spring-basic.c
new file mode 100644
index 0000000..6f983f4
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-basic.c
@@ -0,0 +1,487 @@
+#include "adw-demo-spring-basic.h"
+
+#include <glib/gi18n.h>
+
+#include "adw-demo-transform-layout.h"
+#include "adw-spring-animation-private.h"
+
+#include <math.h>
+
+#define GRAPH_PADDING 24
+
+typedef struct {
+ gint64 time;
+ gdouble value;
+} GraphPoint;
+
+struct _AdwDemoSpringBasic
+{
+ AdwBin parent_instance;
+
+ gdouble damping;
+ gdouble mass;
+ gdouble stiffness;
+ gdouble precision;
+
+ AdwDemoTransformLayout *scale_layout;
+ AdwDemoTransformLayout *htranslate_layout;
+ AdwDemoTransformLayout *rotate_layout;
+ AdwDemoTransformLayout *vtranslate_layout;
+ GtkAdjustment *velocity;
+ GtkToggleButton *info_btn;
+ GtkStack *stack;
+ GtkDrawingArea *darea;
+ GtkWidget *label_box;
+ GtkLabel *duration_label;
+ GtkLabel *min_label;
+ GtkLabel *max_label;
+
+ AdwSpringAnimation *animation;
+ gboolean invert;
+
+ AdwSpringAnimation *graph_animation;
+ GArray *points;
+ gint64 start_time;
+ gint64 duration;
+ gdouble min;
+ gdouble max;
+};
+
+G_DEFINE_TYPE (AdwDemoSpringBasic, adw_demo_spring_basic, ADW_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_DAMPING,
+ PROP_MASS,
+ PROP_STIFFNESS,
+ PROP_PRECISION,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static inline gfloat
+lerp (gfloat a, gfloat b, gfloat t)
+{
+ return a * (1.0 - t) + b * t;
+}
+
+static void
+set_value (AdwDemoSpringBasic *self,
+ gdouble value)
+{
+ gfloat x = lerp (-30, 30, value);
+ gfloat y = lerp (30, -30, value);
+ gfloat s = MAX (0, lerp (3, 1, value));
+ gfloat r = lerp (0, 90, value);
+
+ adw_demo_transform_layout_take_transform (self->htranslate_layout,
+ gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, 0)));
+ adw_demo_transform_layout_take_transform (self->vtranslate_layout,
+ gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (0, y)));
+ adw_demo_transform_layout_take_transform (self->scale_layout,
+ gsk_transform_scale (NULL, s, s));
+ adw_demo_transform_layout_take_transform (self->rotate_layout,
+ gsk_transform_rotate (NULL, r));
+}
+
+static void
+set_min (AdwDemoSpringBasic *self,
+ gdouble min)
+{
+ g_autofree gchar *label = g_strdup_printf (_("Min: %.2lf"), min);
+
+ self->min = min;
+
+ gtk_label_set_label (self->min_label, label);
+}
+
+static void
+set_max (AdwDemoSpringBasic *self,
+ gdouble max)
+{
+ g_autofree gchar *label = g_strdup_printf (_("Max: %.2lf"), max);
+
+ self->max = max;
+
+ gtk_label_set_label (self->max_label, label);
+}
+
+static void
+add_plot_point (AdwDemoSpringBasic *self,
+ gint64 time,
+ gdouble value)
+{
+ GraphPoint point = { time - self->start_time, value };
+
+ g_array_append_val (self->points, point);
+
+ gtk_widget_queue_draw (GTK_WIDGET (self->darea));
+
+ if (value < self->min)
+ set_min (self, value);
+
+ if (value > self->max)
+ set_max (self, value);
+}
+
+static void
+active_changed_cb (AdwDemoSpringBasic *self)
+{
+ const gchar *name = gtk_toggle_button_get_active (self->info_btn) ? "info" : "basic";
+
+ gtk_stack_set_visible_child_name (self->stack, name);
+}
+
+static void
+graph_value_cb (gdouble value,
+ AdwDemoSpringBasic *self)
+{
+ gint64 frame_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (self)));
+
+ add_plot_point (self, frame_time, value);
+}
+
+static void
+graph_done_cb (AdwDemoSpringBasic *self)
+{
+ g_clear_pointer (&self->graph_animation, adw_spring_animation_unref);
+}
+
+static void
+run_graph (AdwDemoSpringBasic *self)
+{
+ gdouble velocity = gtk_adjustment_get_value (self->velocity);
+ gdouble duration;
+
+ if (self->graph_animation)
+ adw_spring_animation_stop (self->graph_animation);
+
+ g_clear_pointer (&self->points, g_array_unref);
+ self->points = g_array_new (FALSE, FALSE, sizeof (GraphPoint));
+
+ self->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (GTK_WIDGET (self)));
+
+ set_min (self, 0);
+ set_max (self, 1);
+ add_plot_point (self, self->start_time, 0);
+
+ self->graph_animation = adw_spring_animation_new (GTK_WIDGET (self),
+ 0,
+ 1,
+ velocity,
+ self->damping,
+ self->mass,
+ self->stiffness,
+ self->precision,
+ (AdwAnimationValueCallback) graph_value_cb,
+ (AdwAnimationDoneCallback) graph_done_cb,
+ self);
+
+ duration = adw_spring_animation_get_estimated_duration (self->graph_animation);
+
+ if (isinf (duration)) {
+ self->duration = -1;
+
+ gtk_label_set_label (self->duration_label, _("Duration: ∞"));
+ } else {
+ g_autofree gchar *label = g_strdup_printf (_("Duration: %.0lfms"), duration * 1000);
+
+ self->duration = (gint64) (duration * 1000000);
+ gtk_label_set_label (self->duration_label, label);
+ }
+
+ adw_spring_animation_start (self->graph_animation);
+
+ gtk_widget_set_opacity (self->label_box, 1);
+}
+
+static void
+basic_value_cb (gdouble value,
+ AdwDemoSpringBasic *self)
+{
+ set_value (self, value);
+}
+
+static void
+basic_done_cb (AdwDemoSpringBasic *self)
+{
+ g_clear_pointer (&self->animation, adw_spring_animation_unref);
+}
+
+static void
+run_basic (AdwDemoSpringBasic *self)
+{
+ gdouble value = self->invert ? 1 : 0;
+ gdouble velocity = gtk_adjustment_get_value (self->velocity);
+
+ if (self->animation) {
+ value = adw_spring_animation_get_value (self->animation);
+ adw_spring_animation_stop (self->animation);
+ }
+
+ self->animation = adw_spring_animation_new (GTK_WIDGET (self),
+ value,
+ self->invert ? 0 : 1,
+ velocity,
+ self->damping,
+ self->mass,
+ self->stiffness,
+ self->precision,
+ (AdwAnimationValueCallback) basic_value_cb,
+ (AdwAnimationDoneCallback) basic_done_cb,
+ self);
+
+ adw_spring_animation_start (self->animation);
+
+ self->invert = !self->invert;
+}
+
+static void
+run_cb (AdwDemoSpringBasic *self)
+{
+ if (gtk_toggle_button_get_active (self->info_btn))
+ run_graph (self);
+ else
+ run_basic (self);
+}
+
+static inline void
+set_color_from_css (AdwDemoSpringBasic *self,
+ cairo_t *cr,
+ const gchar *name,
+ gdouble alpha_multiplier)
+{
+ GdkRGBA rgba = { 0, 0, 0, 1 };
+
+ gtk_style_context_lookup_color (gtk_widget_get_style_context (GTK_WIDGET (self->darea)), name, &rgba);
+
+ cairo_set_source_rgba (cr, rgba.red, rgba.green, rgba.blue, rgba.alpha * alpha_multiplier);
+}
+
+static inline double
+transform_y (AdwDemoSpringBasic *self,
+ gdouble height,
+ gdouble y)
+{
+ gdouble bottom_padding = gtk_widget_get_allocated_height (self->label_box);
+
+ return height - (bottom_padding + (y - self->min) * (height - GRAPH_PADDING - bottom_padding) / (self->max
- self->min));
+}
+
+static void
+draw_cb (GtkDrawingArea *darea,
+ cairo_t *cr,
+ gint width,
+ gint height,
+ AdwDemoSpringBasic *self)
+{
+ gdouble x = 0;
+ gdouble y1 = transform_y (self, height, 0);
+ gdouble y2 = transform_y (self, height, 1);
+ gdouble dashes[2] = { 4, 2 };
+ gint64 d;
+ guint i;
+ cairo_path_t *path;
+
+ cairo_save (cr);
+
+ cairo_set_line_width (cr, 1);
+ cairo_set_dash (cr, dashes, 2, 0);
+ cairo_translate(cr, 0, 0.5);
+
+ set_color_from_css (self, cr, "borders", 1);
+
+ cairo_move_to (cr, 0, y1);
+ cairo_line_to (cr, width, y1);
+
+ cairo_move_to (cr, 0, y2);
+ cairo_line_to (cr, width, y2);
+
+ cairo_stroke (cr);
+
+ cairo_restore (cr);
+
+ if (!self->points)
+ return;
+
+ cairo_new_path (cr);
+
+ d = self->duration < 0 ? 10000000 : self->duration;
+
+ for (i = 0; i < self->points->len; i++) {
+ GraphPoint *point = &g_array_index (self->points, GraphPoint, i);
+
+ x = (double) point->time * width / d;
+
+ cairo_line_to (cr, x, transform_y (self, height, point->value));
+ }
+
+ path = cairo_copy_path (cr);
+
+ set_color_from_css (self, cr, "yellow_1", 0.5);
+
+ cairo_line_to (cr, x, height);
+ cairo_line_to (cr, -1, height);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+
+ cairo_append_path (cr, path);
+
+ cairo_set_line_width (cr, 2);
+ set_color_from_css (self, cr, "yellow_5", 1);
+ cairo_stroke (cr);
+
+ cairo_path_destroy (path);
+}
+
+static void
+adw_demo_spring_basic_finalize (GObject *object)
+{
+ AdwDemoSpringBasic *self = ADW_DEMO_SPRING_BASIC (object);
+
+ g_clear_pointer (&self->points, g_array_unref);
+
+ G_OBJECT_CLASS (adw_demo_spring_basic_parent_class)->finalize (object);
+}
+
+static void
+adw_demo_spring_basic_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringBasic *self = ADW_DEMO_SPRING_BASIC (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ g_value_set_double (value, self->damping);
+ break;
+ case PROP_MASS:
+ g_value_set_double (value, self->mass);
+ break;
+ case PROP_STIFFNESS:
+ g_value_set_double (value, self->stiffness);
+ break;
+ case PROP_PRECISION:
+ g_value_set_double (value, self->precision);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_basic_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringBasic *self = ADW_DEMO_SPRING_BASIC (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ self->damping = g_value_get_double (value);
+ break;
+ case PROP_MASS:
+ self->mass = g_value_get_double (value);
+ break;
+ case PROP_STIFFNESS:
+ self->stiffness = g_value_get_double (value);
+ break;
+ case PROP_PRECISION:
+ self->precision = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_basic_class_init (AdwDemoSpringBasicClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = adw_demo_spring_basic_finalize;
+ object_class->get_property = adw_demo_spring_basic_get_property;
+ object_class->set_property = adw_demo_spring_basic_set_property;
+
+ props[PROP_DAMPING] =
+ g_param_spec_double ("damping",
+ _("Damping"),
+ _("Damping"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_MASS] =
+ g_param_spec_double ("mass",
+ _("Mass"),
+ _("Mass"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_STIFFNESS] =
+ g_param_spec_double ("stiffness",
+ _("Stiffness"),
+ _("Stiffness"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_PRECISION] =
+ g_param_spec_double ("precision",
+ _("Precision"),
+ _("Precision"),
+ 0, 1, 0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita/Demo/pages/spring/adw-demo-spring-basic.ui");
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, scale_layout);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, htranslate_layout);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, rotate_layout);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, vtranslate_layout);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, velocity);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, info_btn);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, stack);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, darea);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, label_box);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, duration_label);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, min_label);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringBasic, max_label);
+ gtk_widget_class_bind_template_callback (widget_class, active_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, run_cb);
+ gtk_widget_class_bind_template_callback (widget_class, adw_demo_spring_basic_reset);
+}
+
+static void
+adw_demo_spring_basic_init (AdwDemoSpringBasic *self)
+{
+ g_type_ensure (ADW_TYPE_DEMO_TRANSFORM_LAYOUT);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_drawing_area_set_draw_func (self->darea, (GtkDrawingAreaDrawFunc) draw_cb, self, NULL);
+
+ set_value (self, 0);
+ adw_demo_spring_basic_reset (self);
+}
+
+void
+adw_demo_spring_basic_reset (AdwDemoSpringBasic *self)
+{
+ if (self->animation)
+ adw_spring_animation_stop (self->animation);
+
+ if (self->graph_animation)
+ adw_spring_animation_stop (self->graph_animation);
+
+ g_clear_pointer (&self->points, g_array_unref);
+ self->points = NULL;
+ self->min = 0;
+ self->max = 1;
+
+ gtk_widget_set_opacity (self->label_box, 0);
+ gtk_widget_queue_draw (GTK_WIDGET (self->darea));
+}
diff --git a/demo/pages/spring/adw-demo-spring-basic.h b/demo/pages/spring/adw-demo-spring-basic.h
new file mode 100644
index 0000000..eb9058f
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-basic.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_SPRING_BASIC (adw_demo_spring_basic_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoSpringBasic, adw_demo_spring_basic, ADW, DEMO_SPRING_BASIC, AdwBin)
+
+void adw_demo_spring_basic_reset (AdwDemoSpringBasic *self);
+
+G_END_DECLS
diff --git a/demo/pages/spring/adw-demo-spring-basic.ui b/demo/pages/spring/adw-demo-spring-basic.ui
new file mode 100644
index 0000000..da1c1de
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-basic.ui
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <template class="AdwDemoSpringBasic" parent="AdwBin">
+ <property name="child">
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="content"/>
+ </style>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="transition-type">crossfade</property>
+ <property name="overflow">hidden</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">basic</property>
+ <property name="child">
+ <object class="GtkFlowBox">
+ <property name="homogeneous">True</property>
+ <property name="min-children-per-line">1</property>
+ <property name="max-children-per-line">2</property>
+ <property name="selection-mode">none</property>
+ <style>
+ <class name="demo-grid"/>
+ </style>
+ <child>
+ <object class="GtkBox">
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="AdwBin">
+ <property name="height-request">160</property>
+ <property name="width-request">140</property>
+ <property name="layout-manager">
+ <object class="AdwDemoTransformLayout" id="scale_layout"/>
+ </property>
+ <property name="child">
+ <object class="AdwBin">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="scale"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwBin">
+ <property name="height-request">160</property>
+ <property name="width-request">140</property>
+ <property name="layout-manager">
+ <object class="AdwDemoTransformLayout" id="htranslate_layout"/>
+ </property>
+ <property name="child">
+ <object class="AdwBin">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="htranslate"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="AdwBin">
+ <property name="height-request">160</property>
+ <property name="width-request">140</property>
+ <property name="layout-manager">
+ <object class="AdwDemoTransformLayout" id="rotate_layout"/>
+ </property>
+ <property name="child">
+ <object class="AdwBin">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="rotate"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwBin">
+ <property name="height-request">160</property>
+ <property name="width-request">140</property>
+ <property name="layout-manager">
+ <object class="AdwDemoTransformLayout" id="vtranslate_layout"/>
+ </property>
+ <property name="child">
+ <object class="AdwBin">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="vtranslate"/>
+ </style>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">info</property>
+ <property name="child">
+ <object class="GtkOverlay">
+ <child>
+ <object class="GtkDrawingArea" id="darea"/>
+ </child>
+ <child type="overlay">
+ <object class="GtkBox" id="label_box">
+ <property name="valign">end</property>
+ <property name="spacing">12</property>
+ <property name="opacity">0</property>
+ <child>
+ <object class="GtkLabel" id="duration_label">
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-start">6</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="min_label">
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="max_label">
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="margin-end">6</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator"/>
+ </child>
+ <child>
+ <object class="AdwSqueezer">
+ <property name="homogeneous">False</property>
+ <property name="interpolate-size">True</property>
+ <property name="transition-type">crossfade</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <child>
+ <object class="GtkBox">
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="width-request">250</property>
+ <property name="title" translatable="yes">Velocity</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">velocity</property>
+ <property name="digits">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="info_btn">
+ <property name="valign">center</property>
+ <property name="icon-name">spring-graph-symbolic</property>
+ <property name="margin-end">12</property>
+ <signal name="notify::active" handler="active_changed_cb" swapped="true"/>
+ <style>
+ <class name="circular-large"/>
+ <class name="list-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="icon-name">spring-run-symbolic</property>
+ <property name="valign">center</property>
+ <property name="margin-end">18</property>
+ <signal name="clicked" handler="run_cb" swapped="true"/>
+ <style>
+ <class name="circular-large"/>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="AdwDemoAdjustmentRow">
+ <property name="title" translatable="yes">Velocity</property>
+ <property name="hexpand">True</property>
+ <property name="adjustment">velocity</property>
+ <property name="digits">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator"/>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="spacing">12</property>
+ <property name="halign">center</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <child>
+ <object class="GtkToggleButton">
+ <property name="active" bind-source="info_btn" bind-property="active"
bind-flags="bidirectional"/>
+ <property name="valign">center</property>
+ <property name="icon-name">spring-graph-symbolic</property>
+ <style>
+ <class name="circular-large"/>
+ <class name="list-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="icon-name">spring-run-symbolic</property>
+ <property name="valign">center</property>
+ <signal name="clicked" handler="run_cb" swapped="true"/>
+ <style>
+ <class name="circular-large"/>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </property>
+ </template>
+ <object class="GtkAdjustment" id="velocity">
+ <property name="lower">-50</property>
+ <property name="upper">50</property>
+ <property name="value">0</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <signal name="value-changed" handler="adw_demo_spring_basic_reset" swapped="true"/>
+ </object>
+</interface>
diff --git a/demo/pages/spring/adw-demo-spring-interactive.c b/demo/pages/spring/adw-demo-spring-interactive.c
new file mode 100644
index 0000000..cc36943
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-interactive.c
@@ -0,0 +1,292 @@
+#include "adw-demo-spring-interactive.h"
+
+#include <glib/gi18n.h>
+
+#include "adw-demo-transform-layout.h"
+#include "adw-spring-animation-private.h"
+
+struct _AdwDemoSpringInteractive
+{
+ AdwBin parent_instance;
+
+ gdouble damping;
+ gdouble mass;
+ gdouble stiffness;
+ gdouble precision;
+
+ AdwDemoTransformLayout *layout;
+ GtkWidget *handle;
+ GtkGesture *drag_gesture;
+ GtkGesture *swipe_gesture;
+ GdkCursor *grab_cursor;
+ GdkCursor *grabbing_cursor;
+
+ AdwSpringAnimation *animation_x;
+ AdwSpringAnimation *animation_y;
+
+ gdouble start_x;
+ gdouble start_y;
+ gdouble last_x;
+ gdouble last_y;
+};
+
+G_DEFINE_TYPE (AdwDemoSpringInteractive, adw_demo_spring_interactive, ADW_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_DAMPING,
+ PROP_MASS,
+ PROP_STIFFNESS,
+ PROP_PRECISION,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void
+set_translation (AdwDemoSpringInteractive *self,
+ gdouble x,
+ gdouble y)
+{
+ self->last_x = x;
+ self->last_y = y;
+
+ adw_demo_transform_layout_take_transform (self->layout,
+ gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (x, y)));
+}
+
+static void
+x_value_cb (gdouble value,
+ AdwDemoSpringInteractive *self)
+{
+ set_translation (self, value, self->last_y);
+}
+
+static void
+x_done_cb (AdwDemoSpringInteractive *self)
+{
+ g_clear_pointer (&self->animation_x, adw_spring_animation_unref);
+}
+
+static void
+y_value_cb (gdouble value,
+ AdwDemoSpringInteractive *self)
+{
+ set_translation (self, self->last_x, value);
+}
+
+static void
+y_done_cb (AdwDemoSpringInteractive *self)
+{
+ g_clear_pointer (&self->animation_y, adw_spring_animation_unref);
+}
+
+static void
+animate (AdwDemoSpringInteractive *self,
+ gdouble velocity_x,
+ gdouble velocity_y)
+{
+ gtk_widget_set_cursor (self->handle,
+ self->grab_cursor);
+
+ self->animation_x = adw_spring_animation_new (self->handle,
+ self->last_x,
+ 0,
+ velocity_x,
+ self->damping,
+ self->mass,
+ self->stiffness,
+ self->precision,
+ (AdwAnimationValueCallback) x_value_cb,
+ (AdwAnimationDoneCallback) x_done_cb,
+ self);
+
+ self->animation_y = adw_spring_animation_new (self->handle,
+ self->last_y,
+ 0,
+ velocity_y,
+ self->damping,
+ self->mass,
+ self->stiffness,
+ self->precision,
+ (AdwAnimationValueCallback) y_value_cb,
+ (AdwAnimationDoneCallback) y_done_cb,
+ self);
+
+ adw_spring_animation_start (self->animation_x);
+ adw_spring_animation_start (self->animation_y);
+}
+
+static void
+drag_begin_cb (AdwDemoSpringInteractive *self,
+ gdouble start_x,
+ gdouble start_y)
+{
+ if (gtk_widget_pick (GTK_WIDGET (self), start_x, start_y, GTK_PICK_DEFAULT) != self->handle) {
+ gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
+
+ return;
+ }
+
+ gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
+
+ self->start_x = self->last_x;
+ self->start_y = self->last_y;
+
+ if (self->animation_x)
+ adw_spring_animation_stop (self->animation_x);
+
+ if (self->animation_y)
+ adw_spring_animation_stop (self->animation_y);
+
+ set_translation (self, self->start_x, self->start_y);
+
+ gtk_widget_set_cursor (self->handle,
+ self->grabbing_cursor);
+}
+
+static void
+drag_update_cb (AdwDemoSpringInteractive *self,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ set_translation (self, offset_x + self->start_x, offset_y + self->start_y);
+}
+
+static void
+drag_cancel_cb (AdwDemoSpringInteractive *self)
+{
+ if (self->animation_x)
+ adw_spring_animation_stop (self->animation_x);
+
+ if (self->animation_y)
+ adw_spring_animation_stop (self->animation_y);
+
+ animate (self, 0, 0);
+}
+
+static void
+adw_demo_spring_interactive_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringInteractive *self = ADW_DEMO_SPRING_INTERACTIVE (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ g_value_set_double (value, self->damping);
+ break;
+ case PROP_MASS:
+ g_value_set_double (value, self->mass);
+ break;
+ case PROP_STIFFNESS:
+ g_value_set_double (value, self->stiffness);
+ break;
+ case PROP_PRECISION:
+ g_value_set_double (value, self->precision);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_interactive_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringInteractive *self = ADW_DEMO_SPRING_INTERACTIVE (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ self->damping = g_value_get_double (value);
+ break;
+ case PROP_MASS:
+ self->mass = g_value_get_double (value);
+ break;
+ case PROP_STIFFNESS:
+ self->stiffness = g_value_get_double (value);
+ break;
+ case PROP_PRECISION:
+ self->precision = g_value_get_double (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_interactive_class_init (AdwDemoSpringInteractiveClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = adw_demo_spring_interactive_get_property;
+ object_class->set_property = adw_demo_spring_interactive_set_property;
+
+ props[PROP_DAMPING] =
+ g_param_spec_double ("damping",
+ _("Damping"),
+ _("Damping"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_MASS] =
+ g_param_spec_double ("mass",
+ _("Mass"),
+ _("Mass"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_STIFFNESS] =
+ g_param_spec_double ("stiffness",
+ _("Stiffness"),
+ _("Stiffness"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_PRECISION] =
+ g_param_spec_double ("precision",
+ _("Precision"),
+ _("Precision"),
+ 0, 1, 0,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita/Demo/pages/spring/adw-demo-spring-interactive.ui");
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, layout);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, handle);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, drag_gesture);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, swipe_gesture);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, grab_cursor);
+ gtk_widget_class_bind_template_child (widget_class, AdwDemoSpringInteractive, grabbing_cursor);
+ gtk_widget_class_bind_template_callback (widget_class, drag_begin_cb);
+ gtk_widget_class_bind_template_callback (widget_class, drag_update_cb);
+ gtk_widget_class_bind_template_callback (widget_class, drag_cancel_cb);
+ gtk_widget_class_bind_template_callback (widget_class, animate);
+}
+
+static void
+adw_demo_spring_interactive_init (AdwDemoSpringInteractive *self)
+{
+ g_type_ensure (ADW_TYPE_DEMO_TRANSFORM_LAYOUT);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_gesture_group (self->drag_gesture, self->swipe_gesture);
+}
+
+void
+adw_demo_spring_interactive_reset (AdwDemoSpringInteractive *self)
+{
+ if (self->animation_x)
+ adw_spring_animation_stop (self->animation_x);
+
+ if (self->animation_y)
+ adw_spring_animation_stop (self->animation_y);
+
+ set_translation (self, 0, 0);
+}
diff --git a/demo/pages/spring/adw-demo-spring-interactive.h b/demo/pages/spring/adw-demo-spring-interactive.h
new file mode 100644
index 0000000..d0461a3
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-interactive.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <adwaita.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_SPRING_INTERACTIVE (adw_demo_spring_interactive_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoSpringInteractive, adw_demo_spring_interactive, ADW, DEMO_SPRING_INTERACTIVE,
AdwBin)
+
+void adw_demo_spring_interactive_reset (AdwDemoSpringInteractive *self);
+
+G_END_DECLS
diff --git a/demo/pages/spring/adw-demo-spring-interactive.ui
b/demo/pages/spring/adw-demo-spring-interactive.ui
new file mode 100644
index 0000000..a38a048
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-interactive.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <template class="AdwDemoSpringInteractive" parent="AdwBin">
+ <property name="layout-manager">
+ <object class="AdwDemoTransformLayout" id="layout"/>
+ </property>
+ <property name="child">
+ <object class="GtkImage" id="handle">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="icon-name">spring-interactive-symbolic</property>
+ <property name="icon-size">large</property>
+ <property name="cursor">grab_cursor</property>
+ <style>
+ <class name="interactive"/>
+ </style>
+ </object>
+ </property>
+ <child>
+ <object class="GtkGestureDrag" id="drag_gesture">
+ <property name="propagation-phase">capture</property>
+ <signal name="drag-begin" handler="drag_begin_cb" swapped="true"/>
+ <signal name="drag-update" handler="drag_update_cb" swapped="true"/>
+ <signal name="cancel" handler="drag_cancel_cb" swapped="true"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGestureSwipe" id="swipe_gesture">
+ <property name="propagation-phase">capture</property>
+ <signal name="swipe" handler="animate" swapped="true"/>
+ </object>
+ </child>
+ </template>
+ <object class="GdkCursor" id="grab_cursor">
+ <property name="name">grab</property>
+ </object>
+ <object class="GdkCursor" id="grabbing_cursor">
+ <property name="name">grabbing</property>
+ </object>
+</interface>
diff --git a/demo/pages/spring/adw-demo-spring-preset.c b/demo/pages/spring/adw-demo-spring-preset.c
new file mode 100644
index 0000000..9418396
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-preset.c
@@ -0,0 +1,174 @@
+#include "adw-demo-spring-preset.h"
+
+#include <glib/gi18n.h>
+
+struct _AdwDemoSpringPreset
+{
+ GObject parent_instance;
+
+ gchar *name;
+ gdouble damping;
+ gdouble mass;
+ gdouble stiffness;
+ gdouble precision;
+};
+
+G_DEFINE_TYPE (AdwDemoSpringPreset, adw_demo_spring_preset, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DAMPING,
+ PROP_MASS,
+ PROP_STIFFNESS,
+ PROP_PRECISION,
+ PROP_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static inline void
+set_string (gchar **dest,
+ const gchar *source)
+{
+ if (*dest)
+ g_free (*dest);
+
+ *dest = g_strdup (source);
+}
+
+static void
+adw_demo_spring_preset_finalize (GObject *object)
+{
+ AdwDemoSpringPreset *self = ADW_DEMO_SPRING_PRESET (object);
+
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (adw_demo_spring_preset_parent_class)->finalize (object);
+}
+
+static void
+adw_demo_spring_preset_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringPreset *self = ADW_DEMO_SPRING_PRESET (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ g_value_set_double (value, self->damping);
+ break;
+ case PROP_MASS:
+ g_value_set_double (value, self->mass);
+ break;
+ case PROP_STIFFNESS:
+ g_value_set_double (value, self->stiffness);
+ break;
+ case PROP_PRECISION:
+ g_value_set_double (value, self->precision);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_preset_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoSpringPreset *self = ADW_DEMO_SPRING_PRESET (object);
+
+ switch (prop_id) {
+ case PROP_DAMPING:
+ self->damping = g_value_get_double (value);
+ break;
+ case PROP_MASS:
+ self->mass = g_value_get_double (value);
+ break;
+ case PROP_STIFFNESS:
+ self->stiffness = g_value_get_double (value);
+ break;
+ case PROP_PRECISION:
+ self->precision = g_value_get_double (value);
+ break;
+ case PROP_NAME:
+ set_string (&self->name, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_spring_preset_class_init (AdwDemoSpringPresetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = adw_demo_spring_preset_finalize;
+ object_class->get_property = adw_demo_spring_preset_get_property;
+ object_class->set_property = adw_demo_spring_preset_set_property;
+
+ props[PROP_DAMPING] =
+ g_param_spec_double ("damping",
+ _("Damping"),
+ _("Damping"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_MASS] =
+ g_param_spec_double ("mass",
+ _("Mass"),
+ _("Mass"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_STIFFNESS] =
+ g_param_spec_double ("stiffness",
+ _("Stiffness"),
+ _("Stiffness"),
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_PRECISION] =
+ g_param_spec_double ("precision",
+ _("Precision"),
+ _("Precision"),
+ 0, 1, 0,
+ G_PARAM_READWRITE);
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name",
+ _("Name"),
+ _("Name"),
+ NULL,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+static void
+adw_demo_spring_preset_init (AdwDemoSpringPreset *self)
+{
+}
+
+AdwDemoSpringPreset *
+adw_demo_spring_preset_new (gdouble damping,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble precision,
+ const gchar *name)
+{
+ return g_object_new (ADW_TYPE_DEMO_SPRING_PRESET,
+ "damping", damping,
+ "mass", mass,
+ "stiffness", stiffness,
+ "precision", precision,
+ "name", name,
+ NULL);
+}
diff --git a/demo/pages/spring/adw-demo-spring-preset.h b/demo/pages/spring/adw-demo-spring-preset.h
new file mode 100644
index 0000000..af8190a
--- /dev/null
+++ b/demo/pages/spring/adw-demo-spring-preset.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_SPRING_PRESET (adw_demo_spring_preset_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoSpringPreset, adw_demo_spring_preset, ADW, DEMO_SPRING_PRESET, GObject)
+
+AdwDemoSpringPreset *adw_demo_spring_preset_new (gdouble damping,
+ gdouble mass,
+ gdouble stiffness,
+ gdouble precision,
+ const gchar *name);
+
+G_END_DECLS
diff --git a/demo/pages/spring/adw-demo-transform-layout.c b/demo/pages/spring/adw-demo-transform-layout.c
new file mode 100644
index 0000000..e79dea9
--- /dev/null
+++ b/demo/pages/spring/adw-demo-transform-layout.c
@@ -0,0 +1,190 @@
+#include "adw-demo-transform-layout.h"
+
+#include <glib/gi18n.h>
+
+struct _AdwDemoTransformLayout
+{
+ GtkLayoutManager parent_instance;
+
+ GskTransform *transform;
+};
+
+G_DEFINE_TYPE (AdwDemoTransformLayout, adw_demo_transform_layout, GTK_TYPE_LAYOUT_MANAGER)
+
+enum {
+ PROP_0,
+ PROP_TRANSFORM,
+ LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void
+adw_demo_transform_layout_measure (GtkLayoutManager *layout,
+ GtkWidget *widget,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum,
+ gint *natural,
+ gint *minimum_baseline,
+ gint *natural_baseline)
+{
+ GtkWidget *child;
+
+ for (child = gtk_widget_get_first_child (widget);
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child)) {
+ gint child_min = 0;
+ gint child_nat = 0;
+ gint child_min_baseline = -1;
+ gint child_nat_baseline = -1;
+
+ if (!gtk_widget_should_layout (child))
+ continue;
+
+ gtk_widget_measure (child, orientation, for_size,
+ &child_min, &child_nat,
+ &child_min_baseline, &child_nat_baseline);
+
+ *minimum = MAX (*minimum, child_min);
+ *natural = MAX (*natural, child_nat);
+
+ if (child_min_baseline > -1)
+ *minimum_baseline = MAX (*minimum_baseline, child_min_baseline);
+ if (child_nat_baseline > -1)
+ *natural_baseline = MAX (*natural_baseline, child_nat_baseline);
+ }
+}
+
+static void
+adw_demo_transform_layout_allocate (GtkLayoutManager *layout,
+ GtkWidget *widget,
+ gint width,
+ gint height,
+ gint baseline)
+{
+ AdwDemoTransformLayout *self = ADW_DEMO_TRANSFORM_LAYOUT (layout);
+ GtkWidget *child;
+
+ for (child = gtk_widget_get_first_child (widget);
+ child != NULL;
+ child = gtk_widget_get_next_sibling (child)) {
+ GskTransform *transform;
+
+ if (!child || !gtk_widget_should_layout (child))
+ continue;
+
+ transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (width / 2.0f, height / 2.0f));
+ transform = gsk_transform_transform (transform, gsk_transform_ref (self->transform));
+ transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width / 2.0f, -height / 2.0f));
+
+ gtk_widget_allocate (child, width, height, baseline, transform);
+ }
+}
+
+static void
+adw_demo_transform_layout_dispose (GObject *object)
+{
+ AdwDemoTransformLayout *self = ADW_DEMO_TRANSFORM_LAYOUT (object);
+
+ g_clear_pointer (&self->transform, gsk_transform_unref);
+
+ G_OBJECT_CLASS (adw_demo_transform_layout_parent_class)->dispose (object);
+}
+
+static void
+adw_demo_transform_layout_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoTransformLayout *self = ADW_DEMO_TRANSFORM_LAYOUT (object);
+
+ switch (prop_id) {
+ case PROP_TRANSFORM:
+ g_value_set_boxed (value, adw_demo_transform_layout_get_transform (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_transform_layout_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwDemoTransformLayout *self = ADW_DEMO_TRANSFORM_LAYOUT (object);
+
+ switch (prop_id) {
+ case PROP_TRANSFORM:
+ adw_demo_transform_layout_set_transform (self, g_value_get_boxed (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_demo_transform_layout_class_init (AdwDemoTransformLayoutClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkLayoutManagerClass *layout_class = GTK_LAYOUT_MANAGER_CLASS (klass);
+
+ object_class->dispose = adw_demo_transform_layout_dispose;
+ object_class->get_property = adw_demo_transform_layout_get_property;
+ object_class->set_property = adw_demo_transform_layout_set_property;
+
+ layout_class->measure = adw_demo_transform_layout_measure;
+ layout_class->allocate = adw_demo_transform_layout_allocate;
+
+ props[PROP_TRANSFORM] =
+ g_param_spec_boxed ("transform",
+ _("Transform"),
+ _("Transform"),
+ GSK_TYPE_TRANSFORM,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+static void
+adw_demo_transform_layout_init (AdwDemoTransformLayout *self)
+{
+}
+
+GskTransform *
+adw_demo_transform_layout_get_transform (AdwDemoTransformLayout *self)
+{
+ g_return_val_if_fail (ADW_IS_DEMO_TRANSFORM_LAYOUT (self), NULL);
+
+ return self->transform;
+}
+
+void
+adw_demo_transform_layout_set_transform (AdwDemoTransformLayout *self,
+ GskTransform *transform)
+{
+ g_return_if_fail (ADW_IS_DEMO_TRANSFORM_LAYOUT (self));
+
+ if (transform == self->transform)
+ return;
+
+ gsk_transform_unref (self->transform);
+ self->transform = gsk_transform_ref (transform);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSFORM]);
+}
+
+void
+adw_demo_transform_layout_take_transform (AdwDemoTransformLayout *self,
+ GskTransform *transform)
+{
+ g_return_if_fail (ADW_IS_DEMO_TRANSFORM_LAYOUT (self));
+
+ adw_demo_transform_layout_set_transform (self, transform);
+ gsk_transform_unref (transform);
+}
diff --git a/demo/pages/spring/adw-demo-transform-layout.h b/demo/pages/spring/adw-demo-transform-layout.h
new file mode 100644
index 0000000..dc263a7
--- /dev/null
+++ b/demo/pages/spring/adw-demo-transform-layout.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_DEMO_TRANSFORM_LAYOUT (adw_demo_transform_layout_get_type())
+
+G_DECLARE_FINAL_TYPE (AdwDemoTransformLayout, adw_demo_transform_layout, ADW, DEMO_TRANSFORM_LAYOUT,
GtkLayoutManager)
+
+GskTransform *adw_demo_transform_layout_get_transform (AdwDemoTransformLayout *self);
+void adw_demo_transform_layout_set_transform (AdwDemoTransformLayout *self,
+ GskTransform *transform);
+
+void adw_demo_transform_layout_take_transform (AdwDemoTransformLayout *self,
+ GskTransform *transform);
+
+G_END_DECLS
diff --git a/demo/style.css b/demo/style.css
index 7e8b2b9..5091b0a 100644
--- a/demo/style.css
+++ b/demo/style.css
@@ -1,3 +1,84 @@
+@define-color red_1 #f66151;
+@define-color red_3 #e01b24;
+@define-color green_2 #57e389;
+@define-color green_5 #26a269;
+@define-color blue_2 #62a0ea;
+@define-color blue_4 #1c71d8;
+@define-color yellow_1 #f9f06b;
+@define-color yellow_2 #f8e45c;
+@define-color yellow_5 #e5a50a;
+
+/* Welp */
+window.maximized {
+ box-shadow: none;
+}
+
+.htranslate,
+.vtranslate,
+.scale {
+ min-width: 30px;
+ min-height: 30px;
+ border-radius: 24px;
+ background: #33d17a;
+}
+
+.scale {
+ background: linear-gradient(to bottom, @blue_2, @blue_4);
+}
+
+.htranslate {
+ background: linear-gradient(to bottom, @green_2, @green_5);
+}
+
+.vtranslate {
+ background: linear-gradient(to bottom, @yellow_2, @yellow_5);
+}
+
+.rotate {
+ min-width: 54px;
+ min-height: 90px;
+ border-radius: 8px;
+ background: linear-gradient(to bottom, @red_1, @red_3);
+}
+
+.interactive {
+ min-width: 96px;
+ min-height: 96px;
+ border-radius: 48px;
+ background: linear-gradient(to bottom, @red_1, @red_3);
+ color: white;
+}
+
+stack.content {
+ border: 1px solid alpha(@borders, .7);
+ border-radius: 8px;
+ background: @theme_base_color;
+}
+
+flowbox.content {
+ background: @theme_base_color;
+ border-radius: 8px;
+ border: 1px solid alpha(@borders, .7);
+ background: alpha(@borders, .7);
+ background-clip: padding-box;
+}
+
+flowbox.content > flowboxchild {
+ background: @theme_base_color;
+}
+
+.numeric {
+ font-feature-settings: "tnum";
+}
+
+.circular-large {
+ min-width: 50px;
+ min-height: 50px;
+ padding: 0;
+ border-radius: 100px;
+ -gtk-icon-size: 24px;
+}
+
.numeric {
font-feature-settings: "tnum";
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]