[libadwaita] Add AdwSpringAnimation



commit 62eb962322b3c4bacbdf23429f7f0d5320f3384c
Author: Manuel Genovés <manuel genoves gmail com>
Date:   Mon Dec 6 21:11:50 2021 +0500

    Add AdwSpringAnimation
    
    Fixes https://gitlab.gnome.org/GNOME/libadwaita/-/issues/34

 src/adw-spring-animation.c | 889 +++++++++++++++++++++++++++++++++++++++++++++
 src/adw-spring-animation.h |  76 ++++
 src/adw-spring-params.c    | 253 +++++++++++++
 src/adw-spring-params.h    |  47 +++
 src/adwaita.h              |   2 +
 src/meson.build            |   4 +
 6 files changed, 1271 insertions(+)
---
diff --git a/src/adw-spring-animation.c b/src/adw-spring-animation.c
new file mode 100644
index 00000000..7371164e
--- /dev/null
+++ b/src/adw-spring-animation.c
@@ -0,0 +1,889 @@
+/*
+ * Copyright (C) 2021 Manuel Genovés <manuel genoves gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "adw-spring-animation.h"
+#include "adw-spring-params.h"
+
+#include "adw-animation-private.h"
+#include "adw-animation-util.h"
+
+#define DELTA 0.001
+#define MAX_ITERATIONS 20000
+
+/**
+ * AdwSpringAnimation:
+ *
+ * A spring-based [class@Adw.Animation].
+ *
+ * `AdwSpringAnimation` implements an animation driven by a physical model of a 
+ * spring described by [struct@Adw.SpringParams], with a resting position in
+ * [property@Adw.SpringAnimation:value-to], stretched to
+ * [property@Adw.SpringAnimation:value-from].
+ * 
+ * Since the animation is physically simulated, spring animations don't have a
+ * fixed duration. The animation will stop when the simulated spring comes to a
+ * rest - when the amplitude of the oscillations becomes smaller than
+ * [property@Adw.SpringAnimation:epsilon], or immediately when it reaches
+ * [property@Adw.SpringAnimation:value-to] if
+ * [property@Adw.SpringAnimation:clamp] is set to `TRUE`. The estimated duration
+ * can be obtained with [property@Adw.SpringAnimation:estimated-duration].
+ *
+ * Due to the nature of spring-driven motion the animation can overshoot
+ * [property@Adw.SpringAnimation:value-to] before coming to a rest. Whether the
+ * animation will overshoot or not depends on the damping ratio of the spring.
+ * See [struct@Adw.SpringParams] for more information about specific damping
+ * ratio values.
+ *
+ * If [property@Adw.SpringAnimation:clamp] is `TRUE`, the animation will
+ * abruptly end as soon as it reaches the final value, preventing overshooting.
+ *
+ * Animations can have an initial velocity value, set via
+ * [property@Adw.SpringAnimation:initial-velocity], which adjusts the curve
+ * without changing the duration. This makes spring animations useful for
+ * deceleration at the end of gestures.
+ *
+ * If the initial and final values are equal, and the initial velocity is not 0,
+ * the animation value will bounce and return to its resting position.
+ *
+ * Since: 1.0
+ */
+
+struct _AdwSpringAnimation
+{
+  AdwAnimation parent_instance;
+
+  double value_from;
+  double value_to;
+
+  AdwSpringParams *spring_params;
+
+  double initial_velocity;
+  double velocity;
+  double epsilon;
+  gboolean clamp;
+
+  guint estimated_duration; /*ms*/
+};
+
+struct _AdwSpringAnimationClass
+{
+  AdwAnimationClass parent_class;
+};
+
+G_DEFINE_TYPE (AdwSpringAnimation, adw_spring_animation, ADW_TYPE_ANIMATION)
+
+enum {
+  PROP_0,
+  PROP_VALUE_FROM,
+  PROP_VALUE_TO,
+  PROP_SPRING_PARAMS,
+  PROP_INITIAL_VELOCITY,
+  PROP_EPSILON,
+  PROP_CLAMP,
+  PROP_ESTIMATED_DURATION,
+  PROP_VELOCITY,
+  LAST_PROP,
+};
+
+static GParamSpec *props[LAST_PROP];
+
+/* Based on RBBSpringAnimation from RBBAnimation, MIT license.
+ * https://github.com/robb/RBBAnimation/blob/master/RBBAnimation/RBBSpringAnimation.m
+ *
+ * @offset: Starting value of the spring simulation. Use -1 for regular animations,
+ * as the formulas are tailored to rest at 0 and the resulting evolution between 
+ * -1 and 0 will be lerped to the desired range afterwards. Otherwise use 0 for in-place
+ * animations which already start at equilibrium
+ */
+static double
+oscillate (AdwSpringAnimation *self,
+           guint               time,
+           double             *velocity)
+{
+  double b = adw_spring_params_get_damping (self->spring_params);
+  double m = adw_spring_params_get_mass (self->spring_params);
+  double k = adw_spring_params_get_stiffness (self->spring_params);
+  double v0 = self->initial_velocity;
+
+  double t = time / 1000.0;
+
+  double beta = b / (2 * m);
+  double omega0 = sqrt (k / m);
+
+  double x0 = self->value_from - self->value_to;
+
+  double envelope = exp (-beta * t);
+
+  /*
+   * Solutions of the form C1*e^(lambda1*x) + C2*e^(lambda2*x)
+   * for the differential equation m*ẍ+b*ẋ+kx = 0
+   */
+
+  /* Underdamped */
+  if (beta < omega0) {
+    double omega1 = sqrt ((omega0 * omega0) - (beta * beta));
+
+    if (velocity)
+      *velocity = envelope * (v0 * cos (omega1 * t) - (x0 * omega1 + (beta * beta * x0 + beta * v0) / 
(omega1)) * sin (omega1 * t));
+    return self->value_to + envelope * (x0 * cos (omega1 * t) + ((beta * x0 + v0) / omega1) * sin (omega1 * 
t));
+  }
+
+  /* Overdamped */
+  if (beta > omega0) {
+    double omega2 = sqrt ((beta * beta) - (omega0 * omega0));
+
+    if (velocity)
+      *velocity = envelope * (v0 * coshl (omega2 * t) + (omega2 * x0 - (beta * beta * x0 + beta * v0) / 
omega2) * sinhl (omega2 * t));
+    return self->value_to + envelope * (x0 * coshl (omega2 * t) + ((beta * x0 + v0) / omega2) * sinhl 
(omega2 * t));
+  }
+
+  /* Critically damped */
+  if (velocity)
+    *velocity = envelope * (beta * x0 + v0) * (1 - beta);
+  return self->value_to + envelope * (x0 + (beta * x0 + v0) * t);
+}
+
+static guint
+get_first_zero (AdwSpringAnimation *self)
+{
+  /* The first frame is not that important and we avoid finding the trivial 0
+   * for in-place animations. */
+  guint i = 1;
+  double y = oscillate (self, i, NULL);
+  gboolean in_place = G_APPROX_VALUE (self->value_to, self->value_from, FLT_EPSILON);
+
+  while ((self->value_to - self->value_from > FLT_EPSILON && self->value_to - y > self->epsilon) ||
+         (self->value_from - self->value_to > FLT_EPSILON && y - self->value_to > self->epsilon) ||
+         (in_place && (self->initial_velocity < 0.0) && (self->value_to - y > self->epsilon)) ||
+         (in_place && (self->initial_velocity > 0.0) && (y -self->value_to > self->epsilon))) {
+    if (i > MAX_ITERATIONS)
+      return 0;
+
+    y = oscillate (self, i++, NULL);
+  }
+
+  return i;
+}
+
+static guint
+calculate_duration (AdwSpringAnimation *self)
+{
+  double damping = adw_spring_params_get_damping (self->spring_params);
+  double mass = adw_spring_params_get_mass (self->spring_params);
+  double stiffness = adw_spring_params_get_stiffness (self->spring_params);
+
+  double beta = damping / (2 * mass);
+  double omega0;
+  double x0, y0;
+  double x1, y1;
+  double m;
+
+  int i = 0;
+
+  if (beta <= 0)
+    return ADW_DURATION_INFINITE;
+
+  if (self->clamp)
+    return get_first_zero (self);
+
+  omega0 = sqrt (stiffness / mass);
+
+  /*
+   * As first ansatz for the overdamped solution,
+   * and general estimation for the oscillating ones
+   * we take the value of the envelope when it's < epsilon
+   */
+  x0 = -log (self->epsilon) / beta;
+
+  if (beta <= omega0)
+    return x0 * 1000;
+
+  /*
+   * 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*1000, NULL);
+  m = (oscillate (self, (x0 + DELTA) * 1000, NULL) - y0) / DELTA;
+
+  x1 = (self->value_to - y0 + m * x0) / m;
+  y1 = oscillate (self, x1*1000, NULL);
+
+  while (ABS (self->value_to - y1) > self->epsilon) {
+    if (i>1000)
+      return 0;
+    x0 = x1;
+    y0 = y1;
+
+    m = (oscillate (self, (x0 + DELTA) * 1000, NULL) - y0) / DELTA;
+
+    x1 = (self->value_to - y0 + m * x0) / m;
+    y1 = oscillate (self, x1*1000, NULL);
+    i++;
+  }
+
+  return x1 * 1000;
+}
+
+static void
+estimate_duration (AdwSpringAnimation *self)
+{
+  /* This function can be called during construction */
+  if (!self->spring_params)
+    return;
+
+  self->estimated_duration = calculate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ESTIMATED_DURATION]);
+}
+
+static guint 
+adw_spring_animation_estimate_duration (AdwAnimation *animation)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (animation);
+
+  return self->estimated_duration;
+}
+
+static double
+adw_spring_animation_calculate_value (AdwAnimation *animation,
+                                      guint         t)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (animation);
+  double value;
+
+  if (t >= self->estimated_duration) {
+    self->velocity = 0;
+    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VELOCITY]);
+
+    return self->value_to;
+  }
+
+  value = oscillate (self, t, &self->velocity);
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VELOCITY]);
+
+  return value;
+}
+
+static void
+adw_spring_animation_constructed (GObject *object)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (object);
+
+  G_OBJECT_CLASS (adw_spring_animation_parent_class)->constructed (object);
+
+  estimate_duration (self);
+}
+
+static void
+adw_spring_animation_dispose (GObject *object)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (object);
+
+  g_clear_pointer (&self->spring_params, adw_spring_params_unref);
+
+  G_OBJECT_CLASS (adw_spring_animation_parent_class)->dispose (object);
+}
+
+static void
+adw_spring_animation_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (object);
+
+  switch (prop_id) {
+  case PROP_VALUE_FROM:
+    g_value_set_double (value, adw_spring_animation_get_value_from (self));
+    break;
+
+  case PROP_VALUE_TO:
+    g_value_set_double (value, adw_spring_animation_get_value_to (self));
+    break;
+
+  case PROP_SPRING_PARAMS:
+    g_value_set_boxed (value, adw_spring_animation_get_spring_params (self));
+    break;
+
+  case PROP_INITIAL_VELOCITY:
+    g_value_set_double (value, adw_spring_animation_get_initial_velocity (self));
+    break;
+
+  case PROP_EPSILON:
+    g_value_set_double (value, adw_spring_animation_get_epsilon (self));
+    break;
+
+  case PROP_CLAMP:
+    g_value_set_boolean (value, adw_spring_animation_get_clamp (self));
+    break;
+
+  case PROP_ESTIMATED_DURATION:
+    g_value_set_uint (value, adw_spring_animation_get_estimated_duration (self));
+    break;
+
+  case PROP_VELOCITY:
+    g_value_set_double (value, adw_spring_animation_get_velocity (self));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_spring_animation_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  AdwSpringAnimation *self = ADW_SPRING_ANIMATION (object);
+
+  switch (prop_id) {
+  case PROP_VALUE_FROM:
+    adw_spring_animation_set_value_from (self, g_value_get_double (value));
+    break;
+
+  case PROP_VALUE_TO:
+    adw_spring_animation_set_value_to (self, g_value_get_double (value));
+    break;
+
+  case PROP_SPRING_PARAMS:
+    adw_spring_animation_set_spring_params (self, g_value_get_boxed (value));
+    break;
+
+  case PROP_INITIAL_VELOCITY:
+    adw_spring_animation_set_initial_velocity (self, g_value_get_double (value));
+    break;
+
+  case PROP_EPSILON:
+    adw_spring_animation_set_epsilon (self, g_value_get_double (value));
+    break;
+
+  case PROP_CLAMP:
+    adw_spring_animation_set_clamp (self, g_value_get_boolean (value));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_spring_animation_class_init (AdwSpringAnimationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  AdwAnimationClass *animation_class = ADW_ANIMATION_CLASS (klass);
+
+  object_class->constructed = adw_spring_animation_constructed;
+  object_class->dispose = adw_spring_animation_dispose;
+  object_class->set_property = adw_spring_animation_set_property;
+  object_class->get_property = adw_spring_animation_get_property;
+
+  animation_class->estimate_duration = adw_spring_animation_estimate_duration;
+  animation_class->calculate_value = adw_spring_animation_calculate_value;
+
+  /**
+   * AdwSpringAnimation:value-from: (attributes org.gtk.Property.get=adw_spring_animation_get_value_from 
org.gtk.Property.set=adw_spring_animation_set_value_from)
+   *
+   * The value to animate from.
+   *
+   * The animation will start at this value and end at
+   * [property@Adw.SpringAnimation:value-to].
+   *
+   * Since: 1.0
+   */
+  props[PROP_VALUE_FROM] =
+    g_param_spec_double ("value-from",
+                         "Initial value",
+                         "The value to animate from",
+                         -G_MAXDOUBLE,
+                         G_MAXDOUBLE,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+  /**
+   * AdwSpringAnimation:value-to: (attributes org.gtk.Property.get=adw_spring_animation_get_value_to 
org.gtk.Property.set=adw_spring_animation_set_value_to)
+   *
+   * The value to animate to.
+   *
+   * The animation will start at [property@Adw.SpringAnimation:value-from] and
+   * end at this value.
+   *
+   * Since: 1.0
+   */
+  props[PROP_VALUE_TO] =
+    g_param_spec_double ("value-to",
+                         "Final value",
+                         "The value to animate to",
+                         -G_MAXDOUBLE,
+                         G_MAXDOUBLE,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+  /**
+   * AdwSpringAnimation:spring-params: (attributes 
org.gtk.Property.get=adw_spring_animation_get_spring_params 
org.gtk.Property.set=adw_spring_animation_set_spring_params)
+   *
+   * Physical parameters describing the spring.
+   *
+   * Since: 1.0
+   */
+  props[PROP_SPRING_PARAMS] =
+    g_param_spec_boxed ("spring-params",
+                        "Spring parameters",
+                        "Physical parameters describing the spring",
+                        ADW_TYPE_SPRING_PARAMS,
+                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+  /**
+   * AdwSpringAnimation:initial-velocity: (attributes 
org.gtk.Property.get=adw_spring_animation_get_initial_velocity 
org.gtk.Property.set=adw_spring_animation_set_initial_velocity)
+   *
+   * The initial velocity to start the animation with.
+   *
+   * Initial velocity affects only the animation curve, but not its duration.
+   *
+   * Since: 1.0
+   */
+  props[PROP_INITIAL_VELOCITY] =
+    g_param_spec_double ("initial-velocity",
+                         "Initial velocity",
+                         "The initial velocity to start the animation with",
+                         -G_MAXDOUBLE,
+                         G_MAXDOUBLE,
+                         0,
+                         G_PARAM_READWRITE);
+
+  /**
+   * AdwSpringAnimation:epsilon: (attributes org.gtk.Property.get=adw_spring_animation_get_epsilon 
org.gtk.Property.set=adw_spring_animation_set_epsilon)
+   *
+   * Precision of the spring.
+   *
+   * The level of precision used to determine when the animation has come to a
+   * rest, that is, when the amplitude of the oscillations becomes smaller than
+   * this value.
+   *
+   * If the epsilon value is too small, the animation will take a long time to
+   * stop after the animated value has stopped visibly changing.
+   *
+   * If the epsilon value is too large, the animation will end prematurely.
+   *
+   * The default value is 0.001.
+   *
+   * Since: 1.0
+   */
+  props[PROP_EPSILON] =
+    g_param_spec_double ("epsilon",
+                         "Epsilon",
+                         "Precision of the spring",
+                         0,
+                         G_MAXDOUBLE,
+                         0.001,
+                         G_PARAM_READWRITE);
+
+  /**
+   * AdwSpringAnimation:clamp: (attributes org.gtk.Property.get=adw_spring_animation_get_clamp 
org.gtk.Property.set=adw_spring_animation_set_clamp)
+   *
+   * Whether the animation should be clamped.
+   *
+   * If set to `TRUE`, the animation will abruptly end as soon as it reaches the
+   * final value, preventing overshooting.
+   *
+   * It won't prevent overshooting [property@Adw.SpringAnimation:value-from] if
+   * a relative negative [property@Adw.SpringAnimation:initial-velocity] is set.
+   *
+   * Since: 1.0
+   */
+  props[PROP_CLAMP] =
+    g_param_spec_boolean ("clamp",
+                          "Clamp",
+                          "Whether the animation should be clamped",
+                          FALSE,
+                          G_PARAM_READWRITE);
+
+  /**
+   * AdwSpringAnimation:estimated-duration: (attributes 
org.gtk.Property.get=adw_spring_animation_get_estimated_duration)
+   *
+   * Estimated duration of the animation in milliseconds.
+   *
+   * Since: 1.0
+   */
+  props[PROP_ESTIMATED_DURATION] =
+    g_param_spec_uint ("estimated-duration",
+                       "Estimated duration",
+                       "Estimated duration of the animation in milliseconds",
+                       0,
+                       ADW_DURATION_INFINITE,
+                       0,
+                       G_PARAM_READABLE);
+
+  /**
+   * AdwSpringAnimation:velocity: (attributes org.gtk.Property.get=adw_spring_animation_get_velocity)
+   *
+   * Current velocity of the animation.
+   *
+   * Since: 1.0
+   */
+  props[PROP_VELOCITY] =
+    g_param_spec_double ("velocity",
+                         "Velocity",
+                         "The current velocity of the animation",
+                         -G_MAXDOUBLE,
+                         G_MAXDOUBLE,
+                         0,
+                         G_PARAM_READABLE);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+static void
+adw_spring_animation_init (AdwSpringAnimation *self)
+{
+  self->epsilon = 0.001;
+}
+
+/**
+ * adw_spring_animation_new:
+ * @widget: a widget to create animation on
+ * @from: a value to animate from
+ * @to: a value to animate to
+ * @spring_params: (transfer full): physical parameters of the spring
+ * @target: (transfer full): a target value to animate
+ *
+ * Creates a new `AdwSpringAnimation` on @widget.
+ *
+ * The animation will animate @target from @from to @to with the dynamics of a
+ * spring described by @spring_params.
+ *
+ * Returns: (transfer none): the newly created animation
+ *
+ * Since: 1.0
+ */
+AdwAnimation *
+adw_spring_animation_new (GtkWidget         *widget,
+                          double              from,
+                          double              to,
+                          AdwSpringParams    *spring_params,
+                          AdwAnimationTarget *target)
+{
+  AdwAnimation *animation;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+  g_return_val_if_fail (spring_params != NULL, NULL);
+  g_return_val_if_fail (ADW_IS_ANIMATION_TARGET (target), NULL);
+
+  animation = g_object_new (ADW_TYPE_SPRING_ANIMATION,
+                            "widget", widget,
+                            "value-from", from,
+                            "value-to", to,
+                            "spring-params", spring_params,
+                            "target", target,
+                            NULL);
+
+  g_object_unref (target);
+  adw_spring_params_unref (spring_params);
+
+  return animation;
+}
+
+/**
+ * adw_spring_animation_get_value_from: (attributes org.gtk.Method.get_property=value-from)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the value @self will animate from.
+ *
+ * Returns: the value to animate from
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_animation_get_value_from (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0.0);
+
+  return self->value_from;
+}
+
+/**
+ * adw_spring_animation_set_value_from: (attributes org.gtk.Method.set_property=value-from)
+ * @self: a `AdwSpringAnimation`
+ * @value: the value to animate from
+ *
+ * Sets the value @self will animate from.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_value_from (AdwSpringAnimation *self,
+                                     double             value)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+
+  if (G_APPROX_VALUE (self->value_from, value, FLT_EPSILON))
+    return;
+
+  self->value_from = value;
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VALUE_FROM]);
+}
+
+/**
+ * adw_spring_animation_get_value_to: (attributes org.gtk.Method.get_property=value-to)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the value @self will animate to.
+ *
+ * Returns: the value to animate to
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_animation_get_value_to (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0.0);
+
+  return self->value_to;
+}
+
+/**
+ * adw_spring_animation_set_value_to: (attributes org.gtk.Method.set_property=value-to)
+ * @self: a `AdwSpringAnimation`
+ * @value: the value to animate to
+ *
+ * Sets the value @self will animate to.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_value_to (AdwSpringAnimation *self,
+                                   double             value)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+
+  if (G_APPROX_VALUE (self->value_to, value, FLT_EPSILON))
+    return;
+
+  self->value_to = value;
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VALUE_TO]);
+}
+
+/**
+ * adw_spring_animation_get_spring_params: (attributes org.gtk.Method.get_property=spring-params)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the physical parameters of the spring of @self.
+ *
+ * Returns: (transfer none): the spring parameters
+ *
+ * Since: 1.0
+ */
+AdwSpringParams *
+adw_spring_animation_get_spring_params (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), NULL);
+
+  return self->spring_params;
+}
+
+/**
+ * adw_spring_animation_set_spring_params: (attributes org.gtk.Method.set_property=spring-params)
+ * @self: a `AdwSpringAnimation`
+ * @spring_params: the new spring parameters
+ *
+ * Sets the physical parameters of the spring of @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_spring_params (AdwSpringAnimation *self,
+                                        AdwSpringParams    *spring_params)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+  g_return_if_fail (spring_params != NULL);
+
+  if (self->spring_params == spring_params)
+    return;
+
+  g_clear_pointer (&self->spring_params, adw_spring_params_unref);
+  self->spring_params = adw_spring_params_ref (spring_params);
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SPRING_PARAMS]);
+}
+
+/**
+ * adw_spring_animation_get_initial_velocity: (attributes org.gtk.Method.get_property=initial-velocity)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the initial velocity of @self.
+ *
+ * Returns: the initial velocity
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_animation_get_initial_velocity (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0.0);
+
+  return self->initial_velocity;
+}
+
+/**
+ * adw_spring_animation_set_initial_velocity: (attributes org.gtk.Method.set_property=initial-velocity)
+ * @self: a `AdwSpringAnimation`
+ * @velocity: the initial velocity
+ *
+ * Sets the initial velocity of @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_initial_velocity (AdwSpringAnimation *self,
+                                           double              velocity)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+
+  if (G_APPROX_VALUE (self->initial_velocity, velocity, FLT_EPSILON))
+    return;
+
+  self->initial_velocity = velocity;
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INITIAL_VELOCITY]);
+}
+
+/**
+ * adw_spring_animation_get_epsilon: (attributes org.gtk.Method.get_property=epsilon)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the precision used to determine the duration of @self.
+ *
+ * Returns: the epsilon value
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_animation_get_epsilon (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0.0);
+
+  return self->epsilon;
+}
+
+/**
+ * adw_spring_animation_set_epsilon: (attributes org.gtk.Method.set_property=epsilon)
+ * @self: a `AdwSpringAnimation`
+ * @epsilon: the new value
+ *
+ * Sets the precision used to determine the duration of @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_epsilon (AdwSpringAnimation *self,
+                                  double              epsilon)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+  g_return_if_fail (epsilon> 0.0);
+
+  if (G_APPROX_VALUE (self->epsilon, epsilon, FLT_EPSILON))
+    return;
+
+  self->epsilon = epsilon;
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_EPSILON]);
+}
+
+/**
+ * adw_spring_animation_get_clamp: (attributes org.gtk.Method.get_property=clamp)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets whether @self should be clamped.
+ *
+ * Returns: whether @self is clamped
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_spring_animation_get_clamp (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), FALSE);
+
+  return self->clamp;
+}
+
+/**
+ * adw_spring_animation_set_clamp: (attributes org.gtk.Method.set_property=clamp)
+ * @self: a `AdwSpringAnimation`
+ * @clamp: the new value
+ *
+ * Sets whether @self should be clamped.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_animation_set_clamp (AdwSpringAnimation *self,
+                                gboolean            clamp)
+{
+  g_return_if_fail (ADW_IS_SPRING_ANIMATION (self));
+
+  if (self->clamp == clamp)
+    return;
+
+  self->clamp = clamp;
+
+  estimate_duration (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CLAMP]);
+}
+
+/**
+ * adw_spring_animation_get_estimated_duration: (attributes org.gtk.Method.get_property=estimated-duration)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the estimated duration of @self.
+ *
+ * Returns: the estimated duration
+ *
+ * Since: 1.0
+ */
+guint
+adw_spring_animation_get_estimated_duration (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0);
+
+  return self->estimated_duration;
+}
+
+/**
+ * adw_spring_animation_get_velocity: (attributes org.gtk.Method.get_property=velocity)
+ * @self: a `AdwSpringAnimation`
+ *
+ * Gets the current velocity of @self.
+ *
+ * Returns: the current velocity
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_animation_get_velocity (AdwSpringAnimation *self)
+{
+  g_return_val_if_fail (ADW_IS_SPRING_ANIMATION (self), 0.0);
+
+  return self->velocity;
+}
diff --git a/src/adw-spring-animation.h b/src/adw-spring-animation.h
new file mode 100644
index 00000000..796d6c91
--- /dev/null
+++ b/src/adw-spring-animation.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 Manuel Genovés <manuel genoves gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-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-animation.h"
+#include "adw-spring-params.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_SPRING_ANIMATION (adw_spring_animation_get_type())
+
+ADW_AVAILABLE_IN_ALL
+GDK_DECLARE_INTERNAL_TYPE (AdwSpringAnimation, adw_spring_animation, ADW, SPRING_ANIMATION, AdwAnimation)
+
+ADW_AVAILABLE_IN_ALL
+AdwAnimation *adw_spring_animation_new (GtkWidget          *widget,
+                                        double              from,
+                                        double              to,
+                                        AdwSpringParams    *spring_params,
+                                        AdwAnimationTarget *target) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_animation_get_value_from (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void   adw_spring_animation_set_value_from (AdwSpringAnimation *self,
+                                            double             value);
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_animation_get_value_to (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void   adw_spring_animation_set_value_to (AdwSpringAnimation *self,
+                                          double             value);
+
+ADW_AVAILABLE_IN_ALL
+AdwSpringParams *adw_spring_animation_get_spring_params (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void             adw_spring_animation_set_spring_params (AdwSpringAnimation *self,
+                                                         AdwSpringParams    *spring_params);
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_animation_get_initial_velocity (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void   adw_spring_animation_set_initial_velocity (AdwSpringAnimation *self,
+                                                  double              velocity);
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_animation_get_epsilon (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void   adw_spring_animation_set_epsilon (AdwSpringAnimation *self,
+                                         double              epsilon);
+
+ADW_AVAILABLE_IN_ALL
+gboolean adw_spring_animation_get_clamp (AdwSpringAnimation *self);
+ADW_AVAILABLE_IN_ALL
+void     adw_spring_animation_set_clamp (AdwSpringAnimation *self,
+                                         gboolean            clamp);
+
+ADW_AVAILABLE_IN_ALL
+guint adw_spring_animation_get_estimated_duration (AdwSpringAnimation *self);
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_animation_get_velocity (AdwSpringAnimation *self);
+
+G_END_DECLS
diff --git a/src/adw-spring-params.c b/src/adw-spring-params.c
new file mode 100644
index 00000000..8093364a
--- /dev/null
+++ b/src/adw-spring-params.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 Manuel Genovés <manuel genoves gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "adw-spring-params.h"
+
+#include <math.h>
+
+G_DEFINE_BOXED_TYPE (AdwSpringParams, adw_spring_params,
+                     adw_spring_params_ref, adw_spring_params_unref)
+
+/**
+ * AdwSpringParams:
+ *
+ * Physical parameters of a spring for [class@Adw.SpringAnimation].
+ *
+ * Any spring can be described by three parameters: mass, stiffness and damping.
+ *
+ * An undamped spring will produce an oscillatory motion which will go on
+ * forever.
+ *
+ * The frequency and amplitude of the oscillations will be determined by the
+ * stiffness (how "strong" the spring is) and its mass (how much "inertia" it
+ * has).
+ *
+ * If damping is larger than 0, the amplitude of that oscillating motion will
+ * exponientally decrease over time. If that damping is strong enough that the
+ * spring can't complete a full oscillation, it's called an overdamped spring.
+ *
+ * If we the spring can oscillate, it's called an underdamped spring.
+ *
+ * The value between these two behaviors is called critical damping; a
+ * critically damped spring will comes to rest in the minimum possible time
+ * without producing oscillations.
+ *
+ * The damping can be replaced by damping ratio, which produces the following
+ * springs:
+ *
+ * * 0: an undamped spring.
+ * * Between 0 and 1: an underdamped spring.
+ * * 1: a critically damped spring.
+ * * Larger than 1: an overdamped spring.
+ *
+ * As such
+ *
+ * Since: 1.0
+ */
+
+struct _AdwSpringParams
+{
+  gatomicrefcount ref_count;
+
+  double damping;
+  double mass;
+  double stiffness;
+};
+
+/**
+ * adw_spring_params_new:
+ * @damping_ratio: the damping ratio of the spring
+ * @mass: the mass of the spring
+ * @stiffness: the stiffness of the spring
+ *
+ * Creates a new `AdwSpringParams` from @mass, @stiffness and @damping_ratio.
+ *
+ * The damping value is calculated from @damping_ratio and the other two
+ * parameters.
+ *
+ * * If @damping_ratio is 0, the spring will not be damped and will oscillate
+ *   endlessly.
+ * * If @damping_ratio is between 0 and 1, the spring is underdamped and will
+ *   always overshoot.
+ * * If @damping_ratio is 1, the spring is critically damped and will reach its
+ *   resting position the quickest way possible.
+ * * If @damping_ratio is larger than 1, the spring is overdamped and will reach
+ *   its resting position faster than it can complete an oscillation.
+ *
+ * [ctor@Adw.SpringParams.new_full] allows to pass a raw damping value instead.
+ *
+ * Returns: (transfer full): the newly created spring parameters
+ *
+ * Since: 1.0
+ */
+AdwSpringParams *
+adw_spring_params_new (double damping_ratio,
+                       double mass,
+                       double stiffness)
+{
+  double critical_damping, damping;
+
+  g_return_val_if_fail (damping_ratio >= 0.0, NULL);
+
+  critical_damping = 2 * sqrt (mass * stiffness);
+  damping = damping_ratio * critical_damping;
+
+  return adw_spring_params_new_full (damping, mass, stiffness);
+}
+
+/**
+ * adw_spring_params_new_full:
+ * @damping: the damping of the spring
+ * @mass: the mass of the spring
+ * @stiffness: the stiffness of the spring
+ *
+ * Creates a new `AdwSpringParams` from @mass, @stiffness and @damping.
+ *
+ * See [ctor Adw SpringParams new] for a simplified constructor using damping
+ * ratio instead of @damping.
+ *
+ * Returns: (transfer full): the newly created spring parameters
+ *
+ * Since: 1.0
+ */
+AdwSpringParams *
+adw_spring_params_new_full (double damping,
+                            double mass,
+                            double stiffness)
+{
+  AdwSpringParams *self;
+
+  g_return_val_if_fail (damping >= 0.0, NULL);
+  g_return_val_if_fail (mass > 0.0, NULL);
+  g_return_val_if_fail (stiffness > 0.0, NULL);
+
+  self = g_slice_new0 (AdwSpringParams);
+
+  g_atomic_ref_count_init (&self->ref_count);
+
+  self->damping = damping;
+  self->mass = mass;
+  self->stiffness = stiffness;
+
+  return self;
+}
+
+/**
+ * adw_spring_params_ref:
+ * @self: a `AdwSpringParams`
+ *
+ * Increases the reference count of @self.
+ *
+ * Return: (transfer full): @self
+ *
+ * Since: 1.0
+ */
+AdwSpringParams *
+adw_spring_params_ref (AdwSpringParams *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+
+  g_atomic_ref_count_inc (&self->ref_count);
+
+  return self;
+}
+
+/**
+ * adw_spring_params_unref:
+ * @self: a `AdwSpringParams`
+ *
+ * Decreases the reference count of @self.
+ *
+ * If the last reference is dropped, the structure is freed.
+ *
+ * Since: 1.0
+ */
+void
+adw_spring_params_unref (AdwSpringParams *self)
+{
+  g_return_if_fail (self != NULL);
+
+  if (g_atomic_ref_count_dec (&self->ref_count))
+    g_slice_free (AdwSpringParams, self);
+}
+
+/**
+ * adw_spring_params_get_damping:
+ * @self: a `AdwSpringParams`
+ *
+ * Gets the damping of @self.
+ *
+ * Returns: the damping
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_params_get_damping (AdwSpringParams *self)
+{
+  g_return_val_if_fail (self != NULL, 0.0);
+
+  return self->damping;
+}
+
+/**
+ * adw_spring_params_get_damping_ratio:
+ * @self: a `AdwSpringParams`
+ *
+ * Gets the damping ratio of @self.
+ *
+ * Returns: the damping ratio
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_params_get_damping_ratio (AdwSpringParams *self)
+{
+  double critical_damping;
+
+  g_return_val_if_fail (self != NULL, 0.0);
+
+  critical_damping = 2 * sqrt (self->mass * self->stiffness);
+
+  return self->damping / critical_damping;
+}
+
+/**
+ * adw_spring_params_get_mass:
+ * @self: a `AdwSpringParams`
+ *
+ * Gets the mass of @self.
+ *
+ * Returns: the mass
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_params_get_mass (AdwSpringParams *self)
+{
+  g_return_val_if_fail (self != NULL, 0.0);
+
+  return self->mass;
+}
+
+/**
+ * adw_spring_params_get_stiffness:
+ * @self: a `AdwSpringParams`
+ *
+ * Gets the stiffness of @self.
+ *
+ * Returns: the stiffness
+ *
+ * Since: 1.0
+ */
+double
+adw_spring_params_get_stiffness (AdwSpringParams *self)
+{
+  g_return_val_if_fail (self != NULL, 0.0);
+
+  return self->stiffness;
+}
diff --git a/src/adw-spring-params.h b/src/adw-spring-params.h
new file mode 100644
index 00000000..6a344dd0
--- /dev/null
+++ b/src/adw-spring-params.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 Manuel Genovés <manuel genoves gmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include "adw-version.h"
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_SPRING_PARAMS (adw_spring_params_get_type())
+
+typedef struct _AdwSpringParams AdwSpringParams;
+
+ADW_AVAILABLE_IN_ALL
+GType adw_spring_params_get_type (void) G_GNUC_CONST;
+
+ADW_AVAILABLE_IN_ALL
+AdwSpringParams *adw_spring_params_new         (double damping_ratio,
+                                                double mass,
+                                                double stiffness) G_GNUC_WARN_UNUSED_RESULT;
+ADW_AVAILABLE_IN_ALL
+AdwSpringParams *adw_spring_params_new_full    (double damping,
+                                                double mass,
+                                                double stiffness) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_ALL
+AdwSpringParams *adw_spring_params_ref   (AdwSpringParams *self);
+ADW_AVAILABLE_IN_ALL
+void             adw_spring_params_unref (AdwSpringParams *self);
+
+ADW_AVAILABLE_IN_ALL
+double adw_spring_params_get_damping       (AdwSpringParams *self);
+ADW_AVAILABLE_IN_ALL
+double adw_spring_params_get_damping_ratio (AdwSpringParams *self);
+ADW_AVAILABLE_IN_ALL
+double adw_spring_params_get_mass          (AdwSpringParams *self);
+ADW_AVAILABLE_IN_ALL
+double adw_spring_params_get_stiffness     (AdwSpringParams *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (AdwSpringParams, adw_spring_params_unref)
+
+G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index 62e2e154..3371b1c9 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -52,6 +52,8 @@ G_BEGIN_DECLS
 #include "adw-preferences-row.h"
 #include "adw-preferences-window.h"
 #include "adw-split-button.h"
+#include "adw-spring-animation.h"
+#include "adw-spring-params.h"
 #include "adw-squeezer.h"
 #include "adw-status-page.h"
 #include "adw-style-manager.h"
diff --git a/src/meson.build b/src/meson.build
index 7988a9f7..19413abf 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -111,6 +111,8 @@ src_headers = [
   'adw-preferences-row.h',
   'adw-preferences-window.h',
   'adw-split-button.h',
+  'adw-spring-animation.h',
+  'adw-spring-params.h',
   'adw-squeezer.h',
   'adw-status-page.h',
   'adw-style-manager.h',
@@ -176,6 +178,8 @@ src_sources = [
   'adw-settings.c',
   'adw-shadow-helper.c',
   'adw-split-button.c',
+  'adw-spring-animation.c',
+  'adw-spring-params.c',
   'adw-squeezer.c',
   'adw-style-manager.c',
   'adw-status-page.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]