[libadwaita] animation-target: Add AdwPropertyAnimationTarget
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita] animation-target: Add AdwPropertyAnimationTarget
- Date: Thu, 12 May 2022 16:19:12 +0000 (UTC)
commit 5f8fe1c9951791f828fb6f97309a552a840c4475
Author: George Barrett <bob bob131 so>
Date: Fri May 13 01:31:27 2022 +1000
animation-target: Add AdwPropertyAnimationTarget
This commit adds a new AdwAnimationTarget subclass,
AdwPropertyAnimationTarget. This facility makes it simpler to animate
GObject properties. This commit also adds a basic smoke test to
test-animation.
Fixes #466.
src/adw-animation-target.c | 419 +++++++++++++++++++++++++++++++++++++++++-
src/adw-animation-target.h | 19 ++
tests/meson.build | 1 +
tests/test-animation-target.c | 78 ++++++++
4 files changed, 515 insertions(+), 2 deletions(-)
---
diff --git a/src/adw-animation-target.c b/src/adw-animation-target.c
index f26911e2..cbc4ab5a 100644
--- a/src/adw-animation-target.c
+++ b/src/adw-animation-target.c
@@ -15,8 +15,6 @@
*
* Represents a value [class@Animation] can animate.
*
- * Currently the only implementation is [class@CallbackAnimationTarget].
- *
* Since: 1.0
*/
@@ -29,6 +27,15 @@
* Since: 1.0
*/
+/**
+ * AdwPropertyAnimationTarget:
+ *
+ * An [class@AnimationTarget] changing the value of a property of a
+ * [class@GObject.Object] instance.
+ *
+ * Since: 1.2
+ */
+
struct _AdwAnimationTarget
{
GObject parent_instance;
@@ -146,3 +153,411 @@ adw_callback_animation_target_new (AdwAnimationTargetFunc callback,
return ADW_ANIMATION_TARGET (self);
}
+
+struct _AdwPropertyAnimationTarget
+{
+ AdwAnimationTarget parent_instance;
+
+ GObject *object;
+
+ /* `property_name` should only be set during construction; if set, `pspec`
+ should be unset, and vice-versa. */
+ char *property_name;
+ GParamSpec *pspec;
+};
+
+struct _AdwPropertyAnimationTargetClass
+{
+ AdwAnimationTargetClass parent_class;
+};
+
+G_DEFINE_FINAL_TYPE (AdwPropertyAnimationTarget, adw_property_animation_target, ADW_TYPE_ANIMATION_TARGET)
+
+enum {
+ PROPERTY_PROP_0,
+ PROPERTY_PROP_OBJECT,
+ PROPERTY_PROP_PROPERTY_NAME,
+ PROPERTY_PROP_PSPEC,
+ LAST_PROPERTY_PROP
+};
+
+static GParamSpec *property_props[LAST_PROPERTY_PROP];
+
+static void
+object_weak_notify (gpointer data,
+ GObject *object)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (data);
+ self->object = NULL;
+ g_critical ("Finalizing object associated with an AdwPropertyAnimationTarget");
+}
+
+static void
+set_object (AdwPropertyAnimationTarget *self,
+ GObject *object)
+{
+ if (self->object)
+ g_object_weak_unref (self->object, object_weak_notify, self);
+ self->object = object;
+ g_object_weak_ref (self->object, object_weak_notify, self);
+}
+
+static void
+set_property_name (AdwPropertyAnimationTarget *self,
+ const char *property_name)
+{
+ if (self->pspec) {
+ g_critical ("Attempt to set property 'property-name' to '%s' on "
+ "AdwPropertyAnimationTarget with property 'pspec' already set "
+ "to '%s:%s'. Using 'property-name' instead",
+ property_name,
+ g_type_name (self->pspec->owner_type), self->pspec->name);
+ g_clear_pointer (&self->pspec, g_param_spec_unref);
+ }
+
+ g_clear_pointer (&self->property_name, g_free);
+ self->property_name = g_strdup (property_name);
+}
+
+static void
+set_pspec (AdwPropertyAnimationTarget *self,
+ GParamSpec *pspec)
+{
+ if (self->property_name) {
+ g_critical ("Attempt to set property 'pspec' to '%s:%s' on "
+ "AdwPropertyAnimationTarget with property 'property-name' "
+ "already set to '%s'. Using 'pspec' instead",
+ g_type_name (pspec->owner_type), pspec->name,
+ self->property_name);
+ g_clear_pointer (&self->property_name, g_free);
+ }
+
+ g_clear_pointer (&self->pspec, g_param_spec_unref);
+ self->pspec = g_param_spec_ref (pspec);
+}
+
+static void
+adw_property_animation_target_set_value (AdwAnimationTarget *target,
+ double value)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (target);
+ GValue gvalue = G_VALUE_INIT;
+
+ if (!self->object || !self->pspec)
+ return;
+
+ g_value_init (&gvalue, G_TYPE_DOUBLE);
+ g_value_set_double (&gvalue, value);
+ g_object_set_property (self->object, self->pspec->name, &gvalue);
+}
+
+static void
+adw_property_animation_target_constructed (GObject *object)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (object);
+
+ G_OBJECT_CLASS (adw_property_animation_target_parent_class)->constructed (object);
+
+ if (!self->object) {
+ g_error ("AdwPropertyAnimationTarget constructed without specifying a value "
+ "for the 'object' property");
+ return;
+ }
+
+ if (!self->property_name && !self->pspec) {
+ g_error ("AdwPropertyAnimationTarget constructed without specifying a value "
+ "for either the 'property-name' or 'pspec' properties");
+ return;
+ }
+
+ /* Only one of these should be set. */
+ g_assert (!(self->property_name && self->pspec));
+
+ if (self->property_name) {
+ GParamSpec *pspec =
+ g_object_class_find_property (G_OBJECT_GET_CLASS (self->object),
+ self->property_name);
+
+ if (pspec) {
+ self->pspec = g_param_spec_ref (pspec);
+
+ } else {
+ g_error ("Type '%s' does not have a property named '%s'",
+ G_OBJECT_TYPE_NAME (self->object),
+ self->property_name);
+ }
+
+ g_clear_pointer (&self->property_name, g_free);
+ } else if (self->pspec) {
+ if (!g_type_is_a (G_OBJECT_TYPE (self->object), self->pspec->owner_type)) {
+ g_error ("Cannot create AdwPropertyAnimationTarget: %s doesn't have the "
+ "%s:%s property",
+ G_OBJECT_TYPE_NAME (self->object),
+ g_type_name (self->pspec->owner_type),
+ self->pspec->name);
+ g_clear_pointer (&self->pspec, g_param_spec_unref);
+ }
+ }
+}
+
+static void
+adw_property_animation_target_dispose (GObject *object)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (object);
+
+ if (self->object)
+ g_object_weak_unref (self->object, object_weak_notify, self);
+ self->object = NULL;
+
+ G_OBJECT_CLASS (adw_property_animation_target_parent_class)->dispose (object);
+}
+
+static void
+adw_property_animation_target_finalize (GObject *object)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (object);
+
+ g_free (self->property_name);
+ g_clear_pointer (&self->pspec, g_param_spec_unref);
+
+ G_OBJECT_CLASS (adw_property_animation_target_parent_class)->finalize (object);
+}
+
+static void
+adw_property_animation_target_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (object);
+
+ switch (prop_id) {
+ case PROPERTY_PROP_OBJECT:
+ g_value_set_object (value, adw_property_animation_target_get_object (self));
+ break;
+
+ case PROPERTY_PROP_PROPERTY_NAME:
+ g_value_set_string (value,
+ adw_property_animation_target_get_property_name (self));
+ break;
+
+ case PROPERTY_PROP_PSPEC:
+ g_value_set_param (value, adw_property_animation_target_get_pspec (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_property_animation_target_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwPropertyAnimationTarget *self = ADW_PROPERTY_ANIMATION_TARGET (object);
+
+ switch (prop_id) {
+ case PROPERTY_PROP_OBJECT:
+ if (g_value_get_object (value) != NULL)
+ set_object (self, g_value_get_object (value));
+ break;
+
+ case PROPERTY_PROP_PROPERTY_NAME:
+ if (g_value_get_string (value) != NULL)
+ set_property_name (self, g_value_get_string (value));
+ break;
+
+ case PROPERTY_PROP_PSPEC:
+ if (g_value_get_param (value) != NULL)
+ set_pspec (self, g_value_get_param (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_property_animation_target_class_init (AdwPropertyAnimationTargetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ AdwAnimationTargetClass *target_class = ADW_ANIMATION_TARGET_CLASS (klass);
+
+ object_class->constructed = adw_property_animation_target_constructed;
+ object_class->dispose = adw_property_animation_target_dispose;
+ object_class->finalize = adw_property_animation_target_finalize;
+ object_class->get_property = adw_property_animation_target_get_property;
+ object_class->set_property = adw_property_animation_target_set_property;
+
+ target_class->set_value = adw_property_animation_target_set_value;
+
+ /**
+ * AdwPropertyAnimationTarget:object: (attributes
org.gtk.Property.get=adw_property_animation_target_get_object)
+ *
+ * The object whose property will be animated.
+ *
+ * The `AdwPropertyAnimationTarget` instance does not hold a strong reference
+ * on the object; make sure the object is kept alive throughout the target's
+ * lifetime.
+ *
+ * Since: 1.2
+ */
+ property_props[PROPERTY_PROP_OBJECT] =
+ g_param_spec_object ("object",
+ "Object",
+ "The object whose property will be animated",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * AdwPropertyAnimationTarget:property-name: (attributes
org.gtk.Property.get=adw_property_animation_target_get_property_name)
+ *
+ * The name of the property to be animated.
+ *
+ * Only one of `property-name` or [property@PropertyAnimationTarget:pspec]
+ * should be set.
+ *
+ * Since: 1.2
+ */
+ property_props[PROPERTY_PROP_PROPERTY_NAME] =
+ g_param_spec_string ("property-name",
+ "Property name",
+ "The name of the property to be animated",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * AdwPropertyAnimationTarget:pspec: (attributes
org.gtk.Property.get=adw_property_animation_target_get_pspec)
+ *
+ * The `GParamSpec` of the property to be animated.
+ *
+ * Only one of `pspec` or [property@PropertyAnimationTarget:property-name]
+ * should be set.
+ *
+ * Since: 1.2
+ */
+ property_props[PROPERTY_PROP_PSPEC] =
+ g_param_spec_param ("pspec",
+ "Param spec",
+ "The param spec of the property to be animated",
+ G_TYPE_PARAM,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class,
+ LAST_PROPERTY_PROP,
+ property_props);
+}
+
+static void
+adw_property_animation_target_init (AdwPropertyAnimationTarget *self)
+{
+}
+
+/**
+ * adw_property_animation_target_new:
+ * @object: an object to be animated
+ * @property_name: the name of the property on @object to animate
+ *
+ * Creates a new `AdwPropertyAnimationTarget` for the @property_name property on
+ * @object.
+ *
+ * Returns: the newly created `AdwPropertyAnimationTarget`
+ *
+ * Since: 1.2
+ */
+AdwAnimationTarget *
+adw_property_animation_target_new (GObject *object,
+ const char *property_name)
+{
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+ g_return_val_if_fail (property_name != NULL, NULL);
+
+ return g_object_new (ADW_TYPE_PROPERTY_ANIMATION_TARGET,
+ "object", object,
+ "property-name", property_name,
+ NULL);
+}
+
+/**
+ * adw_property_animation_target_new_for_pspec:
+ * @object: an object to be animated
+ * @pspec: the param spec of the property on @object to animate
+ *
+ * Creates a new `AdwPropertyAnimationTarget` for the @pspec property on
+ * @object.
+ *
+ * Returns: new newly created `AdwPropertyAnimationTarget`
+ *
+ * Since: 1.2
+ */
+AdwAnimationTarget *
+adw_property_animation_target_new_for_pspec (GObject *object,
+ GParamSpec *pspec)
+{
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+ g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), NULL);
+
+ return g_object_new (ADW_TYPE_PROPERTY_ANIMATION_TARGET,
+ "object", object,
+ "pspec", pspec,
+ NULL);
+}
+
+/**
+ * adw_property_animation_target_get_object: (attributes org.gtk.Method.get_property=object)
+ * @self: a property animation target
+ *
+ * Gets the object animated by @self.
+ *
+ * Returns: (transfer none): the animated object
+ *
+ * Since: 1.2
+ */
+GObject *
+adw_property_animation_target_get_object (AdwPropertyAnimationTarget *self)
+{
+ g_return_val_if_fail (ADW_IS_PROPERTY_ANIMATION_TARGET (self), NULL);
+
+ return self->object;
+}
+
+/**
+ * adw_property_animation_target_get_property_name: (attributes org.gtk.Method.get_property=property-name)
+ * @self: a property animation target
+ *
+ * Gets the name of the property animated by @self.
+ *
+ * Returns: the animated property name
+ *
+ * Since: 1.2
+ */
+const char *
+adw_property_animation_target_get_property_name (AdwPropertyAnimationTarget *self)
+{
+ g_return_val_if_fail (ADW_IS_PROPERTY_ANIMATION_TARGET (self), NULL);
+
+ if (self->pspec)
+ return self->pspec->name;
+ else
+ return self->property_name;
+}
+
+/**
+ * adw_property_animation_target_get_pspec: (attributes org.gtk.Method.get_property=pspec)
+ * @self: a property animation target
+ *
+ * Gets the `GParamSpec` of the property animated by @self.
+ *
+ * Returns: (transfer none): the animated property's `GParamSpec`
+ *
+ * Since: 1.2
+ */
+GParamSpec *
+adw_property_animation_target_get_pspec (AdwPropertyAnimationTarget *self)
+{
+ g_return_val_if_fail (ADW_IS_PROPERTY_ANIMATION_TARGET (self), NULL);
+
+ return self->pspec;
+}
diff --git a/src/adw-animation-target.h b/src/adw-animation-target.h
index 6f9c4383..d85d9b52 100644
--- a/src/adw-animation-target.h
+++ b/src/adw-animation-target.h
@@ -44,4 +44,23 @@ AdwAnimationTarget *adw_callback_animation_target_new (AdwAnimationTargetFunc ca
gpointer user_data,
GDestroyNotify destroy)
G_GNUC_WARN_UNUSED_RESULT;
+#define ADW_TYPE_PROPERTY_ANIMATION_TARGET (adw_property_animation_target_get_type())
+
+ADW_AVAILABLE_IN_1_2
+GDK_DECLARE_INTERNAL_TYPE (AdwPropertyAnimationTarget, adw_property_animation_target, ADW,
PROPERTY_ANIMATION_TARGET, AdwAnimationTarget)
+
+ADW_AVAILABLE_IN_1_2
+AdwAnimationTarget *adw_property_animation_target_new (GObject *object,
+ const char *property_name)
G_GNUC_WARN_UNUSED_RESULT;
+ADW_AVAILABLE_IN_1_2
+AdwAnimationTarget *adw_property_animation_target_new_for_pspec (GObject *object,
+ GParamSpec *pspec)
G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_2
+GObject *adw_property_animation_target_get_object (AdwPropertyAnimationTarget *self);
+ADW_AVAILABLE_IN_1_2
+const char *adw_property_animation_target_get_property_name (AdwPropertyAnimationTarget *self);
+ADW_AVAILABLE_IN_1_2
+GParamSpec *adw_property_animation_target_get_pspec (AdwPropertyAnimationTarget *self);
+
G_END_DECLS
diff --git a/tests/meson.build b/tests/meson.build
index 03293446..aa57172d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -26,6 +26,7 @@ endif
test_names = [
'test-action-row',
'test-animation',
+ 'test-animation-target',
'test-application-window',
'test-avatar',
'test-bin',
diff --git a/tests/test-animation-target.c b/tests/test-animation-target.c
new file mode 100644
index 00000000..c43796d6
--- /dev/null
+++ b/tests/test-animation-target.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 George Barrett <bob bob131 so>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <adwaita.h>
+
+static void
+test_adw_property_animation_target_construct (void)
+{
+ GObject *widget = g_object_ref_sink (G_OBJECT (gtk_button_new ()));
+ AdwPropertyAnimationTarget *named_target, *pspec_target;
+ GParamSpec *target_pspec;
+ const char *target_prop_name;
+
+ named_target =
+ ADW_PROPERTY_ANIMATION_TARGET (adw_property_animation_target_new (widget, "opacity"));
+
+ target_pspec = adw_property_animation_target_get_pspec (named_target);
+ g_assert_nonnull (target_pspec);
+ g_assert_cmpstr (target_pspec->name, ==, "opacity");
+
+ target_prop_name =
+ adw_property_animation_target_get_property_name (named_target);
+ g_assert_nonnull (target_prop_name);
+ g_assert_cmpstr (target_prop_name, ==, "opacity");
+
+ pspec_target =
+ ADW_PROPERTY_ANIMATION_TARGET (adw_property_animation_target_new_for_pspec (widget, target_pspec));
+
+ g_assert_true (adw_property_animation_target_get_pspec (pspec_target) == target_pspec);
+
+ target_prop_name =
+ adw_property_animation_target_get_property_name (pspec_target);
+ g_assert_nonnull (target_prop_name);
+ g_assert_cmpstr (target_prop_name, ==, "opacity");
+
+ g_assert_finalize_object (named_target);
+ g_assert_finalize_object (pspec_target);
+ g_assert_finalize_object (widget);
+}
+
+static void
+test_adw_property_animation_target_basic (void)
+{
+ GtkWidget *widget = g_object_ref_sink (gtk_button_new ());
+ AdwAnimationTarget *target =
+ adw_property_animation_target_new (G_OBJECT (widget), "opacity");
+ AdwAnimation *animation =
+ adw_timed_animation_new (widget, 1, 0, 100, g_object_ref (target));
+
+ g_assert_cmpfloat (gtk_widget_get_opacity (widget), ==, 1);
+
+ adw_animation_play (animation);
+
+ /* Since the widget is not mapped, the animation will immediately finish */
+ g_assert_cmpfloat (gtk_widget_get_opacity (widget), ==, 0);
+
+ g_assert_finalize_object (animation);
+ g_assert_finalize_object (target);
+ g_assert_finalize_object (widget);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gtk_test_init (&argc, &argv, NULL);
+ adw_init ();
+
+ g_test_add_func("/Adwaita/PropertyAnimationTarget/construct",
+ test_adw_property_animation_target_construct);
+ g_test_add_func("/Adwaita/PropertyAnimationTarget/basic",
+ test_adw_property_animation_target_basic);
+
+ return g_test_run();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]