From f46bf09f51630d0637eac190a77cd56c04a3f381 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 18 Feb 2019 20:13:02 +0200 Subject: [PATCH 01/60] build: Build c and cpp --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index aeb4227..1674178 100644 --- a/meson.build +++ b/meson.build @@ -18,7 +18,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -project('libanimation', 'cpp', +project('libanimation', ['c', 'cpp'], version: '0.0.0', default_options : ['cpp_std=c++17'], license: 'LGPL2+', From 7482384755172eb6cceb36b92e3eb185a0455c64 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:51:48 +0300 Subject: [PATCH 02/60] meson: Bump meson version to 0.42.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1674178..6db1b0a 100644 --- a/meson.build +++ b/meson.build @@ -22,7 +22,7 @@ project('libanimation', ['c', 'cpp'], version: '0.0.0', default_options : ['cpp_std=c++17'], license: 'LGPL2+', - meson_version: '>= 0.40.0') + meson_version: '>= 0.42.0') gtest_dep = dependency('gtest', required: false) gtest_main_dep = dependency('gtest', main: true, required: false) From b8d2317e09bb6fbcfed6e96022c2dbe47ad1898b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 18 Feb 2019 20:12:46 +0200 Subject: [PATCH 03/60] glib: Save Animation GirTarget in variable --- animation-glib/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index fb997bb..83744b3 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -57,7 +57,7 @@ animation_glib_dep = declare_dependency( introspection_sources = [ animation_glib_introspectable_sources, animation_glib_headers ] gnome = import('gnome') -gnome.generate_gir( +animation_glib_gir = gnome.generate_gir( animation_glib_lib, extra_args: ['--warn-all', '--warn-error'], identifier_prefix: 'Animation', @@ -68,7 +68,7 @@ gnome.generate_gir( nsversion: api_version, sources: introspection_sources, symbol_prefix: 'animation' -) +)[0] pkg = import('pkgconfig') pkg.generate( From 7aff42f43c6132f23d2578c36b8040d1a05a26b3 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Thu, 23 Aug 2018 06:25:51 +0800 Subject: [PATCH 04/60] geometry: Add operator== for point types If we know that two points are going to be of the same type, it is convenient to be able to compare them with the == operator as opposed to having to use agd::equals --- animation/geometry_traits.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/animation/geometry_traits.h b/animation/geometry_traits.h index 5791657..3b4e27b 100644 --- a/animation/geometry_traits.h +++ b/animation/geometry_traits.h @@ -377,5 +377,10 @@ namespace animation } } + template + inline bool operator== (T const &l, U const &r) + { + return dimension::equals (l, r); + } } } From cf74b6659a1d3aea3838b492850fb3b61facfd03 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 8 Jul 2019 15:41:31 +0300 Subject: [PATCH 05/60] animation: Add necessary includes to animation/geometry.h --- animation/geometry.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/animation/geometry.h b/animation/geometry.h index 51413d8..389c9b9 100644 --- a/animation/geometry.h +++ b/animation/geometry.h @@ -22,6 +22,10 @@ */ #pragma once +#include +#include +#include + #include namespace animation From c6bf32830cefe3cd54c44bf126e70610f5bc7eeb Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 5 Aug 2018 08:27:45 +0800 Subject: [PATCH 06/60] animation: Add box type The "box" type represents a simple perpendicular quadrilateral in 2D space given by its top left and bottom right co-ordinates (eg, x1, y1, x2, y2). --- animation-glib/box.cpp | 40 ++++++++++++++++++++++++++++++++++++++ animation-glib/box.h | 39 +++++++++++++++++++++++++++++++++++++ animation-glib/meson.build | 10 ++++++++-- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 animation-glib/box.cpp create mode 100644 animation-glib/box.h diff --git a/animation-glib/box.cpp b/animation-glib/box.cpp new file mode 100644 index 0000000..00af7b3 --- /dev/null +++ b/animation-glib/box.cpp @@ -0,0 +1,40 @@ +/* + * animation-glib/box.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "wobbly" textures, box + * type implementation. + */ + +#include + +static gpointer +animation_box_copy (gpointer ptr) +{ + AnimationBox *src = reinterpret_cast (ptr); + AnimationBox *dst = g_new0 (AnimationBox, 1); + + *dst = *src; + + return reinterpret_cast (dst); +} + +G_DEFINE_BOXED_TYPE (AnimationBox, + animation_box, + animation_box_copy, + g_free); diff --git a/animation-glib/box.h b/animation-glib/box.h new file mode 100644 index 0000000..b583475 --- /dev/null +++ b/animation-glib/box.h @@ -0,0 +1,39 @@ +/* + * animation-glib/box.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "wobbly" textures, 2D box type. + */ +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +typedef struct { + AnimationVector top_left; + AnimationVector bottom_right; +} AnimationBox; + +#define ANIMATION_TYPE_BOX animation_box_get_type () + +GType animation_box_get_type (); + +G_END_DECLS diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 83744b3..14451e7 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -20,8 +20,14 @@ api_version = '0' -animation_glib_toplevel_headers = ['vector.h'] -animation_glib_toplevel_introspectable_sources = ['vector.cpp'] +animation_glib_toplevel_headers = [ + 'box.h', + 'vector.h' +] +animation_glib_toplevel_introspectable_sources = [ + 'box.cpp', + 'vector.cpp' +] animation_glib_introspectable_sources = [] animation_glib_private_sources = [] From 521d8fa8e0b2411c37f901e4d0544655c16e4e42 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 8 Jul 2019 13:32:44 +0300 Subject: [PATCH 07/60] animation: Add animation::BoxQuery interface When animating from one position to another, it is possible that some things could change underneath us. For instance, the target position of the surface may be changed by the user while the animation is in progress. In this case, the animation needs to adapt to this change by allowing the caller some mechanism to transparently change the target box tha the surface is animating to. This can be done by overriding the animation::BoxQuery class and calling the Update() method as appropriate, which will update the saved geometry of the target position. For example, in a windowing system, one might set up a listener for surface geometry events in their animation::BoxQuery subclass, which then calls Update() when the geometry has been changed. This obviates the need for the caller to explicitly update all animations. --- animation-glib/meson.build | 1 + .../query/geometry-query-internal.cpp | 74 +++++++++ animation-glib/query/geometry-query-private.h | 62 +++++++ animation-glib/query/geometry-query.cpp | 153 ++++++++++++++++++ animation-glib/query/geometry-query.h | 43 +++++ animation-glib/query/meson.build | 28 ++++ animation/meson.build | 1 + animation/query/geometry_query.cpp | 27 ++++ animation/query/geometry_query.h | 118 ++++++++++++++ animation/query/meson.build | 29 ++++ 10 files changed, 536 insertions(+) create mode 100644 animation-glib/query/geometry-query-internal.cpp create mode 100644 animation-glib/query/geometry-query-private.h create mode 100644 animation-glib/query/geometry-query.cpp create mode 100644 animation-glib/query/geometry-query.h create mode 100644 animation-glib/query/meson.build create mode 100644 animation/query/geometry_query.cpp create mode 100644 animation/query/geometry_query.h create mode 100644 animation/query/meson.build diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 14451e7..96172d6 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -34,6 +34,7 @@ animation_glib_private_sources = [] animation_glib_headers = [] animation_glib_headers_subdir = 'animation-glib' +subdir('query') subdir('wobbly') animation_glib_introspectable_sources += files(animation_glib_toplevel_introspectable_sources) diff --git a/animation-glib/query/geometry-query-internal.cpp b/animation-glib/query/geometry-query-internal.cpp new file mode 100644 index 0000000..fad0557 --- /dev/null +++ b/animation-glib/query/geometry-query-internal.cpp @@ -0,0 +1,74 @@ +/* + * animation-glib/query/geometry-query-internal.cpp + * + * Copyright 2019 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Wrapper for the AnimationBoxQuery object into a C++ object satisfying + * the animation::BoxQuery interface, implementation. + */ + +#include +#include +#include + +namespace ag = animation::glib; + +namespace animation +{ + namespace glib + { + struct BoxQueryWrapper::Private + { + Private (AnimationBoxQuery *); + ~Private (); + + AnimationBoxQuery *query; + }; + } +} + +ag::BoxQueryWrapper::Private::Private (AnimationBoxQuery *query) : + query (ANIMATION_BOX_QUERY (g_object_ref (query))) +{ +} + +ag::BoxQueryWrapper::Private::~Private () +{ + g_clear_object (&query); +} + +animation::Box const & +ag::BoxQueryWrapper::Geometry () const +{ + return animation_box_query_get_storage (priv->query).Geometry (); +} + +AnimationBoxQuery * +ag::BoxQueryWrapper::BoxQuery () const +{ + return priv->query; +} + +ag::BoxQueryWrapper::BoxQueryWrapper (AnimationBoxQuery *query) : + priv (std::make_unique (query)) +{ +} + +ag::BoxQueryWrapper::~BoxQueryWrapper () +{ +} + diff --git a/animation-glib/query/geometry-query-private.h b/animation-glib/query/geometry-query-private.h new file mode 100644 index 0000000..bad90e0 --- /dev/null +++ b/animation-glib/query/geometry-query-private.h @@ -0,0 +1,62 @@ +/* + * animation-glib/query/geometry-query-private.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Wrapper for the AnimationBoxQuery object into a C++ object satisfying + * the animation::BoxQuery interface. + */ +#pragma once + +#include + +#include + +#include +#include +#include + +#ifdef __cplusplus +namespace animation +{ + namespace glib + { + class BoxQueryWrapper : + public BoxQuery + { + public: + + BoxQueryWrapper (AnimationBoxQuery *); + ~BoxQueryWrapper (); + + geometry::Box const & Geometry () const override; + + AnimationBoxQuery * BoxQuery () const; + + private: + + class Private; + std::unique_ptr priv; + }; + } +} +#endif + +G_BEGIN_DECLS + +animation::BoxQueryStorage const & +animation_box_query_get_storage (AnimationBoxQuery *query); + +G_END_DECLS diff --git a/animation-glib/query/geometry-query.cpp b/animation-glib/query/geometry-query.cpp new file mode 100644 index 0000000..d90ae3a --- /dev/null +++ b/animation-glib/query/geometry-query.cpp @@ -0,0 +1,153 @@ +/* + * animation-glib/query/geometry-query.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject interface for the BoxQuery class. + */ + +#include + +#include +#include +#include +#include +#include + +namespace agd = animation::geometry::dimension; + +typedef struct _AnimationBoxQueryPrivate +{ + animation::BoxQueryStorage query; +} AnimationBoxQueryPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationBoxQuery, + animation_box_query, + G_TYPE_OBJECT) + +/** + * animation_box_query_update: + * @query: An #AnimationBoxQuery + * @box: An #AnimationBox + * + * Update the internal geometry in this geometry query. + */ +void +animation_box_query_update (AnimationBoxQuery *query, + const AnimationBox *box) +{ + AnimationBoxQueryPrivate *priv = + reinterpret_cast (animation_box_query_get_instance_private (query)); + + priv->query.Update (animation::Box (animation::Point (box->top_left.x, + box->top_left.y), + animation::Point (box->bottom_right.x, + box->bottom_right.y))); +} + +/** + * animation_box_query_get_storage: + * @query: An #AnimationBoxQuery + * + * Get the underlying storage for the #AnimationBoxQuery. This is + * an internal method that is used by animation::glib::BoxQueryWrapper + * to directly get the geometry referred to in the storage. + * + * Return: An animation::BoxQueryStorage used by this #AnimationBoxQuery. + */ +animation::BoxQueryStorage const & +animation_box_query_get_storage (AnimationBoxQuery *query) +{ + AnimationBoxQueryPrivate *priv = + reinterpret_cast (animation_box_query_get_instance_private (query)); + + return priv->query; +} + +/** + * animation_box_query_get_geometry: + * @query: An #AnimationBoxQuery + * @out_box: (out caller-allocates): An #AnimationBox to write the return value to. + * + * Get the currently stored geometry as an AnimationBox. + */ +void +animation_box_query_geometry (AnimationBoxQuery *query, + AnimationBox *out_box) +{ + AnimationBoxQueryPrivate *priv = + reinterpret_cast (animation_box_query_get_instance_private (query)); + + g_return_if_fail (out_box != NULL); + + auto box = priv->query.Geometry (); + + out_box->top_left.x = agd::get <0> (box.topLeft ()); + out_box->top_left.y = agd::get <1> (box.topLeft ()); + out_box->bottom_right.x = agd::get <0> (box.bottomRight ()); + out_box->bottom_right.y = agd::get <1> (box.bottomRight ()); +} + +static void +animation_box_query_finalize (GObject *object) +{ + AnimationBoxQuery *query = ANIMATION_BOX_QUERY (object); + AnimationBoxQueryPrivate *priv = + reinterpret_cast (animation_box_query_get_instance_private (query)); + + /* Explicitly call destructor, which will release the pointer + * and return the memory to an uninitialized state */ + priv->query.~BoxQueryStorage(); + + G_OBJECT_CLASS (animation_box_query_parent_class)->finalize (object); +} + +static void +animation_box_query_init (AnimationBoxQuery *query) +{ + AnimationBoxQueryPrivate *priv = + reinterpret_cast (animation_box_query_get_instance_private (query)); + + new (&priv->query) animation::BoxQueryStorage ( + animation::Box ( + animation::Point (0, 0), + animation::Point (1, 1) + ) + ); +} + +static void +animation_box_query_class_init (AnimationBoxQueryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = animation_box_query_finalize; +} + +/** + * animation_box_query_new: + * + * Create a new AnimationBoxQuery * with default-initialized geometry. + * + * You probably don't want to directly use this, and instead use a derived class + * class constructor. + * + * Returns: A new #AnimationBoxQuery + */ +AnimationBoxQuery * +animation_box_query_new (void) +{ + return ANIMATION_BOX_QUERY (g_object_new (ANIMATION_TYPE_BOX_QUERY, NULL)); +} diff --git a/animation-glib/query/geometry-query.h b/animation-glib/query/geometry-query.h new file mode 100644 index 0000000..8219a75 --- /dev/null +++ b/animation-glib/query/geometry-query.h @@ -0,0 +1,43 @@ +/* + * animation-glib/query/geometry-query.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for the BoxQuery class. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_BOX_QUERY animation_box_query_get_type () +G_DECLARE_DERIVABLE_TYPE (AnimationBoxQuery, animation_box_query, ANIMATION, BOX_QUERY, GObject) + +struct _AnimationBoxQueryClass { + GObjectClass parent_class; +}; + +void animation_box_query_update (AnimationBoxQuery *query, + const AnimationBox *box); + +AnimationBox * animation_box_query_geometry (AnimationBoxQuery *query); + +AnimationBoxQuery * animation_box_query_new (void); + +G_END_DECLS diff --git a/animation-glib/query/meson.build b/animation-glib/query/meson.build new file mode 100644 index 0000000..7826ee9 --- /dev/null +++ b/animation-glib/query/meson.build @@ -0,0 +1,28 @@ +# /animation-glib/query/meson.build +# +# Build the libanimation library (box query GLib binding component). +# +# See /LICENCE.md for Copyright information. + +geometry_query_introspectable_sources = files([ + 'geometry-query.cpp' +]) + +geometry_query_headers = files([ + 'geometry-query.h' +]) + +geometry_query_private_headers = files([ + 'geometry-query-private.h' +]) + +geometry_query_private_sources = files([ + 'geometry-query-internal.cpp' +]) + +animation_glib_introspectable_sources += geometry_query_introspectable_sources +animation_glib_headers += geometry_query_headers +animation_glib_private_headers += geometry_query_private_headers +animation_glib_private_sources += geometry_query_private_sources + +install_headers(magiclamp_headers, subdir: join_paths(animation_glib_headers_subdir, 'query')) diff --git a/animation/meson.build b/animation/meson.build index 706d5c1..f12056f 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -24,6 +24,7 @@ animation_sources = [] animation_headers = [] animation_headers_subdir = 'animation' +subdir('query') subdir('wobbly') animation_lib = shared_library( diff --git a/animation/query/geometry_query.cpp b/animation/query/geometry_query.cpp new file mode 100644 index 0000000..1e22012 --- /dev/null +++ b/animation/query/geometry_query.cpp @@ -0,0 +1,27 @@ + /* + * animation/query/geometry_query.cpp + * + * Copyright 2019 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Implementation details for geometry cache. + */ + +#include "animation/query/geometry_query.h" + +animation::BoxQuery::~BoxQuery () +{ +} diff --git a/animation/query/geometry_query.h b/animation/query/geometry_query.h new file mode 100644 index 0000000..d04f267 --- /dev/null +++ b/animation/query/geometry_query.h @@ -0,0 +1,118 @@ +/* + * animation/query/geometry_query.h + * + * Copyright 2019 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for geometry cache. A function is kept to query the + * geometry of the box in case it is invalidated. The idea behind + * this class to allow the animations to have an up to date view of + * of the relevant geometry at all times, but avoid expensive calls + * to re-query the geometry on every frame (for instance, the true + * geometry at any point might only be known by some javascript calling + * code - we allow it to signal on geometry changes that the geometry + * has been updated while the javascript code is being executed, as opposed + * to the C++ code calling back into the javascript code to determine + * the updated geometry on every frame). + */ + +#pragma once + +#include + +#include + +namespace animation +{ + template + class BoxQuery + { + public: + + typedef std::shared_ptr > Shared; + + virtual ~BoxQuery () {}; + virtual geometry::Box const & Geometry () const = 0; + }; + + template + class BoxQueryStorage : + public BoxQuery + { + public: + + typedef std::shared_ptr > Shared; + + BoxQueryStorage (geometry::Box const &box); + BoxQueryStorage (geometry::Box &&box); + + void Update (geometry::Box const &box); + void Update (geometry::Box &&box); + geometry::Box const & Geometry () const override; + + private: + + /* Not copyable or movable */ + BoxQueryStorage (BoxQueryStorage const &query) = delete; + BoxQueryStorage (BoxQueryStorage &&query) = delete; + BoxQueryStorage & operator= (BoxQueryStorage const &query) = delete; + BoxQueryStorage & operator= (BoxQueryStorage &&query) = delete; + + geometry::Box box; + }; + + template + BoxQueryStorage ::BoxQueryStorage (geometry::Box const &box) : + box(box) + { + } + + template + BoxQueryStorage ::BoxQueryStorage (geometry::Box &&box) : + box (std::move (box)) + { + } + + template + void BoxQueryStorage ::Update (geometry::Box const &box) + { + this->box = box; + } + + template + void BoxQueryStorage ::Update (geometry::Box &&box) + { + this->box = std::move(box); + } + + template + geometry::Box const & BoxQueryStorage ::Geometry () const + { + return box; + } + + template + typename animation::BoxQuery ::Shared MakeStaticBoxQuery (animation::Box &&box) + { + return std::make_shared > (std::move (box)); + } + + template + typename animation::BoxQuery ::Shared MakeStaticBoxQuery (animation::Box const &box) + { + return std::make_shared > (box); + } +} diff --git a/animation/query/meson.build b/animation/query/meson.build new file mode 100644 index 0000000..85ec262 --- /dev/null +++ b/animation/query/meson.build @@ -0,0 +1,29 @@ +# /animation/query/meson.build +# +# Meson build file for libanimation (geometry query component). +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (geometry query component). + +geometry_query_headers = files([ + 'geometry_query.h' +]) + +animation_headers += geometry_query_headers + +install_headers(glide_headers, subdir: join_paths(animation_headers_subdir, 'query')) From 62c54775d7360413d4f5fdb6804fe9e9e1587d5b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 22 Aug 2018 12:04:49 +0800 Subject: [PATCH 08/60] tests: Add ostream operator for Vector4D This is necessary in order to use it with some of the testing operators (like agd::Equals or agd::AlmostEq), since those testing operators use operator<< in order to print out the contents of the type in the event of a matching failure. --- tests/ostream_point_operator.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/ostream_point_operator.h b/tests/ostream_point_operator.h index a2cac5b..9705f2f 100644 --- a/tests/ostream_point_operator.h +++ b/tests/ostream_point_operator.h @@ -54,5 +54,21 @@ namespace animation agd::assign (point, p); return lhs << point; } + + inline std::ostream & + operator<< (std::ostream &lhs, Vector4D const &p) + { + namespace agd = animation::geometry::dimension; + + return lhs << std::setprecision (10) + << "x: " + << agd::get <0> (p) + << " y: " + << agd::get <1> (p) + << " z: " + << agd::get <2> (p) + << " w: " + << agd::get <3> (p); + } } } From 0cac2813edc418d7b33869ae6fcab4ba4b63c84d Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sat, 4 Aug 2018 23:22:18 +0800 Subject: [PATCH 09/60] build: Add header-dependency on glm We're going to do affine transformations and glm is a particularly well respected library for things like that. Note that the dependency is a submodule, so you'll need to fetch and pull submodules before building. --- .gitmodules | 3 +++ animation/meson.build | 2 +- animation/third_party/glm | 1 + meson.build | 1 + tests/meson.build | 2 +- 5 files changed, 7 insertions(+), 2 deletions(-) create mode 160000 animation/third_party/glm diff --git a/.gitmodules b/.gitmodules index e69de29..018c244 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "animation/third_party/glm"] + path = animation/third_party/glm + url = git://github.com/g-truc/glm diff --git a/animation/meson.build b/animation/meson.build index f12056f..a61edb8 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -32,7 +32,7 @@ animation_lib = shared_library( animation_sources, soversion: api_version, install: true, - include_directories: [ animation_inc ] + include_directories: [ animation_inc, glm_inc ] ) animation_dep = declare_dependency( diff --git a/animation/third_party/glm b/animation/third_party/glm new file mode 160000 index 0000000..fe7c7b5 --- /dev/null +++ b/animation/third_party/glm @@ -0,0 +1 @@ +Subproject commit fe7c7b5ac15ba8d5c9c45984831dc2e830726b33 diff --git a/meson.build b/meson.build index 6db1b0a..c1c6695 100644 --- a/meson.build +++ b/meson.build @@ -36,6 +36,7 @@ if not gtest_dep.found() or not gtest_main_dep.found() or not gmock_dep.found() endif animation_inc = include_directories('.') +glm_inc = include_directories(join_paths('animation', 'third_party', 'glm')) tests_inc = include_directories('tests') subdir('animation') diff --git a/tests/meson.build b/tests/meson.build index 9d59aa9..3811d9e 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -47,7 +47,7 @@ animation_test_executable = executable( animation_dep, animation_glib_dep ], - include_directories: [ tests_inc ] + include_directories: [ glm_inc, tests_inc ] ) test('animation_test', animation_test_executable) From d0494efe216634c952e42f7435f223bd7b51198c Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:18:44 +0300 Subject: [PATCH 10/60] tests: Add JS tests and common JS tests utils These include matrixMultiplyVector (multiplying a 4D matrix with a 4D vector), BoxWrapper (wraps a Animation.Box in an Animation.BoxQuery), and applyCallerTranslation --- tests/js/common.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/js/common.js diff --git a/tests/js/common.js b/tests/js/common.js new file mode 100644 index 0000000..597c5d3 --- /dev/null +++ b/tests/js/common.js @@ -0,0 +1,52 @@ +/* + * /tests/js/common.js + * + * Helper functions for javascript tests for animations. + * + * Copyright (C) 2019 Sam Spilsbury. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +const { Animation, GLib, GObject } = imports.gi; + +var BoxWrapper = GObject.registerClass({ + Properties: { + 'box': GObject.ParamSpec.boxed('box', + 'AnimationBox', + 'AnimationBox to animate to (static)', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Animation.Box) + } +}, class BoxWrapper extends Animation.BoxQuery { + _init(props) { + super._init(props); + this.update(this.box); + } +}); + +function multiplyMatrixVector(mat, vec) { + return [ + mat[0] * vec[0] + mat[4] * vec[1] + mat[8] * vec[2] + mat[12] * vec[3], + mat[1] * vec[0] + mat[5] * vec[1] + mat[9] * vec[2] + mat[13] * vec[3], + mat[2] * vec[0] + mat[6] * vec[1] + mat[10] * vec[2] + mat[14] * vec[3], + mat[3] * vec[0] + mat[7] * vec[1] + mat[11] * vec[2] + mat[15] * vec[3] + ] +} + +function applyCallerTranslation(vector, x, y) { + return [vector[0] + x, vector[1] + y, 0, 1]; +} + From 026ad96e4da8d3cb1e778f30b77a60342cd74738 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 21:56:00 +0300 Subject: [PATCH 11/60] animation-glib: Use files() in meson.build --- animation-glib/meson.build | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 96172d6..42863c9 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -20,25 +20,25 @@ api_version = '0' -animation_glib_toplevel_headers = [ +animation_glib_toplevel_headers = files([ 'box.h', 'vector.h' -] -animation_glib_toplevel_introspectable_sources = [ +]) +animation_glib_toplevel_introspectable_sources = files([ 'box.cpp', 'vector.cpp' -] +]) -animation_glib_introspectable_sources = [] -animation_glib_private_sources = [] -animation_glib_headers = [] +animation_glib_introspectable_sources = files([]) +animation_glib_private_sources = files([]) +animation_glib_headers = files([]) animation_glib_headers_subdir = 'animation-glib' subdir('query') subdir('wobbly') -animation_glib_introspectable_sources += files(animation_glib_toplevel_introspectable_sources) -animation_glib_headers += files(animation_glib_toplevel_headers) +animation_glib_introspectable_sources += animation_glib_toplevel_introspectable_sources +animation_glib_headers += animation_glib_toplevel_headers install_headers(animation_glib_toplevel_headers, subdir: animation_glib_headers_subdir) From 8bf7b227037cce1a6f5c19d5847a18ca9766120a Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 21:56:28 +0300 Subject: [PATCH 12/60] animation: Add concept of a "stepper" Steppers encapsulate some progress change within the animation. The typical case is linear stepping, which just interpolates from 0 to 1. However, another case is reverse stepping which is composed by 1.0f - linear (ms). Future steppers can be added to add all sorts of easing parameters. https://phabricator.endlessm.com/T23543 --- animation-glib/meson.build | 1 + animation-glib/stepper/linear.cpp | 136 +++++++++++++++ animation-glib/stepper/linear.h | 36 ++++ animation-glib/stepper/meson.build | 45 +++++ animation-glib/stepper/reverse.cpp | 148 ++++++++++++++++ animation-glib/stepper/reverse.h | 36 ++++ .../stepper/stepper-holder-internal.h | 33 ++++ animation-glib/stepper/stepper-holder.cpp | 113 +++++++++++++ animation-glib/stepper/stepper-holder.h | 34 ++++ animation-glib/stepper/stepper-internal.cpp | 71 ++++++++ animation-glib/stepper/stepper-internal.h | 61 +++++++ animation-glib/stepper/stepper-wrapper.cpp | 160 ++++++++++++++++++ animation-glib/stepper/stepper-wrapper.h | 36 ++++ animation-glib/stepper/stepper.cpp | 44 +++++ animation-glib/stepper/stepper.h | 44 +++++ animation/meson.build | 1 + animation/stepper/linear.cpp | 59 +++++++ animation/stepper/linear.h | 63 +++++++ animation/stepper/meson.build | 38 +++++ animation/stepper/reverse.cpp | 57 +++++++ animation/stepper/reverse.h | 60 +++++++ animation/stepper/stepper.cpp | 32 ++++ animation/stepper/stepper.h | 53 ++++++ 23 files changed, 1361 insertions(+) create mode 100644 animation-glib/stepper/linear.cpp create mode 100644 animation-glib/stepper/linear.h create mode 100644 animation-glib/stepper/meson.build create mode 100644 animation-glib/stepper/reverse.cpp create mode 100644 animation-glib/stepper/reverse.h create mode 100644 animation-glib/stepper/stepper-holder-internal.h create mode 100644 animation-glib/stepper/stepper-holder.cpp create mode 100644 animation-glib/stepper/stepper-holder.h create mode 100644 animation-glib/stepper/stepper-internal.cpp create mode 100644 animation-glib/stepper/stepper-internal.h create mode 100644 animation-glib/stepper/stepper-wrapper.cpp create mode 100644 animation-glib/stepper/stepper-wrapper.h create mode 100644 animation-glib/stepper/stepper.cpp create mode 100644 animation-glib/stepper/stepper.h create mode 100644 animation/stepper/linear.cpp create mode 100644 animation/stepper/linear.h create mode 100644 animation/stepper/meson.build create mode 100644 animation/stepper/reverse.cpp create mode 100644 animation/stepper/reverse.h create mode 100644 animation/stepper/stepper.cpp create mode 100644 animation/stepper/stepper.h diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 42863c9..8aba56f 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -35,6 +35,7 @@ animation_glib_headers = files([]) animation_glib_headers_subdir = 'animation-glib' subdir('query') +subdir('stepper') subdir('wobbly') animation_glib_introspectable_sources += animation_glib_toplevel_introspectable_sources diff --git a/animation-glib/stepper/linear.cpp b/animation-glib/stepper/linear.cpp new file mode 100644 index 0000000..3852fcd --- /dev/null +++ b/animation-glib/stepper/linear.cpp @@ -0,0 +1,136 @@ +/* + * animation-glib/stepper/linear.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject wrapper for linear stepper. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +namespace as = animation::stepper; + +struct _AnimationLinearStepper +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE (AnimationLinearStepper, + animation_linear_stepper, + ANIMATION_TYPE_STEPPER_HOLDER) + +enum { + PROP_0, + PROP_LENGTH, + NPROPS +}; + +static GParamSpec *animation_linear_stepper_properties[NPROPS]; + +static void +animation_linear_stepper_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + + switch (prop_id) + { + case PROP_LENGTH: + std::static_pointer_cast (animation_stepper_holder_get_ptr (holder))->Length (g_value_get_uint (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_linear_stepper_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + + switch (prop_id) + { + case PROP_LENGTH: + g_value_set_uint (value, + std::static_pointer_cast (animation_stepper_holder_get_ptr (holder))->Length ()); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_linear_stepper_init (AnimationLinearStepper *stepper) +{ + static const unsigned int DefaultAnimationLength = 300; + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (stepper); + + animation_stepper_holder_set_ptr (holder, std::make_shared (DefaultAnimationLength)); +} + +static void +animation_linear_stepper_class_init (AnimationLinearStepperClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_linear_stepper_get_property; + object_class->set_property = animation_linear_stepper_set_property; + + animation_linear_stepper_properties[PROP_LENGTH] = + g_param_spec_uint ("length", + "Length", + "How long the animation lasts", + 1, + 5000, + 300, + static_cast (G_PARAM_READWRITE)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_linear_stepper_properties); +} + +/** + * animation_linear_stepper_new: + * @length: Length of the transition in milliseconds. + * + * Return a new #AnimationStepper which linearly increments progress + * every time the step() method is called on it. + * + * Returns: (transfer full): An #AnimationLinearStepper + * implementation of #AnimationStepper + */ +AnimationStepper * +animation_linear_stepper_new (unsigned int length) +{ + return ANIMATION_STEPPER (g_object_new (ANIMATION_TYPE_LINEAR_STEPPER, + "length", length, + NULL)); +} diff --git a/animation-glib/stepper/linear.h b/animation-glib/stepper/linear.h new file mode 100644 index 0000000..1c83c5f --- /dev/null +++ b/animation-glib/stepper/linear.h @@ -0,0 +1,36 @@ +/* + * animation-glib/stepper/linear.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject class for linear stepper. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_LINEAR_STEPPER animation_linear_stepper_get_type () +G_DECLARE_FINAL_TYPE (AnimationLinearStepper, animation_linear_stepper, ANIMATION, LINEAR_STEPPER, AnimationStepperHolder) + +AnimationStepper * animation_linear_stepper_new (unsigned int length); + +G_END_DECLS diff --git a/animation-glib/stepper/meson.build b/animation-glib/stepper/meson.build new file mode 100644 index 0000000..abb1907 --- /dev/null +++ b/animation-glib/stepper/meson.build @@ -0,0 +1,45 @@ +# /animation/stepper/meson.build +# +# Meson build file for libanimation stepper GObject classes. +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (stepper base component) + +stepper_introspectable_sources = files([ + 'linear.cpp', + 'reverse.cpp', + 'stepper.cpp', + 'stepper-holder.cpp' +]) +stepper_private_sources = files([ + 'stepper-internal.cpp' +]) + +stepper_headers = files([ + 'linear.h', + 'reverse.h', + 'stepper.h', + 'stepper-holder.h', + 'stepper-internal.h' +]) + +animation_glib_introspectable_sources += stepper_introspectable_sources +animation_glib_private_sources += stepper_private_sources +animation_glib_headers += stepper_headers + +install_headers(stepper_headers, subdir: join_paths(animation_glib_headers_subdir, 'stepper')) diff --git a/animation-glib/stepper/reverse.cpp b/animation-glib/stepper/reverse.cpp new file mode 100644 index 0000000..d47ee05 --- /dev/null +++ b/animation-glib/stepper/reverse.cpp @@ -0,0 +1,148 @@ +/* + * animation-glib/stepper/reverse.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject wrapper for reverse stepper. + */ + +#include +#include + +#include +#include +#include +#include + +namespace as = animation::stepper; +namespace asg = animation::stepper::glib; + +struct _AnimationReverseStepper +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE (AnimationReverseStepper, + animation_reverse_stepper, + ANIMATION_TYPE_STEPPER_HOLDER) + +enum { + PROP_0, + PROP_BASE_STEPPER, + NPROPS +}; + +static GParamSpec *animation_reverse_stepper_properties[NPROPS]; + +static std::shared_ptr +construct_reverse_stepper_wrapper (AnimationStepper *base_stepper) +{ + unsigned int const DefaultAnimationLength = 300; + + if (base_stepper != nullptr) + return std::make_shared (std::make_shared (base_stepper)); + + return std::make_shared (std::make_shared (DefaultAnimationLength)); +} + +static void +animation_reverse_stepper_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + + switch (prop_id) + { + case PROP_BASE_STEPPER: + animation_stepper_holder_set_ptr (holder, + construct_reverse_stepper_wrapper (ANIMATION_STEPPER (g_value_get_object (value)))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_reverse_stepper_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + + switch (prop_id) + { + case PROP_BASE_STEPPER: + g_value_set_object (value, + G_OBJECT (std::static_pointer_cast (animation_stepper_holder_get_ptr (holder))->BaseStepper ())); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_reverse_stepper_dispose (GObject *object) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + + animation_stepper_holder_set_ptr (holder, as::Stepper::Shared ()); +} + +static void +animation_reverse_stepper_init (AnimationReverseStepper *reverse_stepper) +{ +} + +static void +animation_reverse_stepper_class_init (AnimationReverseStepperClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_reverse_stepper_get_property; + object_class->set_property = animation_reverse_stepper_set_property; + object_class->dispose = animation_reverse_stepper_dispose; + + animation_reverse_stepper_properties[PROP_BASE_STEPPER] = + g_param_spec_object ("base-stepper", + "Base Stepper", + "Stepper to reverse", + ANIMATION_TYPE_STEPPER, + static_cast (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_reverse_stepper_properties); +} + +/** + * animation_reverse_stepper_new: + * @base_stepper: The stepper to reverse. + * + * Return a new #AnimationStepper which runs @base_stepper in reverse. + * + * Returns: (transfer full): An #AnimationReverseStepper + * implementation of #AnimationStepper + */ +AnimationStepper * +animation_reverse_stepper_new (AnimationStepper *base_stepper) +{ + return ANIMATION_STEPPER (g_object_new (ANIMATION_TYPE_REVERSE_STEPPER, + "base-stepper", base_stepper, + NULL)); +} diff --git a/animation-glib/stepper/reverse.h b/animation-glib/stepper/reverse.h new file mode 100644 index 0000000..0a4f625 --- /dev/null +++ b/animation-glib/stepper/reverse.h @@ -0,0 +1,36 @@ +/* + * animation-glib/stepper/reverse.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject class for reverse stepper. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_REVERSE_STEPPER animation_reverse_stepper_get_type () +G_DECLARE_FINAL_TYPE (AnimationReverseStepper, animation_reverse_stepper, ANIMATION, REVERSE_STEPPER, AnimationStepperHolder) + +AnimationStepper * animation_reverse_stepper_new (AnimationStepper *base_stepper); + +G_END_DECLS diff --git a/animation-glib/stepper/stepper-holder-internal.h b/animation-glib/stepper/stepper-holder-internal.h new file mode 100644 index 0000000..f5063d0 --- /dev/null +++ b/animation-glib/stepper/stepper-holder-internal.h @@ -0,0 +1,33 @@ +/* + * animation-glib/stepper/stepper-holder-internal.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Internally public methods for GObject stepper holder implementation. + */ +#pragma once + +#include + +#include +#include + + +animation::stepper::Stepper::Shared animation_stepper_holder_get_ptr (AnimationStepperHolder *holder); + +void animation_stepper_holder_set_ptr (AnimationStepperHolder *holder, + animation::stepper::Stepper::Shared const &ptr); diff --git a/animation-glib/stepper/stepper-holder.cpp b/animation-glib/stepper/stepper-holder.cpp new file mode 100644 index 0000000..968bea2 --- /dev/null +++ b/animation-glib/stepper/stepper-holder.cpp @@ -0,0 +1,113 @@ +/* + * animation-glib/stepper/stepper-holder.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for steppers. Copies the stepper + * passed to it in its construct parameters. + */ + +#include +#include + +#include +#include + +namespace as = animation::stepper; + +struct _AnimationStepperHolder +{ + GObject parent_instance; +}; + +typedef struct _AnimationStepperHolderPrivate +{ + as::Stepper::Shared stepper; +} AnimationStepperHolderPrivate; + +static void animation_stepper_iface_init (AnimationStepperInterface *stepper_iface); + +G_DEFINE_TYPE_WITH_CODE (AnimationStepperHolder, + animation_stepper_holder, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (ANIMATION_TYPE_STEPPER, animation_stepper_iface_init) + G_ADD_PRIVATE (AnimationStepperHolder)) + +static float +animation_stepper_holder_step (AnimationStepper *stepper, + unsigned int ms) +{ + AnimationStepperHolderPrivate *priv = + reinterpret_cast (animation_stepper_holder_get_instance_private (ANIMATION_STEPPER_HOLDER (stepper))); + + return (*priv->stepper) (ms); +} + +as::Stepper::Shared +animation_stepper_holder_get_ptr (AnimationStepperHolder *holder) +{ + AnimationStepperHolderPrivate *priv = + reinterpret_cast (animation_stepper_holder_get_instance_private (holder)); + + return priv->stepper; +} + +void +animation_stepper_holder_set_ptr (AnimationStepperHolder *holder, + as::Stepper::Shared const &ptr) +{ + AnimationStepperHolderPrivate *priv = + reinterpret_cast (animation_stepper_holder_get_instance_private (holder)); + + priv->stepper = ptr; +} + +static void +animation_stepper_holder_finalize (GObject *object) +{ + AnimationStepperHolder *holder = ANIMATION_STEPPER_HOLDER (object); + AnimationStepperHolderPrivate *priv = + reinterpret_cast (animation_stepper_holder_get_instance_private (holder)); + + using Ptr = as::Stepper::Shared; + priv->stepper.~Ptr (); + + G_OBJECT_CLASS (animation_stepper_holder_parent_class)->finalize (object); +} + +static void +animation_stepper_holder_init (AnimationStepperHolder *holder) +{ + AnimationStepperHolderPrivate *priv = + reinterpret_cast (animation_stepper_holder_get_instance_private (holder)); + + new (&priv->stepper) std::shared_ptr (); +} + +static void +animation_stepper_iface_init (AnimationStepperInterface *interface) +{ + interface->step = animation_stepper_holder_step; +} + +static void +animation_stepper_holder_class_init (AnimationStepperHolderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = animation_stepper_holder_finalize; +} diff --git a/animation-glib/stepper/stepper-holder.h b/animation-glib/stepper/stepper-holder.h new file mode 100644 index 0000000..89e69fb --- /dev/null +++ b/animation-glib/stepper/stepper-holder.h @@ -0,0 +1,34 @@ +/* + * animation-glib/stepper/stepper-holder.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for steppers, which holds a copy of a stepper. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_STEPPER_HOLDER animation_stepper_holder_get_type () +G_DECLARE_FINAL_TYPE (AnimationStepperHolder, animation_stepper_holder, ANIMATION, STEPPER_HOLDER, GObject) + +G_END_DECLS diff --git a/animation-glib/stepper/stepper-internal.cpp b/animation-glib/stepper/stepper-internal.cpp new file mode 100644 index 0000000..5e74321 --- /dev/null +++ b/animation-glib/stepper/stepper-internal.cpp @@ -0,0 +1,71 @@ +/* + * animation-glib/stepper/stepper-internal.cp + * + * Copyright 2019 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Internal helper class for stepper, which wraps a GObject stepper + * in the C++ interface, so it can be consumed directly by other C++ + * steppers, implementation. + */ + +#include + +#include +#include +#include + + +namespace as = animation::stepper; +namespace asg = animation::stepper::glib; + +namespace animation { + namespace stepper { + namespace glib { + struct StepperWrapper::Private + { + AnimationStepper *baseStepper; + + Private (AnimationStepper *baseStepper) : + baseStepper (ANIMATION_STEPPER (g_object_ref (baseStepper))) + { + } + + ~Private () + { + g_clear_object (&baseStepper); + } + }; + } + } +} + +ANIMATION_DEFINE_PROPERTY (asg::StepperWrapper, BaseStepper, AnimationStepper *, priv->baseStepper) + +asg::StepperWrapper::StepperWrapper (AnimationStepper *baseStepper) : + priv (std::make_unique (baseStepper)) +{ +} + +asg::StepperWrapper::~StepperWrapper () +{ +} + +float +asg::StepperWrapper::Step (unsigned int ms) +{ + return animation_stepper_step (priv->baseStepper, ms); +} diff --git a/animation-glib/stepper/stepper-internal.h b/animation-glib/stepper/stepper-internal.h new file mode 100644 index 0000000..cf90676 --- /dev/null +++ b/animation-glib/stepper/stepper-internal.h @@ -0,0 +1,61 @@ +/* + * animation-glib/stepper/stepper-internal.h + * + * Copyright 2019 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Internal helper class for stepper, which wraps a GObject stepper + * in the C++ interface, so it can be consumed directly by other C++ + * steppers. + */ +#pragma once + +#ifdef __cplusplus + +#include + +#include +#include +#include + + +namespace animation { + namespace stepper { + namespace glib { + class StepperWrapper : + public Stepper + { + public: + + StepperWrapper (AnimationStepper *); + ~StepperWrapper (); + + ANIMATION_DECLARE_PROPERTY (StepperWrapper, BaseStepper, AnimationStepper *); + + protected: + + float Step (unsigned int ms) override; + + private: + + struct Private; + std::unique_ptr priv; + }; + } + } +} + +#endif diff --git a/animation-glib/stepper/stepper-wrapper.cpp b/animation-glib/stepper/stepper-wrapper.cpp new file mode 100644 index 0000000..5d93762 --- /dev/null +++ b/animation-glib/stepper/stepper-wrapper.cpp @@ -0,0 +1,160 @@ +/* + * animation-glib/stepper/stepper-wrapper.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for steppers. Only observes the stepper passed + * to it in its construct parameters - not expected to outlive the stepper. + */ + +#include +#include + +#include +#include + +namespace as = animation::stepper; + +struct _AnimationStepperWrapper +{ + GObject parent_instance; +}; + +typedef struct _AnimationStepperWrapperPrivate +{ + as::Stepper *stepper; +} AnimationStepperWrapperPrivate; + +static void animation_stepper_iface_init (AnimationStepperInterface *stepper_iface); + +G_DEFINE_TYPE_WITH_CODE (AnimationStepperWrapper, + animation_stepper_wrapper, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (ANIMATION_TYPE_STEPPER, animation_stepper_iface_init) + G_ADD_PRIVATE (AnimationStepperWrapper)) + +enum { + PROP_0, + PROP_STEPPER, + NPROPS +}; + +static float +animation_stepper_wrapper_step (AnimationStepper *stepper, + unsigned int ms) +{ + AnimationStepperWrapperPrivate *priv = + reinterpret_cast (animation_stepper_wrapper_get_instance_private (ANIMATION_STEPPER_WRAPPER (stepper))); + + return (*priv->stepper) (ms); +} + +static void +animation_stepper_wrapper_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationStepperWrapper *holder = ANIMATION_STEPPER_WRAPPER (object); + AnimationStepperWrapperPrivate *priv = + reinterpret_cast (animation_stepper_wrapper_get_instance_private (holder)); + + switch (prop_id) + { + case PROP_STEPPER: + priv->stepper = reinterpret_cast > (g_value_get_pointer (value))->get (); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_stepper_wrapper_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationStepperWrapper *holder = ANIMATION_STEPPER_WRAPPER (object); + AnimationStepperWrapperPrivate *priv = + reinterpret_cast (animation_stepper_wrapper_get_instance_private (holder)); + + switch (prop_id) + { + case PROP_STEPPER: + g_value_set_pointer (value, reinterpret_cast (priv->stepper)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_stepper_wrapper_finalize (GObject *object) +{ + AnimationStepperWrapper *holder = ANIMATION_STEPPER_WRAPPER (object); + AnimationStepperWrapperPrivate *priv = + reinterpret_cast (animation_stepper_wrapper_get_instance_private (holder)); + + /* The pointer was only observing, so we need not free it */ + priv->stepper = nullptr; + + G_OBJECT_CLASS (animation_stepper_wrapper_parent_class)->finalize (object); +} + +static void +animation_stepper_wrapper_init (AnimationStepperWrapper *stepper_wrapper) +{ +} + +static void +animation_stepper_iface_init (AnimationStepperInterface *interface) +{ + interface->step = animation_stepper_wrapper_step; +} + +static void +animation_stepper_wrapper_class_init (AnimationStepperWrapperClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_stepper_wrapper_get_property; + object_class->set_property = animation_stepper_wrapper_set_property; + object_class->finalize = animation_stepper_wrapper_finalize; + + g_object_class_override_property (object_class, + PROP_STEPPER, + "stepper"); +} + +/** + * animation_stepper_wrapper_new: (skip): + * @interface: A pointer to an interlying stepper implementation. + * + * Create a new #AnimationStepperWrapper, an implementation of + * #AnimationStepper which only observes the underlying stepper when it + * is constructed (keeping its internal state). However, the wrapper + * must not outlive the underlying stepper. + * + * Returns: (transfer full): A new #AnimationStepper with the underlying + * stepper merely observed. + */ +AnimationStepper * +animation_stepper_wrapper_new (gpointer interface) +{ + return ANIMATION_STEPPER (g_object_new (ANIMATION_TYPE_STEPPER_WRAPPER, "stepper", interface, NULL)); +} diff --git a/animation-glib/stepper/stepper-wrapper.h b/animation-glib/stepper/stepper-wrapper.h new file mode 100644 index 0000000..bd59e53 --- /dev/null +++ b/animation-glib/stepper/stepper-wrapper.h @@ -0,0 +1,36 @@ +/* + * animation-glib/stepper/stepper-wrapper.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for steppers, which holds a copy of a stepper. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_STEPPER_WRAPPER animation_stepper_wrapper_get_type () +G_DECLARE_FINAL_TYPE (AnimationStepperWrapper, animation_stepper_wrapper, ANIMATION, STEPPER_WRAPPER, GObject) + +AnimationStepper * animation_stepper_wrapper_new (gpointer interface); + +G_END_DECLS diff --git a/animation-glib/stepper/stepper.cpp b/animation-glib/stepper/stepper.cpp new file mode 100644 index 0000000..2bdf5b1 --- /dev/null +++ b/animation-glib/stepper/stepper.cpp @@ -0,0 +1,44 @@ +/* + * animation-glib/stepper/stepper.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base interface for steppers. + */ + +#include +#include + +namespace as = animation::stepper; + + +G_DEFINE_INTERFACE (AnimationStepper, + animation_stepper, + G_TYPE_OBJECT) +float +animation_stepper_step (AnimationStepper *stepper, + unsigned int ms) +{ + g_return_val_if_fail (ANIMATION_IS_STEPPER (stepper), 0.0f); + + return ANIMATION_STEPPER_GET_IFACE (stepper)->step (stepper, ms); +} + +static void +animation_stepper_default_init (AnimationStepperInterface *iface) +{ +} diff --git a/animation-glib/stepper/stepper.h b/animation-glib/stepper/stepper.h new file mode 100644 index 0000000..40e98f3 --- /dev/null +++ b/animation-glib/stepper/stepper.h @@ -0,0 +1,44 @@ +/* + * animation-glib/stepper/stepper.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base interface for steppers. + */ +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_STEPPER animation_stepper_get_type () +G_DECLARE_INTERFACE (AnimationStepper, animation_stepper, ANIMATION, STEPPER, GObject) + +struct _AnimationStepperInterface +{ + GTypeInterface parent_iface; + + float (*step) (AnimationStepper *self, + unsigned int ms); +}; + +float animation_stepper_step (AnimationStepper *stepper, + unsigned int ms); + +G_END_DECLS diff --git a/animation/meson.build b/animation/meson.build index a61edb8..28e319e 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -25,6 +25,7 @@ animation_headers = [] animation_headers_subdir = 'animation' subdir('query') +subdir('stepper') subdir('wobbly') animation_lib = shared_library( diff --git a/animation/stepper/linear.cpp b/animation/stepper/linear.cpp new file mode 100644 index 0000000..1273781 --- /dev/null +++ b/animation/stepper/linear.cpp @@ -0,0 +1,59 @@ +/* + * animation/stepper/linear.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a linear stepping function - just adds to progress + * according to how long the animation is. + */ + +#include + +namespace as = animation::stepper; + +namespace animation +{ + namespace stepper + { + struct LinearStepper::Private + { + Private(float length) : + length (length) + { + } + + float length; + float progress; + }; + } +} + +ANIMATION_DEFINE_PROPERTY (as::LinearStepper, Length, float, priv->length) + +as::LinearStepper::LinearStepper (unsigned int length): + priv (std::make_unique (length)) +{ +} + +float +as::LinearStepper::Step (unsigned int ms) +{ + priv->progress += ms / priv->length; + return priv->progress; +} + +as::LinearStepper::~LinearStepper () = default; diff --git a/animation/stepper/linear.h b/animation/stepper/linear.h new file mode 100644 index 0000000..dfb3c60 --- /dev/null +++ b/animation/stepper/linear.h @@ -0,0 +1,63 @@ +/* + * animation/stepper/linear.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a linear stepping function - just adds to progress + * according to how long the animation is. + */ + +#include + +#include +#include + +#pragma once + +namespace animation +{ + namespace stepper + { + class LinearStepper: + public Stepper + { + public: + + LinearStepper(); + LinearStepper(unsigned int length); + ~LinearStepper(); + + static constexpr unsigned int DefaultAnimationLength = 300; + + ANIMATION_DECLARE_PROPERTY (LinearStepper, Length, float); + + protected: + + float Step (unsigned int ms) override; + + private: + + struct Private; + std::unique_ptr priv; + }; + + inline std::shared_ptr Linear (unsigned int length) + { + return std::make_shared (length); + } + } +} diff --git a/animation/stepper/meson.build b/animation/stepper/meson.build new file mode 100644 index 0000000..9677bc2 --- /dev/null +++ b/animation/stepper/meson.build @@ -0,0 +1,38 @@ +# /animation/stepper/meson.build +# +# Meson build file for libanimation stepper functions. +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (stepper animation component). + +stepper_headers = files([ + 'linear.h', + 'reverse.h', + 'stepper.h' +]) + +stepper_sources = files([ + 'linear.cpp', + 'reverse.cpp', + 'stepper.cpp' +]) + +animation_headers += stepper_headers +animation_sources += stepper_sources + +install_headers(stepper_headers, subdir: join_paths(animation_headers_subdir, 'stepper')) diff --git a/animation/stepper/reverse.cpp b/animation/stepper/reverse.cpp new file mode 100644 index 0000000..9ead31b --- /dev/null +++ b/animation/stepper/reverse.cpp @@ -0,0 +1,57 @@ +/* + * animation/stepper/reerse.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a reverse stepping function. Takes another stepping + * function and inverts its output. + */ + +#include + +namespace as = animation::stepper; + +namespace animation +{ + namespace stepper + { + struct ReverseStepper::Private + { + Private(as::Stepper::Shared const &stepper) : + stepper (stepper) + { + } + + as::Stepper::Shared stepper; + }; + } +} + +ANIMATION_DEFINE_READONLY_PROPERTY (as::ReverseStepper, Stepper, as::Stepper::Shared, priv->stepper) + +as::ReverseStepper::ReverseStepper (Stepper::Shared const &stepper): + priv (std::make_unique (stepper)) +{ +} + +float +as::ReverseStepper::Step (unsigned int ms) +{ + return 1.0 - (*priv->stepper) (ms); +} + +as::ReverseStepper::~ReverseStepper () = default; diff --git a/animation/stepper/reverse.h b/animation/stepper/reverse.h new file mode 100644 index 0000000..28a58cc --- /dev/null +++ b/animation/stepper/reverse.h @@ -0,0 +1,60 @@ +/* + * animation/stepper/reverse.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a reverse stepping function. Takes another stepping + * function and inverts its output. + */ + +#include + +#include +#include + +#pragma once + +namespace animation +{ + namespace stepper + { + class ReverseStepper : + public Stepper + { + public: + + ReverseStepper (Stepper::Shared const &stepper); + ~ReverseStepper(); + + ANIMATION_DECLARE_READONLY_PROPERTY (ReverseStepper, Stepper, Stepper::Shared); + + protected: + + float Step (unsigned int ms) override; + + private: + + struct Private; + std::unique_ptr priv; + }; + + inline std::shared_ptr Reverse (Stepper::Shared const &stepper) + { + return std::make_shared (stepper); + } + } +} diff --git a/animation/stepper/stepper.cpp b/animation/stepper/stepper.cpp new file mode 100644 index 0000000..7b5080d --- /dev/null +++ b/animation/stepper/stepper.cpp @@ -0,0 +1,32 @@ +/* + * animation/stepper/stepper.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a stepping function, taking an unsigned int of + * milliseconds passed and returning a floating point number + * between 0.0 and 1.0. + */ + +#include + +namespace as = animation::stepper; + +as::Stepper::~Stepper () +{ +} + diff --git a/animation/stepper/stepper.h b/animation/stepper/stepper.h new file mode 100644 index 0000000..1c7580f --- /dev/null +++ b/animation/stepper/stepper.h @@ -0,0 +1,53 @@ +/* + * animation/stepper/stepper.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Definition for a stepping function, taking an unsigned int of + * milliseconds passed and returning a floating point number + * between 0.0 and 1.0. + */ + +#include + +#pragma once + +namespace animation +{ + namespace stepper + { + class Stepper + { + public: + + typedef std::shared_ptr Shared; + + inline float operator() (unsigned int ms) + { + return Step (ms); + } + + protected: + + Stepper () {}; + + virtual float Step (unsigned int) = 0; + virtual ~Stepper (); + + }; + } +} From 5503c9bf900b89832f291b4c40b7e42e2cd3d078 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:21:31 +0300 Subject: [PATCH 13/60] tests: Add meson build file for JS tests, with testStepper.js as the first --- tests/js/meson.build | 47 ++++++++++++++++++++++++++++++++++++++++++++ tests/meson.build | 2 ++ 2 files changed, 49 insertions(+) create mode 100644 tests/js/meson.build diff --git a/tests/js/meson.build b/tests/js/meson.build new file mode 100644 index 0000000..927d925 --- /dev/null +++ b/tests/js/meson.build @@ -0,0 +1,47 @@ +# tests/js/meson.build +# +# Meson build file for the libanimation library javascript tests. +# +# Copyright (C) 2019 Sam Spilsbury +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +javascript_tests = [ + 'testStepper.js' +] + +gjs = find_program('gjs', required: false) +jasmine = find_program('jasmine', required: false) +include_path = '@0@:@1@'.format(meson.source_root(), meson.build_root()) +built_library_path = join_paths(meson.build_root(), 'animation-glib') + +tests_environment = environment() +tests_environment.set('GJS_PATH', include_path) +tests_environment.prepend('GI_TYPELIB_PATH', built_library_path) +tests_environment.prepend('LD_LIBRARY_PATH', built_library_path) +tests_environment.prepend('DYLD_LIBRARY_PATH', built_library_path) + +if gjs.found() and jasmine.found() + foreach test_file : javascript_tests + test(test_file, + gjs, + args: [ + jasmine.path(), + '--verbose', + join_paths(meson.current_source_dir(), test_file) + ], + env: tests_environment) + endforeach +endif diff --git a/tests/meson.build b/tests/meson.build index 3811d9e..d6fbb7d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -18,6 +18,8 @@ # # Build the libanimation unit tests. +subdir('js') + animation_test_sources = [ 'ostream_point_operator.h', 'wobbly/anchor_test.cpp', From a28fbfa4026f910f3ae4c262ee95b972b05fdc34 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 21 Aug 2018 12:36:16 +0800 Subject: [PATCH 14/60] animation: Add 4D vector type Right now we only have 2D vector types, but some consumers like clutter want bounding volumes in a four dimensional space, so we need a type to represent such bounding volumes. --- animation-glib/meson.build | 9 +- animation-glib/vector4d-internal.h | 99 ++++++++++++++++++++++ animation-glib/vector4d.cpp | 39 +++++++++ animation-glib/vector4d.h | 39 +++++++++ animation/geometry.h | 131 +++++++++++++++++++++++++++++ 5 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 animation-glib/vector4d-internal.h create mode 100644 animation-glib/vector4d.cpp create mode 100644 animation-glib/vector4d.h diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 8aba56f..0ccae02 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -22,11 +22,16 @@ api_version = '0' animation_glib_toplevel_headers = files([ 'box.h', - 'vector.h' + 'vector.h', + 'vector4d.h' ]) animation_glib_toplevel_introspectable_sources = files([ 'box.cpp', - 'vector.cpp' + 'vector.cpp', + 'vector4d.cpp' +]) +animation_glib_toplevel_private_headers = files([ + 'vector4d-internal.h' ]) animation_glib_introspectable_sources = files([]) diff --git a/animation-glib/vector4d-internal.h b/animation-glib/vector4d-internal.h new file mode 100644 index 0000000..ca5e305 --- /dev/null +++ b/animation-glib/vector4d-internal.h @@ -0,0 +1,99 @@ +/* + * animation-glib/vector4d-internal.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject boxed type 4D vector implementation. + */ + +#include + +namespace animation +{ + namespace geometry + { + namespace dimension + { + template <> + struct Dimension + { + typedef double data_type; + static const size_t dimensions = 4; + }; + + template <> + struct DimensionAccess + { + static inline double get (AnimationVector4D const &p) + { + return p.x; + } + + static inline void + set (AnimationVector4D &p, double const &value) + { + p.x = value; + } + }; + + template <> + struct DimensionAccess + { + static inline double get (AnimationVector4D const &p) + { + return p.y; + } + + static inline void + set (AnimationVector4D &p, double const &value) + { + p.y = value; + } + }; + + template <> + struct DimensionAccess + { + static inline double get (AnimationVector4D const &p) + { + return p.z; + } + + static inline void + set (AnimationVector4D &p, double const &value) + { + p.z = value; + } + }; + + template <> + struct DimensionAccess + { + static inline double get (AnimationVector4D const &p) + { + return p.w; + } + + static inline void + set (AnimationVector4D &p, double const &value) + { + p.w = value; + } + }; + } + } +} diff --git a/animation-glib/vector4d.cpp b/animation-glib/vector4d.cpp new file mode 100644 index 0000000..a705a29 --- /dev/null +++ b/animation-glib/vector4d.cpp @@ -0,0 +1,39 @@ +/* + * animation-glib/vector4d.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject boxed type 4D vector implementation. + */ + +#include + +static gpointer +animation_vector4d_copy (gpointer ptr) +{ + AnimationVector4D *src = reinterpret_cast (ptr); + AnimationVector4D *dst = g_new0 (AnimationVector4D, 1); + + *dst = *src; + + return reinterpret_cast (dst); +} + +G_DEFINE_BOXED_TYPE (AnimationVector4D, + animation_vector4d, + animation_vector4d_copy, + g_free); diff --git a/animation-glib/vector4d.h b/animation-glib/vector4d.h new file mode 100644 index 0000000..ff2b2b5 --- /dev/null +++ b/animation-glib/vector4d.h @@ -0,0 +1,39 @@ +/* + * animation-glib/vector4d.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject boxed type 4D vector implementation. + */ +#pragma once + +#include + +G_BEGIN_DECLS + +typedef struct { + double x; + double y; + double z; + double w; +} AnimationVector4D; + +#define ANIMATION_TYPE_VECTOR4D animation_vector4d_get_type () + +GType animation_vector4d_get_type (void); + +G_END_DECLS diff --git a/animation/geometry.h b/animation/geometry.h index 389c9b9..413c312 100644 --- a/animation/geometry.h +++ b/animation/geometry.h @@ -371,12 +371,143 @@ namespace animation dimension::get <1> (p) >= y1 && dimension::get <1> (p) <= y2); } + + /** + * Vector4DModel: + * + * A detached 2D point or vector in space for a given data + * type T. This is a structure of two values. + * + * PointModel implements the Dimension trait, meaning that it + * can be used with functions in the animation::geometry::dimension + * namespace. + */ + template + struct Vector4DModel + { + Vector4DModel (T x, T y, T z, T w) noexcept : + x (x), + y (y), + z (z), + w (w) + { + } + + Vector4DModel () noexcept : + x (0), + y (0), + z (0), + w (0) + { + } + + Vector4DModel (Vector4DModel const &v) noexcept : + x (v.x), + y (v.y), + z (v.z), + w (v.w) + { + } + + void swap (Vector4DModel &a, Vector4DModel &b) noexcept + { + std::swap (a.x, b.x); + std::swap (a.y, b.y); + std::swap (a.z, b.w); + std::swap (a.w, b.z); + } + + Vector4DModel & operator= (Vector4DModel other) noexcept + { + swap (*this, other); + + return *this; + } + + T x; + T y; + T z; + T w; + }; + + typedef Vector4DModel Vector4D; + + namespace dimension + { + template + struct Dimension > + { + typedef T data_type; + static const size_t dimensions = 4; + }; + + template + struct DimensionAccess , 0> + { + static inline T get (Vector4DModel const &p) + { + return p.x; + } + + static inline void + set (Vector4DModel &p, T const &value) + { + p.x = value; + } + }; + + template + struct DimensionAccess , 1> + { + static inline T get (Vector4DModel const &p) + { + return p.y; + } + + static inline void + set (Vector4DModel &p, T const &value) + { + p.y = value; + } + }; + + template + struct DimensionAccess , 2> + { + static inline T get (Vector4DModel const &p) + { + return p.z; + } + + static inline void + set (Vector4DModel &p, T const &value) + { + p.z = value; + } + }; + + template + struct DimensionAccess , 3> + { + static inline T get (Vector4DModel const &p) + { + return p.w; + } + + static inline void + set (Vector4DModel &p, T const &value) + { + p.w = value; + } + }; + } } /* Import animation::geometry::Point types into * animation namespace for compatibility. */ typedef animation::geometry::Point Point; typedef animation::geometry::Vector Vector; + typedef animation::geometry::Vector4D Vector4D; template using PointView = animation::geometry::PointView ; From ccbeaa7b6dbf25e8022f33090bd61189942200a5 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 8 Jul 2019 13:58:18 +0300 Subject: [PATCH 15/60] animation-glib/vector: Fix headerblock description --- animation-glib/vector.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/animation-glib/vector.cpp b/animation-glib/vector.cpp index b2f0ec6..e81036d 100644 --- a/animation-glib/vector.cpp +++ b/animation-glib/vector.cpp @@ -17,8 +17,7 @@ * License along with eos-companion-app-service. If not, see * . * - * GObject Interface for "wobbly" textures, vector - * type implementation. + * GObject boxed type vector implementation. */ #include From f475e0776de75c6d9645b866c322663b0816386d Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 2 Sep 2018 14:32:48 +0800 Subject: [PATCH 16/60] animation: Put animation toplevel headers in variable Then install them. This extends on #9, just adds some consistency in the build definition. --- animation/meson.build | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/animation/meson.build b/animation/meson.build index 28e319e..93aaaeb 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -28,6 +28,15 @@ subdir('query') subdir('stepper') subdir('wobbly') +animation_toplevel_headers = files([ + 'geometry.h', + 'geometry_traits.h' +]) + +animation_headers += animation_toplevel_headers + +install_headers(animation_toplevel_headers, subdir: animation_headers_subdir) + animation_lib = shared_library( 'animation', animation_sources, @@ -41,12 +50,6 @@ animation_dep = declare_dependency( include_directories: [ animation_inc ], ) -install_headers( - 'geometry.h', - 'geometry_traits.h', - subdir: animation_headers_subdir -) - pkg = import('pkgconfig') pkg.generate( description: 'Library to provide 2D surface animations', From 256e479f9d1cc0103483d9bf5e68da9c9d547a2a Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 22:30:25 +0300 Subject: [PATCH 17/60] animation: Use files([]) for list-of-files specification in meson.build --- animation/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/animation/meson.build b/animation/meson.build index 93aaaeb..e797271 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -20,8 +20,8 @@ api_version = '0' -animation_sources = [] -animation_headers = [] +animation_sources = files([]) +animation_headers = files([]) animation_headers_subdir = 'animation' subdir('query') From e92da81f60873c2749ac605f32c5b67d25bb7849 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 5 Aug 2018 08:28:35 +0800 Subject: [PATCH 18/60] animation: Add a TransformAnimation base class We'll do some affine transformations based on this one. There's three main methods to override here: - Step() -> Take a single step in the animation by a given number of milliseconds. Returns true if more steps need to be taken, false otherwise - Matrix() -> Get the transformation matrix for the animated surface at this point in the animation. - Extremes() -> Get the four points specifying the 3D bounding volume for this surface, usually transforming the co-ordinates of the surface in scene-relative co-ordinates that have been passed in. --- animation-glib/meson.build | 1 + animation-glib/transform/meson.build | 34 ++++ animation-glib/transform/transform.cpp | 226 +++++++++++++++++++++++++ animation-glib/transform/transform.h | 52 ++++++ animation/meson.build | 4 +- animation/transform/meson.build | 13 ++ animation/transform/transform.h | 45 +++++ animation/transform_calculation.h | 39 +++++ 8 files changed, 413 insertions(+), 1 deletion(-) create mode 100644 animation-glib/transform/meson.build create mode 100644 animation-glib/transform/transform.cpp create mode 100644 animation-glib/transform/transform.h create mode 100644 animation/transform/meson.build create mode 100644 animation/transform/transform.h create mode 100644 animation/transform_calculation.h diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 0ccae02..433e9c7 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -41,6 +41,7 @@ animation_glib_headers_subdir = 'animation-glib' subdir('query') subdir('stepper') +subdir('transform') subdir('wobbly') animation_glib_introspectable_sources += animation_glib_toplevel_introspectable_sources diff --git a/animation-glib/transform/meson.build b/animation-glib/transform/meson.build new file mode 100644 index 0000000..fa8c476 --- /dev/null +++ b/animation-glib/transform/meson.build @@ -0,0 +1,34 @@ +# animation-glib/transform/meson.build +# +# Toplevel meson build file for libanimation. +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (transform animation base component + +transform_introspectable_sources = files([ + 'transform.cpp' +]) + +transform_headers = files([ + 'transform.h' +]) + +animation_glib_introspectable_sources += transform_introspectable_sources +animation_glib_headers += transform_headers + +install_headers(transform_headers, subdir: join_paths(animation_glib_headers_subdir, 'transform')) diff --git a/animation-glib/transform/transform.cpp b/animation-glib/transform/transform.cpp new file mode 100644 index 0000000..bc3f854 --- /dev/null +++ b/animation-glib/transform/transform.cpp @@ -0,0 +1,226 @@ +/* + * animation-glib/transform/transform.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for affine transform based animations. + */ + +#include + +#include +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace at = animation::transform; + +typedef struct _AnimationTransformAnimationPrivate +{ + at::TransformAnimation *interface; +} AnimationTransformAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationTransformAnimation, + animation_transform_animation, + G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_INTERFACE, + NPROPS +}; + +static GParamSpec *animation_transform_animation_props [NPROPS] = { NULL, }; + +gboolean +animation_transform_animation_step (AnimationTransformAnimation *transform_animation, + unsigned int ms) +{ + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + return priv->interface->Step (ms); +} + +/** + * animation_transform_animation_matrix: + * @transform_animation: A #AnimationTransformAnimation + * + * Get the 4x4 column-major transformation matrix for this + * representing the state of this animation. + * + * Returns: (array fixed-size=16): The 4x4 column-major ordered + * transformation matrix. + */ +float const * +animation_transform_animation_matrix (AnimationTransformAnimation *transform_animation) +{ + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + return priv->interface->Matrix (); +} + +/** + * animation_transform_animation_extremes: (skip) + * @transform_animation: A #AnimationTransformAnimation + * @corners: (array fixed-size=4): The four #AnimationVector4D values + * describing the location of the surface corners. + * @out_extremes: (array fixed-size=4) (out): The transformed four #AnimationVector + * values describing the location of the transformed surface + * surface corners. + * + * Get the four co-ordinates of a 3D plane which bound the animated surface. + */ +void +animation_transform_animation_extremes (AnimationTransformAnimation *transform_animation, + AnimationVector const *corners, + AnimationVector4D *out_extremes) +{ + g_return_if_fail (corners != NULL); + g_return_if_fail (out_extremes != NULL); + + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + std::array points = { + animation::Point (corners[0].x, corners[0].y), + animation::Point (corners[1].x, corners[1].y), + animation::Point (corners[2].x, corners[2].y), + animation::Point (corners[3].x, corners[3].y) + }; + + std::array extremes = priv->interface->Extremes (points); + + agd::assign (out_extremes[0], extremes[0]); + agd::assign (out_extremes[1], extremes[1]); + agd::assign (out_extremes[2], extremes[2]); + agd::assign (out_extremes[3], extremes[3]); +} + +/** + * animation_transform_animation_alloc_extremes: (rename-to animation_transform_animation_extremes) + * @transform_animation: A #AnimationTransformAnimation + * @corners: (array fixed-size=4): The four #AnimationVector4D values + * describing the location of the surface corners. + * + * Get the four co-ordinates of a 3D plane which bound the animated surface. This + * function exists as a workaround for the fact that some language bindings do not support + * caller allocation for out-array types. + * + * Returns: (array fixed-size=4) (transfer full): The transformed four #AnimationVector + * values describing the location of the transformed surface + * surface corners. + */ +AnimationVector4D * +animation_transform_animation_alloc_extremes (AnimationTransformAnimation *transform_animation, + AnimationVector const *corners) +{ + AnimationVector4D *out_array = g_new0 (AnimationVector4D, 4); + + animation_transform_animation_extremes (transform_animation, corners, out_array); + return out_array; +} + +float +animation_transform_animation_progress (AnimationTransformAnimation *transform_animation) +{ + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + return priv->interface->Progress (); +} + +static void +animation_transform_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationTransformAnimation *transform_animation = ANIMATION_TRANSFORM_ANIMATION (object); + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + switch (prop_id) + { + case PROP_INTERFACE: + priv->interface = reinterpret_cast (g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_transform_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationTransformAnimation *transform_animation = ANIMATION_TRANSFORM_ANIMATION (object); + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + switch (prop_id) + { + case PROP_INTERFACE: + g_value_set_pointer (value, reinterpret_cast (priv->interface)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_transform_animation_finalize (GObject *object) +{ + AnimationTransformAnimation *transform_animation = ANIMATION_TRANSFORM_ANIMATION (object); + AnimationTransformAnimationPrivate *priv = + reinterpret_cast (animation_transform_animation_get_instance_private (transform_animation)); + + delete priv->interface; + priv->interface = nullptr; + + G_OBJECT_CLASS (animation_transform_animation_parent_class)->finalize (object); +} + +static void +animation_transform_animation_init (AnimationTransformAnimation *model) +{ +} + + +static void +animation_transform_animation_class_init (AnimationTransformAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_transform_animation_get_property; + object_class->set_property = animation_transform_animation_set_property; + object_class->finalize = animation_transform_animation_finalize; + + animation_transform_animation_props[PROP_INTERFACE] = + g_param_spec_pointer ("interface", + "Internal Interface", + "Internal C++ interface that this class wraps", + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_transform_animation_props); +} diff --git a/animation-glib/transform/transform.h b/animation-glib/transform/transform.h new file mode 100644 index 0000000..480b95a --- /dev/null +++ b/animation-glib/transform/transform.h @@ -0,0 +1,52 @@ +/* + * animation-glib/transform/transform.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for affine transform animations. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_TRANSFORM_ANIMATION animation_transform_animation_get_type () +G_DECLARE_DERIVABLE_TYPE (AnimationTransformAnimation, animation_transform_animation, ANIMATION, TRANSFORM_ANIMATION, GObject) + +struct _AnimationTransformAnimationClass { + GObjectClass parent_class; +}; + +gboolean animation_transform_animation_step (AnimationTransformAnimation *transform_animation, + unsigned int ms); + +float animation_transform_animation_progress (AnimationTransformAnimation *transform_animation); + +float const * animation_transform_animation_matrix (AnimationTransformAnimation *transform_animation); + +void animation_transform_animation_extremes (AnimationTransformAnimation *transform_animation, + AnimationVector const *corners, + AnimationVector4D *out_extremes); + +AnimationVector4D * animation_transform_animation_alloc_extremes (AnimationTransformAnimation *transform_animation, + AnimationVector const *corners); + +G_END_DECLS diff --git a/animation/meson.build b/animation/meson.build index e797271..e7c25b3 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -26,11 +26,13 @@ animation_headers_subdir = 'animation' subdir('query') subdir('stepper') +subdir('transform') subdir('wobbly') animation_toplevel_headers = files([ 'geometry.h', - 'geometry_traits.h' + 'geometry_traits.h', + 'transform_calculation.h' ]) animation_headers += animation_toplevel_headers diff --git a/animation/transform/meson.build b/animation/transform/meson.build new file mode 100644 index 0000000..322fe07 --- /dev/null +++ b/animation/transform/meson.build @@ -0,0 +1,13 @@ +# animation/transform/meson.build +# +# Build the libanimation library (transform animation base class component). +# +# See /LICENCE.md for Copyright information. + +transform_headers = files([ + 'transform.h' +]) + +animation_headers += transform_headers + +install_headers(transform_headers, subdir: join_paths(animation_headers_subdir, 'transform')) diff --git a/animation/transform/transform.h b/animation/transform/transform.h new file mode 100644 index 0000000..f35acf5 --- /dev/null +++ b/animation/transform/transform.h @@ -0,0 +1,45 @@ +/* + * animation/transform/transform.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Interface definition for animations that output + * an affine transformation (4x4) for a 2D surface. + */ + +#include + +#include + +#pragma once + +namespace animation +{ + namespace transform + { + class TransformAnimation + { + public: + + virtual ~TransformAnimation() {}; + virtual float * const Matrix () const = 0; + virtual float Progress () const = 0; + virtual bool Step (unsigned int ms) = 0; + virtual std::array Extremes (std::array const &corners) const = 0; + }; + } +} diff --git a/animation/transform_calculation.h b/animation/transform_calculation.h new file mode 100644 index 0000000..41f7b6d --- /dev/null +++ b/animation/transform_calculation.h @@ -0,0 +1,39 @@ +/* + * animation/transform_calculation.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Utility functions with some helpful matrix transforms. + */ +#pragma once + +#include + +#include + +namespace animation +{ + namespace transform_calculation + { + inline animation::Vector4D TransformFlattened2DPointBy3DMatrix (animation::Point const &p, + glm::mat4 const &matrix) + { + namespace agd = animation::geometry::dimension; + + glm::vec4 t (matrix * glm::vec4 (agd::get <0> (p), agd::get <1> (p), 0.0, 1.0)); + return animation::Vector4D (t[0], t[1], t[2], t[3]); + } + } +} From 0b6f2797ab6a96d680f0b238b26c6473cae0fe57 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 13 Aug 2018 14:04:20 +0800 Subject: [PATCH 19/60] animation: Add box_calculation inline functions We'll use these later for the affine transformation animations to calculate box offsets. Usually we want to compute where the center of a box is in both absolute co-ordinates and relative co-ordinates. ComputeBoxCenter will do it in absolute co-ordinates and ComputeBoxCenterOffset in co-ordinates relative to the top left corner of the box. --- animation/box_calculation.h | 70 +++++++++++++++++++++++++++++++++++++ animation/meson.build | 1 + 2 files changed, 71 insertions(+) create mode 100644 animation/box_calculation.h diff --git a/animation/box_calculation.h b/animation/box_calculation.h new file mode 100644 index 0000000..8821dec --- /dev/null +++ b/animation/box_calculation.h @@ -0,0 +1,70 @@ +/* + * animation/box_calculation.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Utility functions to calculate points and box offsets. + */ +#pragma once + +#include + +namespace animation +{ + namespace box_calculation + { + inline animation::Point ComputeBoxCenterOffset (animation::Box const &box) + { + namespace agd = animation::geometry::dimension; + + return animation::Point ((agd::get <0> (box.bottomRight ()) - + agd::get <0> (box.topLeft ())) / 2.0, + (agd::get <1> (box.bottomRight ()) - + agd::get <1> (box.topLeft ())) / 2.0); + } + + inline animation::Point ComputeBoxCenter (animation::Box const &box) + { + namespace agd = animation::geometry::dimension; + + animation::Point topLeft (box.topLeft ()); + agd::pointwise_add (topLeft, ComputeBoxCenterOffset (box)); + return topLeft; + } + + inline animation::Point ComputeRotationAxisOffset (animation::Box const &box, + float u, + float v) + { + namespace agd = animation::geometry::dimension; + + return animation::Point ((agd::get <0> (box.bottomRight ()) - + agd::get <0> (box.topLeft ())) * u, + (agd::get <1> (box.bottomRight ()) - + agd::get <1> (box.topLeft ())) * v); + } + + inline animation::Point ComputeRotationAxis (animation::Box const &box, + float u, + float v) + { + namespace agd = animation::geometry::dimension; + + animation::Point topLeft (box.topLeft ()); + agd::pointwise_add (topLeft, ComputeRotationAxisOffset (box, u, v)); + return topLeft; + } + } +} diff --git a/animation/meson.build b/animation/meson.build index e7c25b3..be8a98b 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -30,6 +30,7 @@ subdir('transform') subdir('wobbly') animation_toplevel_headers = files([ + 'box_calculation.h', 'geometry.h', 'geometry_traits.h', 'transform_calculation.h' From aeaf0fb1db858a68220bd176380b0fd5b8c5fe6e Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 13 Aug 2018 14:04:45 +0800 Subject: [PATCH 20/60] animation: Add math inline helper functions We'll use these later when implementing linear progress based animations. Right now we only have clamp(), used to ensure that the given value sits between some bounds. --- animation/math.h | 33 +++++++++++++++++++++++++++++++++ animation/meson.build | 1 + 2 files changed, 34 insertions(+) create mode 100644 animation/math.h diff --git a/animation/math.h b/animation/math.h new file mode 100644 index 0000000..d47ec9d --- /dev/null +++ b/animation/math.h @@ -0,0 +1,33 @@ +/* + * animation/math.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Math utility functions not availble elsewhere. + */ +#pragma once + +#include + +namespace animation +{ + namespace math + { + template T clamp (T const &value, T const &lower, T const &higher) + { + return std::fmin (higher, std::fmax (lower, value)); + } + } +} diff --git a/animation/meson.build b/animation/meson.build index be8a98b..c9bc3c7 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -33,6 +33,7 @@ animation_toplevel_headers = files([ 'box_calculation.h', 'geometry.h', 'geometry_traits.h', + 'math.h', 'transform_calculation.h' ]) From 9a5fe9168dfe1f7f69007481cf286d81af293c75 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 2 Sep 2018 00:58:43 +0800 Subject: [PATCH 21/60] animation: Add property.h header These are just some simple macros to define ABI safe property get/set functions. They are accessed by calling the correct overload, for instance, to set properties: animation.Target(target).Source(source) And to get properties: auto const &target = animation.Target(); --- animation/meson.build | 1 + animation/property.h | 53 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 animation/property.h diff --git a/animation/meson.build b/animation/meson.build index c9bc3c7..b6f1de3 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -34,6 +34,7 @@ animation_toplevel_headers = files([ 'geometry.h', 'geometry_traits.h', 'math.h', + 'property.h', 'transform_calculation.h' ]) diff --git a/animation/property.h b/animation/property.h new file mode 100644 index 0000000..32b64dd --- /dev/null +++ b/animation/property.h @@ -0,0 +1,53 @@ +/* + * animation/property.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Properties that can be get and set, without a bunch of boilerplate. + */ + +#pragma once + +#define ANIMATION_DECLARE_READONLY_PROPERTY(klass, name, type) \ + type const & name () const ; + +#define ANIMATION_DEFINE_READONLY_PROPERTY(qualifier, name, type, location) \ + type const & qualifier::name() const \ + { \ + return location; \ + } + +#define ANIMATION_DECLARE_PROPERTY(klass, name, type) \ + ANIMATION_DECLARE_READONLY_PROPERTY(klass, name, type) \ + klass & name (type const &v); + +#define ANIMATION_DEFINE_PROPERTY(qualifier, name, type, location) \ + ANIMATION_DEFINE_READONLY_PROPERTY(qualifier, name, type, location) \ + qualifier & qualifier::name(type const &v) \ + { \ + location = v; \ + return *this; \ + } + +#define ANIMATION_DEFINE_PROPERTY_WITH_HOOKS(qualifier, name, type, read_hook, write_hook) \ + type const & qualifier::name() const \ + { \ + return read_hook(); \ + } \ + qualifier & qualifier::name(type const &v) \ + { \ + write_hook(v); \ + return *this; \ + } From bbf583fc3b518433d99c2dc3041132faa02e111c Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 13 Aug 2018 12:48:37 +0800 Subject: [PATCH 22/60] animation-glib: Add constructor helpers These helpers are used to quickly append a new "interface" construction prop to the existing construction properties and make it easy to lookup the props inline. Basically, we need these helpers because we are wrapping some C++ interface implementation in a GObject. GObject doesn't require all the properties to be set at construction time, whereas constructing the C++ interface implementation might. So we need to collect all the properties in the constructor and then construct the underlying C++ interface there, so that it is immediately available to the base class as a property on the object. Doing this by looking over all the construct properties and assigning values would be cumbersome, especially doing it for every effect implementation. Instead, we put all the construct properties into a hash table (hashed by the property name) and then fetch them in O(1) using the InterfaceConstructor template. This template constructs the "Interface" type (the type passed should be the implementation of the interface you want to construct) and variadically takes in all the construction parameters. Use ForwardValueFromHT to extract a typed value from the given property name from its corresponding GValue in the construct properties hash table. For instance, supposing that MyType's constructor is defined as follows: MyType::MyType (float bar, animation::Point const &baz) { ... } You would use the following in the constructor override for the GObject wrapper: const char *interesting_props = { "bar", "baz", NULL }; g_autoptr(GHashTable) props_ht = static_hash_table_of_values_for_specs (interesting_props, construct_params, n_construct_params); auto *interface = InterfaceConstructor ::construct ( ForwardValueFromHT (props_ht, g_value_get_float, "bar"), ForwardValueFromHT (props_ht, animation_point_from_gvalue, "baz") ); g_auto(GValue) interface_value = G_VALUE_INIT; unsigned int new_n_construct_params = 0; GObjectConstructParam *new_construct_params = append_interface_prop_to_construct_params (construct_params, n_construct_params, object_class, &interface_value, interface, &new_n_construct_params); object_class->constructor (type, new_construct_params, new_n_construct_params); Another thing you may wish to do is ensure that certain properties always take a certain value that is not their default (for instance, you might want to ensure tha a certain object-valued property is some non-NULL sane default in the event that it is not set). To do that, you can use replace_named_pointer_props_in_construct_params_if_null, which will only construct the desired placeholder object property values in the event that the property provided by the user or by default due to the G_PARAM_SPEC_CONSTRUCT flag was NULL. For example: /* We need to also set defaults for certain properties in order * to ensure that they are bindable later on, in case they are * not set by the caller. */ replace_named_pointer_props_in_construct_params_if_null ( construct_params, n_construct_params, { ReplacePropSpec ("stepper", g_value_get_object, g_value_set_object, []() -> gpointer { return animation_linear_stepper_new (1); }), ReplacePropSpec ("target", g_value_get_object, g_value_set_object, []() -> gpointer { return animation_box_query_new (); }) } ); Note that with most of the animations in this library, you can get away with the latter only - which is to say that most of the animations can be default-constructed, however their default properties may not be easily bindable because they refer to some object that is not being wrapped by a GObject that can be exposed to the introspection system. Therefore, the animation author's job is to ensure that the properties set on the underlying C++ type support bindability. --- animation-glib/constructor-helpers.cpp | 139 +++++++++++++++++++++++++ animation-glib/constructor-helpers.h | 130 +++++++++++++++++++++++ animation-glib/meson.build | 7 ++ 3 files changed, 276 insertions(+) create mode 100644 animation-glib/constructor-helpers.cpp create mode 100644 animation-glib/constructor-helpers.h diff --git a/animation-glib/constructor-helpers.cpp b/animation-glib/constructor-helpers.cpp new file mode 100644 index 0000000..522a7a2 --- /dev/null +++ b/animation-glib/constructor-helpers.cpp @@ -0,0 +1,139 @@ +/* + * animation-glib/constructor-helpers.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Helpers for constructing C++ objects directly from GObject properties + * in a GObject constructor. + */ + +#include + +#include "constructor-helpers.h" + +/** + * replace_construct_param: + * @construct_params: (array length=n_construct_params) An array of #GObjectConstructParam + * @n_construct_params: Number of elements in @construct_params. + * @prop_name: The name of the construct prop to replace the value of. + * @initialize_func: A function which sets the GValue to something sensible. + * + * Replace a construction parameter @prop_name in the + * passed @construct_params by using the passed @initialize_func. + * + * This function must always replace one construct parameter, it is + * an error to pass a @prop_name that is not in the @construct_params. + */ +void +replace_construct_param (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + AnimationConstructorHelpersInitializeValueFunc initialize_func, + gpointer initialize_func_data) +{ + /* The prop should always be found in the array so that we can replace + * it, this function doesn't support appending the prop. That means + * that the relevant prop must always G_PARAM_CONSTRUCT or + * G_PARAM_CONSTRUCT_ONLY. */ + for (unsigned int i = 0; i < n_construct_params; ++i) + { + if (g_strcmp0 (construct_params[i].pspec->name, prop_name) == 0) + { + g_value_unset (construct_params[i].value); + initialize_func (construct_params[i].value, initialize_func_data); + return; + } + } + + g_assert_not_reached (); +} + +template +static typename std::result_of ::type +invoke_function_thunk (Args... args, gpointer lambda) +{ + FunctionType *f = reinterpret_cast (lambda); + + return (*f)(args...); +} + + +void +replace_named_pointer_prop_in_construct_params (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + gpointer ptr) +{ + auto set_value = [ptr](GValue *value) { + g_value_init (value, G_TYPE_POINTER); + g_value_set_pointer (value, ptr); + }; + replace_construct_param (construct_params, + n_construct_params, + prop_name, + (AnimationConstructorHelpersInitializeValueFunc) invoke_function_thunk , + &set_value); +} + +void +replace_interface_prop_in_construct_params (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + gpointer interface) +{ + replace_named_pointer_prop_in_construct_params (construct_params, + n_construct_params, + "interface", + interface); +} + +void +replace_named_pointer_prop_in_construct_params_if_null (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + AnimationConstructorHelpersGValueGetPointerFunc get_func, + AnimationConstructorHelpersGValueSetPointerFunc set_func, + AnimationConstructorHelpersConstructDefaultValueFunc construct_func) +{ + /* The prop should always be found in the array so that we can replace + * it, this function doesn't support appending the prop. That means + * that the relevant prop must always G_PARAM_CONSTRUCT or + * G_PARAM_CONSTRUCT_ONLY. */ + for (unsigned int i = 0; i < n_construct_params; ++i) + { + if (g_strcmp0 (construct_params[i].pspec->name, prop_name) == 0) + { + if (get_func (construct_params[i].value) == nullptr) + set_func (construct_params[i].value, construct_func ()); + + return; + } + } + + g_assert_not_reached (); +} + +void replace_named_pointer_props_in_construct_params_if_null (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + std::initializer_list replace_specs) +{ + for (auto const &spec : replace_specs) { + replace_named_pointer_prop_in_construct_params_if_null (construct_params, + n_construct_params, + spec.prop_name, + spec.get_func, + spec.set_func, + spec.construct_func); + } +} diff --git a/animation-glib/constructor-helpers.h b/animation-glib/constructor-helpers.h new file mode 100644 index 0000000..c6f9218 --- /dev/null +++ b/animation-glib/constructor-helpers.h @@ -0,0 +1,130 @@ +/* + * animation-glib/constructor-helpers.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Helpers for constructing C++ objects directly from GObject properties + * in a GObject constructor. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +inline GValue * +lookup_gvalue (GHashTable *ht, const char *key) +{ + return reinterpret_cast (g_hash_table_lookup (ht, key)); +} + +typedef void (*AnimationConstructorHelpersInitializeValueFunc) (GValue *value, + gpointer user_data); + +void replace_construct_param (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + AnimationConstructorHelpersInitializeValueFunc initialize_func, + gpointer initialize_func_data); + +void replace_named_pointer_prop_in_construct_params (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + gpointer ptr); + +void replace_interface_prop_in_construct_params (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + gpointer interface); + +typedef gpointer (*AnimationConstructorHelpersGValueGetPointerFunc) (const GValue *value); +typedef void (*AnimationConstructorHelpersGValueSetPointerFunc) (GValue *value, gpointer); +typedef gpointer (*AnimationConstructorHelpersConstructDefaultValueFunc) (void); + +void replace_named_pointer_prop_in_construct_params_if_null (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + const char *prop_name, + AnimationConstructorHelpersGValueGetPointerFunc get_func, + AnimationConstructorHelpersGValueSetPointerFunc set_func, + AnimationConstructorHelpersConstructDefaultValueFunc construct_func); + +G_END_DECLS + +#ifdef __cplusplus +struct ReplacePropSpec +{ + ReplacePropSpec (const char *prop_name, + AnimationConstructorHelpersGValueGetPointerFunc get_func, + AnimationConstructorHelpersGValueSetPointerFunc set_func, + AnimationConstructorHelpersConstructDefaultValueFunc construct_func) : + prop_name (prop_name), + get_func (get_func), + set_func (set_func), + construct_func (construct_func) + { + } + + const char *prop_name; + AnimationConstructorHelpersGValueGetPointerFunc get_func; + AnimationConstructorHelpersGValueSetPointerFunc set_func; + AnimationConstructorHelpersConstructDefaultValueFunc construct_func; +}; + +void replace_named_pointer_props_in_construct_params_if_null (GObjectConstructParam *construct_params, + unsigned int n_construct_params, + std::initializer_list replace_specs); + +template +typename std::result_of ::type ForwardFromValueHT (GHashTable *ht, + Marshaller &&m, + const char *name) +{ + return m (lookup_gvalue (ht, name)); +} + +template +struct InterfaceConstructor +{ + template + static Interface * construct (ArgTypes&&... args) + { + return new Interface (args...); + } +}; + +template +DerivedType * LookupTypedInterfaceProp (GObject *object) +{ + InterfaceType *iface = nullptr; + g_object_get (object, "interface", (gpointer) &iface, NULL); + + return static_cast (iface); +} +#endif diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 433e9c7..358d6fc 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -31,10 +31,15 @@ animation_glib_toplevel_introspectable_sources = files([ 'vector4d.cpp' ]) animation_glib_toplevel_private_headers = files([ + 'constructor-helpers.h', 'vector4d-internal.h' ]) +animation_glib_toplevel_private_sources = files([ + 'constructor-helpers.cpp' +]) animation_glib_introspectable_sources = files([]) +animation_glib_private_headers = files([]) animation_glib_private_sources = files([]) animation_glib_headers = files([]) animation_glib_headers_subdir = 'animation-glib' @@ -45,7 +50,9 @@ subdir('transform') subdir('wobbly') animation_glib_introspectable_sources += animation_glib_toplevel_introspectable_sources +animation_glib_private_sources += animation_glib_toplevel_private_sources animation_glib_headers += animation_glib_toplevel_headers +animation_glib_private_headers += animation_glib_toplevel_private_headers install_headers(animation_glib_toplevel_headers, subdir: animation_glib_headers_subdir) From 70714bd2b18f173118f7d810c5d009e5c6d4e221 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 13 Aug 2018 14:00:34 +0800 Subject: [PATCH 23/60] tests: Add glm_ostream_operators.h This will be useful for printing out the contents of matrices when tests fail. Also necessary if we want to use comparison matchers with them. --- tests/glm_ostream_operators.h | 66 +++++++++++++++++++++++++++++++++++ tests/meson.build | 1 + 2 files changed, 67 insertions(+) create mode 100644 tests/glm_ostream_operators.h diff --git a/tests/glm_ostream_operators.h b/tests/glm_ostream_operators.h new file mode 100644 index 0000000..c3e7832 --- /dev/null +++ b/tests/glm_ostream_operators.h @@ -0,0 +1,66 @@ +/* + * tests/glm_ostream_operators.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * A simple helper to output the contents of glm types when + * tests fail. + */ +#pragma once + +#include +#include // for operator<<, setprecision +#include // for ostream +#include // for basic_ostream, char_traits, etc + +namespace +{ + namespace detail + { + template + struct ArrayDimensionPrinter + { + static std::ostream & Apply (std::ostream &lhs, + T const &array) + { + ArrayDimensionPrinter ::Apply (lhs, array); + return lhs << ", " << array[D]; + } + }; + + template + struct ArrayDimensionPrinter + { + static std::ostream & Apply (std::ostream &lhs, + T const &array) + { + return lhs << array[0]; + } + }; + } +} + +namespace glm +{ + inline std::ostream & operator<< (std::ostream &lhs, + glm::vec4 const &vector) + { + lhs << "vec4("; + ::detail::ArrayDimensionPrinter ::Apply (lhs, vector); + return lhs << ")"; + } +} diff --git a/tests/meson.build b/tests/meson.build index d6fbb7d..600ef7d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -21,6 +21,7 @@ subdir('js') animation_test_sources = [ + 'glm_ostream_operators.h', 'ostream_point_operator.h', 'wobbly/anchor_test.cpp', 'wobbly/constrainment_test.cpp', From 7c596ca70f0dce3516c8e52fd83b884ce441e467 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 21 Aug 2018 12:40:13 +0800 Subject: [PATCH 24/60] matchers: Add AlmostEqMatcher This matcher allows the verify that one type instance is "almost equal" to another type instance. The way that is done is that the type must implement the animation::matcher::AlmostEqTrait type trait and define the apply() method. --- matchers/mathematical_model_matcher.h | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/matchers/mathematical_model_matcher.h b/matchers/mathematical_model_matcher.h index f3f74e1..01fc3f9 100644 --- a/matchers/mathematical_model_matcher.h +++ b/matchers/mathematical_model_matcher.h @@ -445,6 +445,59 @@ namespace animation namespace t = ::testing; namespace agd = ::animation::geometry::dimension; + template + struct AlmostEqTrait + { + typedef T ValueType; + + template + static bool apply (T const &lhs, + T const &rhs, + Comparator &&comparator) + { + return comparator (lhs, rhs); + } + }; + + template + class AlmostEqMatcher : + public t::MatcherInterface + { + public: + + AlmostEqMatcher (T const &value, float tolerance) : + value (value), + tolerance (tolerance) + { + } + + bool MatchAndExplain (T const &x, + t::MatchResultListener *listener) const + { + using namespace std::placeholders; + auto func = animation::testing::close_at_tolerance ::ValueType, typename AlmostEqTrait ::ValueType, typename AlmostEqTrait ::ValueType>; + return AlmostEqTrait ::apply (x, + value, + std::bind (func, _1, _2, tolerance)); + } + + void DescribeTo (std::ostream *os) const + { + *os << "almost equal to " << value << " (at tolerance " << tolerance << ")"; + } + + private: + + T value; + float tolerance; + }; + + template + t::Matcher AlmostEq (T const &value, float tolerance) + { + return t::MakeMatcher (new AlmostEqMatcher (value, tolerance)); + } + template class GeometricallyEqualMatcher : public t::MatcherInterface From 2d21a71e0b7f2b832c4a0992a1712710536df2b8 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 5 Aug 2018 08:30:04 +0800 Subject: [PATCH 25/60] animation: Add zoom animation This is a simple affine transformation that goes from one box to another. The animation runs "from" some source point "to" some target point which is the surface' "natural" position within the scene (eg, if there were a scene graph, the "to" position forms its co-ordinates and its geometry). For instance, if the window was minimizing, then the animation would have to be run in reverse, "from" the minimized point "to" the unminimized point, but stepping backwards. --- animation-glib/meson.build | 1 + animation-glib/zoom/meson.build | 32 ++++ animation-glib/zoom/zoom.cpp | 274 +++++++++++++++++++++++++++++ animation-glib/zoom/zoom.h | 50 ++++++ animation/meson.build | 1 + animation/zoom/meson.build | 32 ++++ animation/zoom/zoom.cpp | 223 +++++++++++++++++++++++ animation/zoom/zoom.h | 62 +++++++ tests/js/meson.build | 3 +- tests/js/testZoomAnimation.js | 130 ++++++++++++++ tests/meson.build | 3 +- tests/zoom/zoom_animation_test.cpp | 175 ++++++++++++++++++ 12 files changed, 984 insertions(+), 2 deletions(-) create mode 100644 animation-glib/zoom/meson.build create mode 100644 animation-glib/zoom/zoom.cpp create mode 100644 animation-glib/zoom/zoom.h create mode 100644 animation/zoom/meson.build create mode 100644 animation/zoom/zoom.cpp create mode 100644 animation/zoom/zoom.h create mode 100644 tests/js/testZoomAnimation.js create mode 100644 tests/zoom/zoom_animation_test.cpp diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 358d6fc..44863b1 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -48,6 +48,7 @@ subdir('query') subdir('stepper') subdir('transform') subdir('wobbly') +subdir('zoom') animation_glib_introspectable_sources += animation_glib_toplevel_introspectable_sources animation_glib_private_sources += animation_glib_toplevel_private_sources diff --git a/animation-glib/zoom/meson.build b/animation-glib/zoom/meson.build new file mode 100644 index 0000000..d58b2f8 --- /dev/null +++ b/animation-glib/zoom/meson.build @@ -0,0 +1,32 @@ +# animation-glib/zoom/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (zoom animation component), GObject bindings. + +zoom_introspectable_sources = files([ + 'zoom.cpp' +]) + +zoom_headers = files([ + 'zoom.h' +]) + +animation_glib_introspectable_sources += zoom_introspectable_sources +animation_glib_headers += zoom_headers + +install_headers(zoom_headers, subdir: join_paths(animation_glib_headers_subdir, 'zoom')) diff --git a/animation-glib/zoom/zoom.cpp b/animation-glib/zoom/zoom.cpp new file mode 100644 index 0000000..db9bd77 --- /dev/null +++ b/animation-glib/zoom/zoom.cpp @@ -0,0 +1,274 @@ +/* + * animation-glib/zoom/zoom.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject interface for a zoom animation. + */ + +#include + +#include +#include +#include +#include +#include +#include + +namespace asg = animation::stepper::glib; +namespace agl = animation::glib; +namespace at = animation::transform; +namespace az = animation::zoom; + +struct _AnimationZoomAnimation +{ + AnimationTransformAnimation parent_instance; +}; + +typedef struct _AnimationZoomAnimationPrivate +{ +} AnimationZoomAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationZoomAnimation, + animation_zoom_animation, + ANIMATION_TYPE_TRANSFORM_ANIMATION) + +enum { + PROP_0, + PROP_SOURCE, + PROP_TARGET, + PROP_STEPPER, + NPROPS +}; + +static GParamSpec *animation_zoom_animation_props [NPROPS] = { NULL, }; + +void +animation_zoom_animation_set_source (AnimationZoomAnimation *animation, + AnimationBoxQuery *source) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Source (std::make_shared (source)); +} + +/** + * animation_zoom_animation_get_source: + * @animation: An #AnimationBoxQuery + * + * Returns: (transfer none): Get the #AnimationBoxQuery source for this #AnimationZoomAnimation + */ +AnimationBoxQuery * +animation_zoom_animation_get_source (AnimationZoomAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Source ())->BoxQuery (); +} + +void +animation_zoom_animation_set_target (AnimationZoomAnimation *animation, + AnimationBoxQuery *target) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Target (std::make_shared (target)); +} + +/** + * animation_zoom_animation_get_target: + * @animation: An #AnimationBoxQuery + * + * Returns: (transfer none): Get the #AnimationBoxQuery target for this #AnimationZoomAnimation + */ +AnimationBoxQuery * +animation_zoom_animation_get_target (AnimationZoomAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Target ())->BoxQuery (); +} + + +void +animation_zoom_animation_set_stepper (AnimationZoomAnimation *animation, + AnimationStepper *stepper) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper (std::make_shared (stepper)); +} + +/** + * animation_zoom_animation_get_stepper: + * @animation: An #AnimationZoomAnimation + * + * Returns: (transfer full): Get the stepper for this #AnimationZoomAnimation + */ +AnimationStepper * +animation_zoom_animation_get_stepper (AnimationZoomAnimation *animation) +{ + auto const &stepper (LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper ()); + + return std::static_pointer_cast (stepper)->BaseStepper (); +} + +static void +animation_zoom_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationZoomAnimation *zoom_animation = ANIMATION_ZOOM_ANIMATION (object); + + switch (prop_id) + { + case PROP_SOURCE: + animation_zoom_animation_set_source (zoom_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_TARGET: + animation_zoom_animation_set_target (zoom_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_STEPPER: + animation_zoom_animation_set_stepper (zoom_animation, + ANIMATION_STEPPER (g_value_get_object (value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_zoom_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationZoomAnimation *zoom_animation = ANIMATION_ZOOM_ANIMATION (object); + + switch (prop_id) + { + case PROP_SOURCE: + g_value_set_object (value, + animation_zoom_animation_get_source (zoom_animation)); + break; + case PROP_TARGET: + g_value_set_object (value, + animation_zoom_animation_get_target (zoom_animation)); + break; + case PROP_STEPPER: + g_value_set_object (value, animation_zoom_animation_get_stepper (zoom_animation)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GObject * +animation_zoom_animation_constructor (GType type, + unsigned int n_construct_params, + GObjectConstructParam *construct_params) +{ + auto *interface = InterfaceConstructor ::construct (); + auto *transform_interface = static_cast (interface); + + /* We need to also set defaults for certain properties in order + * to ensure that they are bindable later on, in case they are + * not set by the caller. */ + replace_named_pointer_props_in_construct_params_if_null ( + construct_params, + n_construct_params, + { + ReplacePropSpec ("stepper", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_linear_stepper_new (1); + }), + ReplacePropSpec ("source", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_box_query_new (); + }), + ReplacePropSpec ("target", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_box_query_new (); + }) + } + ); + + replace_interface_prop_in_construct_params (construct_params, + n_construct_params, + transform_interface); + + return G_OBJECT_CLASS (animation_zoom_animation_parent_class)->constructor (type, + n_construct_params, + construct_params); +} + +static void +animation_zoom_animation_constructed (GObject *object) +{ + /* Take a size-zero step, which ensures that we update the + * internal state of the animation with all the properties we + * just set */ + animation_transform_animation_step (ANIMATION_TRANSFORM_ANIMATION (object), 0); +} + +static void +animation_zoom_animation_init (AnimationZoomAnimation *model) +{ +} + +static void +animation_zoom_animation_class_init (AnimationZoomAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = animation_zoom_animation_constructor; + object_class->constructed = animation_zoom_animation_constructed; + object_class->get_property = animation_zoom_animation_get_property; + object_class->set_property = animation_zoom_animation_set_property; + + animation_zoom_animation_props[PROP_SOURCE] = + g_param_spec_object ("source", + "Source Box Query", + "BoxQuery that we are animating from", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_zoom_animation_props[PROP_TARGET] = + g_param_spec_object ("target", + "Target Box Query", + "BoxQuery that we are animating to", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + animation_zoom_animation_props[PROP_STEPPER] = + g_param_spec_object ("stepper", + "Stepper", + "Stepper to use to progress the animation", + ANIMATION_TYPE_STEPPER, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_zoom_animation_props); +} + +/** + * animation_zoom_new: + * @source: The #AnimationBoxQuery that we are animating from. + * @target: The #AnimationBoxQuery that we are animating to (current location). + * @stepper: The stepper to use. + * + * Returns: (transfer full): A new #AnimationZoomAnimation. + */ +AnimationZoomAnimation * +animation_zoom_new (const AnimationBoxQuery *source, + const AnimationBoxQuery *target, + AnimationStepper *stepper) +{ + return ANIMATION_ZOOM_ANIMATION (g_object_new (ANIMATION_TYPE_ZOOM_ANIMATION, + "source", source, + "target", target, + "stepper", stepper, + NULL)); +} diff --git a/animation-glib/zoom/zoom.h b/animation-glib/zoom/zoom.h new file mode 100644 index 0000000..ab79135 --- /dev/null +++ b/animation-glib/zoom/zoom.h @@ -0,0 +1,50 @@ +/* + * animation-glib/zoom/zoom.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "zoom" animation. + */ +#pragma once + +#include + +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_ZOOM_ANIMATION animation_zoom_animation_get_type () +G_DECLARE_FINAL_TYPE (AnimationZoomAnimation, animation_zoom_animation, ANIMATION, ZOOM_ANIMATION, AnimationTransformAnimation) + +void animation_zoom_animation_set_source (AnimationZoomAnimation *animation, + AnimationBoxQuery *source); +AnimationBoxQuery * animation_zoom_animation_get_source (AnimationZoomAnimation *animation); + +void animation_zoom_animation_set_target (AnimationZoomAnimation *animation, + AnimationBoxQuery *target); +AnimationBoxQuery * animation_zoom_animation_get_target (AnimationZoomAnimation *animation); + + +void animation_zoom_animation_set_stepper (AnimationZoomAnimation *animation, + AnimationStepper *stepper); +AnimationStepper * animation_zoom_animation_get_stepper (AnimationZoomAnimation *animation); + +AnimationZoomAnimation * animation_zoom_new (const AnimationBoxQuery *source, + const AnimationBoxQuery *target, + AnimationStepper *stepper); + +G_END_DECLS diff --git a/animation/meson.build b/animation/meson.build index b6f1de3..fdccd16 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -28,6 +28,7 @@ subdir('query') subdir('stepper') subdir('transform') subdir('wobbly') +subdir('zoom') animation_toplevel_headers = files([ 'box_calculation.h', diff --git a/animation/zoom/meson.build b/animation/zoom/meson.build new file mode 100644 index 0000000..5c2413e --- /dev/null +++ b/animation/zoom/meson.build @@ -0,0 +1,32 @@ +# animation/zoom/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (zoom animation component). + +zoom_sources = files([ + 'zoom.cpp' +]) + +zoom_headers = files([ + 'zoom.h' +]) + +animation_sources += zoom_sources +animation_headers += zoom_headers + +install_headers(zoom_headers, subdir: join_paths(animation_headers_subdir, 'zoom')) diff --git a/animation/zoom/zoom.cpp b/animation/zoom/zoom.cpp new file mode 100644 index 0000000..5023477 --- /dev/null +++ b/animation/zoom/zoom.cpp @@ -0,0 +1,223 @@ +/* + * animation/zoom/zoom.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Animation that causes a surface to zoom from one rectangle to another. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace agd = animation::geometry::dimension; +namespace abc = animation::box_calculation; +namespace am = animation::math; +namespace atc = animation::transform_calculation; +namespace az = animation::zoom; + +namespace +{ + animation::Box EnsureNonZeroArea (animation::Box const &box) + { + auto x1 = agd::get <0> (box.topLeft()); + auto y1 = agd::get <1> (box.topLeft()); + auto x2 = agd::get <0> (box.bottomRight()); + auto y2 = agd::get <1> (box.bottomRight()); + + return animation::Box (animation::Point (x1, y1), + animation::Point (x2 == x1 ? x2 + 1 : x2, + y2 == y1 ? y2 + 1 : y2)); + } + + /* This is a simple affine transformation that goes from + * one box to another. + * + * The animation runs "from" some source point "to" some target point + * which is the surface' "natural" position within the scene (eg, if + * there were a scene graph, the "to" position forms its co-ordinates + * and its geometry). + * + * For instance, if the window was minimizing, then the animation would + * have to be run in reverse, "from" the minimized point "to" the unminimized + * point, but stepping backwards. */ + void ComputeZoomTransform (glm::mat4 &transform, + float progress, + animation::Box const &from, + animation::Box const &to) + { + animation::Point const &fromTopLeft (from.topLeft ()); + animation::Point const &fromBottomRight (from.bottomRight ()); + animation::Point const &toTopLeft (to.topLeft ()); + animation::Point const &toBottomRight (to.bottomRight ()); + + auto fromWidth = agd::get <0> (fromBottomRight) - agd::get <0> (fromTopLeft); + auto fromHeight = agd::get <1> (fromBottomRight) - agd::get <1> (fromTopLeft); + + auto toWidth = agd::get <0> (toBottomRight) - agd::get <0> (toTopLeft); + auto toHeight = agd::get <1> (toBottomRight) - agd::get <1> (toTopLeft); + + animation::Point fromCenter (abc::ComputeBoxCenter (from)); + animation::Point toCenter (abc::ComputeBoxCenter (to)); + animation::Point toCenterOffset (abc::ComputeBoxCenterOffset (to)); + + /* Translate backwards from "to" to "from" according to the inverse of progress + * (eg, at progress == 1.0, there will be no translation from the natural point + * but at progress == 0.0 we translate all the way back from "to" to "from"). */ + animation::Point translation ((agd::get <0> (fromCenter) - agd::get <0> (toCenter)) * (1.0f - progress), + (agd::get <1> (fromCenter) - agd::get <1> (toCenter)) * (1.0f - progress)); + + /* Interpolate between the source and destination width */ + animation::Point scaleFactor (((toWidth * progress) + (fromWidth * (1.0 - progress))) / + toWidth, + ((toHeight * progress) + (fromHeight * (1.0 - progress))) / + toHeight); + + /* Remember that transforamtions are done back to front when postmultiplying. + * + * So we translate to the center first, then scale, then undo the translation + * then translate to the correct point along the animation. */ + auto centerMat = glm::translate (glm::mat4 (1.0), + glm::vec3 (-1.0 * agd::get <0> (toCenterOffset), + -1.0 * agd::get <1> (toCenterOffset), + 0.0)); + auto scaleMat = glm::scale (glm::mat4 (1.0), + glm::vec3 (agd::get <0> (scaleFactor), + agd::get <1> (scaleFactor), + 1.0)); + auto invCenterMat = glm::translate (glm::mat4 (1.0), + glm::vec3 (agd::get <0> (toCenterOffset), + agd::get <1> (toCenterOffset), + 0.0)); + auto translationMat = glm::translate (glm::mat4 (1.0), + glm::vec3 (agd::get <0> (translation), + agd::get <1> (translation), + 0.0)); + + transform = translationMat * invCenterMat * scaleMat * centerMat; + } +} + +namespace animation +{ + namespace zoom + { + struct ZoomAnimation::Private + { + Private (animation::BoxQuery ::Shared const &source, + animation::BoxQuery ::Shared const &target, + animation::stepper::Stepper::Shared const &stepper); + + animation::BoxQuery ::Shared sourceQuery; + animation::BoxQuery ::Shared targetQuery; + + glm::mat4 transform; + float progress; + + animation::stepper::Stepper::Shared stepper; + }; + + ZoomAnimation::Private::Private (animation::BoxQuery ::Shared const &sourceQuery, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper) : + sourceQuery (sourceQuery), + targetQuery (targetQuery), + transform (glm::mat4 (1.0)), + progress ((*stepper) (0)), + stepper (stepper) + { + } + } +} + +std::array +az::ZoomAnimation::Extremes (std::array const &corners) const +{ + return std::array { + atc::TransformFlattened2DPointBy3DMatrix (corners[0], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[1], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[2], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[3], priv->transform) + }; +} + +float +az::ZoomAnimation::Progress () const +{ + return priv->progress; +} + +bool +az::ZoomAnimation::Step (unsigned int ms) +{ + priv->progress = am::clamp ((*priv->stepper) (ms), 0.0f, 1.0f); + + auto from = EnsureNonZeroArea (priv->sourceQuery->Geometry ()); + auto to = EnsureNonZeroArea (priv->targetQuery->Geometry ()); + + ComputeZoomTransform (priv->transform, + priv->progress, + from, + to); + + return priv->progress != 0.0f && priv->progress != 1.0f; +} + +float * const +az::ZoomAnimation::Matrix () const +{ + return glm::value_ptr (priv->transform); +} + +ANIMATION_DEFINE_PROPERTY (az::ZoomAnimation, Source, animation::BoxQuery ::Shared, priv->sourceQuery) +ANIMATION_DEFINE_PROPERTY (az::ZoomAnimation, Target, animation::BoxQuery ::Shared, priv->targetQuery) +ANIMATION_DEFINE_PROPERTY (az::ZoomAnimation, Stepper, animation::stepper::Stepper::Shared, priv->stepper) + +az::ZoomAnimation::ZoomAnimation (animation::BoxQuery ::Shared const &from, + animation::BoxQuery ::Shared const &to, + animation::stepper::Stepper::Shared const &stepper) : + priv (new az::ZoomAnimation::Private (from, to, stepper)) +{ + /* Taking a size-zero step ensures that our matrix is + * in sync with the animation parameters we just set. */ + Step (0); +} + +az::ZoomAnimation::ZoomAnimation () : + ZoomAnimation (animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::stepper::Linear (1)) +{ +} + +az::ZoomAnimation::~ZoomAnimation () +{ +} diff --git a/animation/zoom/zoom.h b/animation/zoom/zoom.h new file mode 100644 index 0000000..07d8849 --- /dev/null +++ b/animation/zoom/zoom.h @@ -0,0 +1,62 @@ +/* + * animation/zoom/zoom.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Animation that causes a surface to zoom from one rectangle to another. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#pragma once + +namespace animation +{ + namespace zoom + { + class ZoomAnimation : + public animation::transform::TransformAnimation + { + public: + + ZoomAnimation (); + ZoomAnimation (animation::BoxQuery ::Shared const &source, + animation::BoxQuery ::Shared const &target, + animation::stepper::Stepper::Shared const &stepper); + ~ZoomAnimation (); + + float * const Matrix () const; + float Progress () const; + bool Step (unsigned int ms); + std::array Extremes (std::array const &corners) const; + + ANIMATION_DECLARE_PROPERTY (ZoomAnimation, Source, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (ZoomAnimation, Target, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (ZoomAnimation, Stepper, animation::stepper::Stepper::Shared) + + private: + + struct Private; + std::unique_ptr priv; + }; + } +} diff --git a/tests/js/meson.build b/tests/js/meson.build index 927d925..abed20d 100644 --- a/tests/js/meson.build +++ b/tests/js/meson.build @@ -19,7 +19,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. javascript_tests = [ - 'testStepper.js' + 'testStepper.js', + 'testZoomAnimation.js' ] gjs = find_program('gjs', required: false) diff --git a/tests/js/testZoomAnimation.js b/tests/js/testZoomAnimation.js new file mode 100644 index 0000000..b253927 --- /dev/null +++ b/tests/js/testZoomAnimation.js @@ -0,0 +1,130 @@ +/* + * /tests/js/testZoomAnimation.js + * + * Tests for the JavaScript Binding to the Zoom Animation. + * + * Copyright (C) 2019 Sam Spilsbury. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +const { Animation } = imports.gi; +const { BoxWrapper, applyCallerTranslation, multiplyMatrixVector } = imports.common; + +describe('Animation', function() { + describe('ZoomAnimation', function() { + let animation; + + beforeEach(function() { + animation = new Animation.ZoomAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 100, + y: 100, + }), + bottom_right: new Animation.Vector({ + x: 200, + y: 200, + }), + }), + }), + target: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 200, + y: 200, + }), + bottom_right: new Animation.Vector({ + x: 300, + y: 300, + }), + }), + }), + stepper: new Animation.LinearStepper({ + length: 10, + }) + }); + }); + + it('has expected properties set on construction', function() { + expect(animation.stepper.length).toEqual(10); + }); + + it('animates to correct position after length timesteps', function() { + expect(animation.step(10)).toBeFalsy(); + + // Note that the animation is applied taking the translation the caller + // would have already applied into account. Since the size does not change, + // at the end of the animation we are in the same position as the caller + // would be. + expect(multiplyMatrixVector(animation.matrix(), [200, 200, 0, 1])).toEqual([200, 200, 0, 1]); + }); + + describe('when scaling', function() { + beforeEach(function() { + animation = new Animation.ZoomAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 100, + y: 100, + }), + bottom_right: new Animation.Vector({ + x: 150, + y: 150, + }), + }), + }), + target: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 200, + y: 200, + }), + bottom_right: new Animation.Vector({ + x: 300, + y: 300, + }), + }), + }), + stepper: new Animation.LinearStepper({ + length: 10, + }) + }); + }); + + it('animates and scales to correct position after length timesteps', function() { + expect(animation.step(10)).toBeFalsy(); + + // Take into account caller translation and size (we translated to (200, 200), (250, 250)), + // meaning that and the end of the animation, nothing changes. + expect(multiplyMatrixVector(animation.matrix(), [250, 250, 0, 1])).toEqual([250, 250, 0, 1]); + }); + + it('is scaled down at the beginning of the animation', function() { + // We need to scale down at the beginning of the animation, given that the + // caller size is already 100x100 + expect(applyCallerTranslation(multiplyMatrixVector(animation.matrix(), [0, 0, 0, 1]), 200, 200)).toEqual([100, 100, 0, 1]); + expect(applyCallerTranslation(multiplyMatrixVector(animation.matrix(), [100, 100, 0, 1]), 200, 200)).toEqual([150, 150, 0, 1]); + }); + }); + + it('starts in the correct position', function() { + // Take into account caller translation, we need to apply a negative offset + expect(multiplyMatrixVector(animation.matrix(), [200, 200, 0, 1])).toEqual([100, 100, 0, 1]); + }); + }); +}); diff --git a/tests/meson.build b/tests/meson.build index 600ef7d..9b01527 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -30,7 +30,8 @@ animation_test_sources = [ 'wobbly/mesh_interpolation_test.cpp', 'wobbly/model_test.cpp', 'wobbly/point_test.cpp', - 'wobbly/spring_test.cpp' + 'wobbly/spring_test.cpp', + 'zoom/zoom_animation_test.cpp' ] glib = dependency('glib-2.0') diff --git a/tests/zoom/zoom_animation_test.cpp b/tests/zoom/zoom_animation_test.cpp new file mode 100644 index 0000000..9ed8161 --- /dev/null +++ b/tests/zoom/zoom_animation_test.cpp @@ -0,0 +1,175 @@ +/* + * tests/wobbly/zoom_animation_test.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Tests for the "zoom" animation. + */ +#include // for size_t +#include // for bind, __bind, _1 +#include + +#include +#include +#include + +#include // for AtLeast +#include // for FunctionMocker, etc +#include // for AnythingMatcher, etc +#include // for EXPECT_CALL, etc +#include // for TEST_F, Test, Types, etc + +#include +#include +#include + +#include +#include +#include +#include +#include + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Eq; +using ::testing::Test; + +namespace am = animation::matchers; +namespace as = animation::stepper; +namespace az = animation::zoom; + +namespace +{ + template + decltype(auto) BQ (animation::Box &&box) + { + return animation::MakeStaticBoxQuery (std::move (box)); + } + + TEST (ZoomAnimation, AnimationIncompleteBeforeLengthTimesteps) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + BQ (animation::Box (animation::Point (100, 100), + animation::Point (200, 200))), + as::Linear (200)); + + EXPECT_TRUE (anim.Step (199)); + } + + TEST (ZoomAnimation, AnimationCompleteAfterLengthTimesteps) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + BQ (animation::Box (animation::Point (100, 100), + animation::Point (200, 200))), + as::Linear (200)); + + EXPECT_FALSE (anim.Step (200)); + } + + TEST (ZoomAnimation, AnimatesToCorrectPositionAfterLengthTimesteps) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (200, 200))), + BQ (animation::Box (animation::Point (200, 200), + animation::Point (300, 300))), + as::Linear (200)); + anim.Step (200); + + /* Apply to a shape at (0, 0) */ + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors, with existing + * translation to scene position */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (200, 200, 0))); + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should be about halfway */ + EXPECT_THAT (translation * transformation * tl, Eq (translation * tl)); + EXPECT_THAT (translation * transformation * br, Eq (translation * br)); + } + + TEST (ZoomAnimation, AnimatesToCorrectReversePositionAfterLengthTimesteps) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (200, 200), + animation::Point (300, 300))), + as::Reverse (as::Linear (200))); + anim.Step (200); + + /* Apply to a shape at (0, 0) */ + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors, with existing + * translation to scene position */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (200, 200, 0))); + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + EXPECT_THAT (translation * transformation * tl, Eq (glm::vec4 (100, 100, 0, 1))); + EXPECT_THAT (translation * transformation * br, Eq (glm::vec4 (150, 150, 0, 1))); + } + + TEST (ZoomAnimation, StartsAtCorrectPositionAtZeroTimesteps) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (200, 200))), + BQ (animation::Box (animation::Point (200, 200), + animation::Point (300, 300))), + as::Linear (200)); + + /* Apply to a shape at (0, 0) */ + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors, with existing + * translation to scene position */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (200, 200, 0))); + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should be about halfway */ + EXPECT_THAT (translation * transformation * tl, Eq (glm::vec4 (100, 100, 0, 1))); + EXPECT_THAT (translation * transformation * br, Eq (glm::vec4 (200, 200, 0, 1))); + } + + TEST (ZoomAnimation, AtCorrectPositionAfterHalfwayPoint) + { + az::ZoomAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (200, 200))), + BQ (animation::Box (animation::Point (200, 200), + animation::Point (300, 300))), + as::Linear (200)); + + anim.Step (100); + + /* Apply to a shape at (0, 0) */ + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors, with existing + * translation to scene position */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (200, 200, 0))); + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should be about halfway */ + EXPECT_THAT (translation * transformation * tl, Eq (glm::vec4 (150, 150, 0, 1))); + EXPECT_THAT (translation * transformation * br, Eq (glm::vec4 (250, 250, 0, 1))); + } +} From 6a039f4d0f4eb52ac93dc64e103e7950fb51b90f Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 12 Aug 2018 22:49:50 +0800 Subject: [PATCH 26/60] animation: Add bounce animation This animation simulates a gentle bounce on a sine wave from the center of the window outwards. The best way to think of this animation is to think of an attenuating sine wave squeezed inwards by two bounds that converge on a single point. In thise case those lines are running from the "initialScale" to 1.0 and the "maximumScale" down to 1.0. We run the sine wave for 2pi * nBounce iterations (scaling it it to fit within the time range), but the effect of the naimation is scaled according to where we are on the bunds. Now, rotate the attenuating sine wave so that it is facing towards you on the z-axis (OpenGL co-ordinates). This is essentially what the animation is. --- animation-glib/bounce/bounce.cpp | 336 +++++++++++++++++++++++++ animation-glib/bounce/bounce.h | 63 +++++ animation-glib/bounce/meson.build | 32 +++ animation-glib/meson.build | 1 + animation/bounce/bounce.cpp | 226 +++++++++++++++++ animation/bounce/bounce.h | 67 +++++ animation/bounce/meson.build | 32 +++ animation/meson.build | 1 + tests/bounce/bounce_animation_test.cpp | 217 ++++++++++++++++ tests/js/meson.build | 1 + tests/js/testBounceAnimation.js | 70 ++++++ tests/meson.build | 1 + 12 files changed, 1047 insertions(+) create mode 100644 animation-glib/bounce/bounce.cpp create mode 100644 animation-glib/bounce/bounce.h create mode 100644 animation-glib/bounce/meson.build create mode 100644 animation/bounce/bounce.cpp create mode 100644 animation/bounce/bounce.h create mode 100644 animation/bounce/meson.build create mode 100644 tests/bounce/bounce_animation_test.cpp create mode 100644 tests/js/testBounceAnimation.js diff --git a/animation-glib/bounce/bounce.cpp b/animation-glib/bounce/bounce.cpp new file mode 100644 index 0000000..f262e7e --- /dev/null +++ b/animation-glib/bounce/bounce.cpp @@ -0,0 +1,336 @@ +/* + * animation-glib/bounce/bounce.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject implementation for a "bounce" animation. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace agl = animation::glib; +namespace ab = animation::bounce; +namespace ag = animation::glib; +namespace as = animation::stepper; +namespace asg = animation::stepper::glib; +namespace at = animation::transform; + +struct _AnimationBounceAnimation +{ + AnimationTransformAnimation parent_instance; +}; + +typedef struct _AnimationBounceAnimationPrivate +{ +} AnimationBounceAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationBounceAnimation, + animation_bounce_animation, + ANIMATION_TYPE_TRANSFORM_ANIMATION) + +enum { + PROP_0, + PROP_INITIAL_SCALE, + PROP_MAXIMUM_SCALE, + PROP_N_BOUNCE, + PROP_TARGET, + PROP_STEPPER, + NPROPS +}; + +static GParamSpec *animation_bounce_animation_props [NPROPS] = { NULL, }; + +double +animation_bounce_animation_get_initial_scale (AnimationBounceAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->InitialScale (); +} + +void +animation_bounce_animation_set_initial_scale (AnimationBounceAnimation *animation, + double initial_scale) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->InitialScale (initial_scale); +} + +double +animation_bounce_animation_get_maximum_scale (AnimationBounceAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->MaximumScale (); +} + +void +animation_bounce_animation_set_maximum_scale (AnimationBounceAnimation *animation, + double maximum_scale) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->MaximumScale (maximum_scale); +} + +unsigned int +animation_bounce_animation_get_n_bounce (AnimationBounceAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->NBounce (); +} + +void +animation_bounce_animation_set_n_bounce (AnimationBounceAnimation *animation, + unsigned int n_bounce) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->NBounce (n_bounce); +} + +void +animation_bounce_animation_set_target (AnimationBounceAnimation *animation, + AnimationBoxQuery *target) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Target (std::make_shared (target)); +} + +/** + * animation_bounce_animation_get_target: + * @animation: An #AnimationBoxQuery + * + * Returns: (transfer none): Get the #AnimationBoxQuery target for this #AnimationBounceAnimation + */ +AnimationBoxQuery * +animation_bounce_animation_get_target (AnimationBounceAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Target ())->BoxQuery (); +} + +void +animation_bounce_animation_set_stepper (AnimationBounceAnimation *animation, + AnimationStepper *stepper) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper (std::make_shared (stepper)); +} + +/** + * animation_bounce_animation_get_stepper: + * @animation: An #AnimationBounceAnimation + * + * Returns: (transfer none): Get the #AnimationStepper for this #AnimationBounceAnimation + */ +AnimationStepper * +animation_bounce_animation_get_stepper (AnimationBounceAnimation *animation) +{ + auto const &stepper (LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper ()); + + return std::static_pointer_cast (stepper)->BaseStepper (); +} + +static void +animation_bounce_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_INITIAL_SCALE: + animation_bounce_animation_set_initial_scale (ANIMATION_BOUNCE_ANIMATION (object), + g_value_get_double (value)); + break; + case PROP_MAXIMUM_SCALE: + animation_bounce_animation_set_maximum_scale (ANIMATION_BOUNCE_ANIMATION (object), + g_value_get_double (value)); + break; + case PROP_N_BOUNCE: + animation_bounce_animation_set_n_bounce (ANIMATION_BOUNCE_ANIMATION (object), + g_value_get_uint (value)); + break; + case PROP_TARGET: + animation_bounce_animation_set_target (ANIMATION_BOUNCE_ANIMATION (object), + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_STEPPER: + animation_bounce_animation_set_stepper (ANIMATION_BOUNCE_ANIMATION (object), + ANIMATION_STEPPER (g_value_get_object (value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_bounce_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationBounceAnimation *bounce_animation = ANIMATION_BOUNCE_ANIMATION (object); + + switch (prop_id) + { + case PROP_INITIAL_SCALE: + g_value_set_double (value, animation_bounce_animation_get_initial_scale (bounce_animation)); + break; + case PROP_MAXIMUM_SCALE: + g_value_set_double (value, animation_bounce_animation_get_maximum_scale (bounce_animation)); + break; + case PROP_N_BOUNCE: + g_value_set_uint (value, animation_bounce_animation_get_n_bounce (bounce_animation)); + break; + case PROP_TARGET: + g_value_set_object (value, animation_bounce_animation_get_target (bounce_animation)); + break; + case PROP_STEPPER: + g_value_set_object (value, animation_bounce_animation_get_stepper (bounce_animation)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GObject * +animation_bounce_animation_constructor (GType type, + unsigned int n_construct_params, + GObjectConstructParam *construct_params) +{ + auto *interface = InterfaceConstructor ::construct (); + auto *transform_interface = static_cast (interface); + + /* We need to also set defaults for certain properties in order + * to ensure that they are bindable later on, in case they are + * not set by the caller. */ + replace_named_pointer_props_in_construct_params_if_null ( + construct_params, + n_construct_params, + { + ReplacePropSpec ("stepper", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_linear_stepper_new (1); + }), + ReplacePropSpec ("target", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_box_query_new (); + }) + } + ); + + replace_interface_prop_in_construct_params (construct_params, + n_construct_params, + transform_interface); + + return G_OBJECT_CLASS (animation_bounce_animation_parent_class)->constructor (type, + n_construct_params, + construct_params); +} + +static void +animation_bounce_animation_constructed (GObject *object) +{ + /* Take a size-zero step, which ensures that we update the + * internal state of the animation with all the properties we + * just set */ + animation_transform_animation_step (ANIMATION_TRANSFORM_ANIMATION (object), 0); +} + +static void +animation_bounce_animation_init (AnimationBounceAnimation *model) +{ +} + + +static void +animation_bounce_animation_class_init (AnimationBounceAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = animation_bounce_animation_constructor; + object_class->constructed = animation_bounce_animation_constructed; + object_class->get_property = animation_bounce_animation_get_property; + object_class->set_property = animation_bounce_animation_set_property; + + animation_bounce_animation_props[PROP_INITIAL_SCALE] = + g_param_spec_double ("initial-scale", + "Initial Scale", + "The initial scale of the animation", + 0.1, + 1.0, + 0.7, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_bounce_animation_props[PROP_MAXIMUM_SCALE] = + g_param_spec_double ("maximum-scale", + "Maximum Scale", + "The maximum scale of the animation", + 1.0, + 3.0, + 1.2, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_bounce_animation_props[PROP_N_BOUNCE] = + g_param_spec_uint ("n-bounce", + "Number of Bounces", + "The number of bounces in the animation", + 1, + 10, + 1, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_bounce_animation_props[PROP_TARGET] = + g_param_spec_object ("target", + "Target Box", + "Box that we are animating to", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_bounce_animation_props[PROP_STEPPER] = + g_param_spec_object ("stepper", + "Stepper", + "Stepper to use to progress the animation", + ANIMATION_TYPE_STEPPER, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_bounce_animation_props); +} + +/** + * animation_bounce_new: + * @initial_scale: Scale factor that the surface will initially have. + * @maximum_scale: Scale factor that the surface will have at maximum. + * @n_bounce: Number of bounces. + * @target: The #AnimationBoxQuery that we are animating to. + * @stepper: The #AnimationStepper of the animation. + * + * Returns: (transfer full): A new #AnimationBounceAnimation. + */ +AnimationBounceAnimation * +animation_bounce_new (double initial_scale, + double maximum_scale, + unsigned int n_bounce, + const AnimationBoxQuery *target, + AnimationStepper *stepper) +{ + return ANIMATION_BOUNCE_ANIMATION (g_object_new (ANIMATION_TYPE_BOUNCE_ANIMATION, + "initial-scale", initial_scale, + "maximum-scale", maximum_scale, + "n-bounce", n_bounce, + "target", target, + "stepper", stepper, + NULL)); +} diff --git a/animation-glib/bounce/bounce.h b/animation-glib/bounce/bounce.h new file mode 100644 index 0000000..4315fb3 --- /dev/null +++ b/animation-glib/bounce/bounce.h @@ -0,0 +1,63 @@ +/* + * animation-glib/bounce/bounce.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "bounce" animation. + */ +#pragma once + +#include + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_BOUNCE_ANIMATION animation_bounce_animation_get_type () +G_DECLARE_FINAL_TYPE (AnimationBounceAnimation, animation_bounce_animation, ANIMATION, BOUNCE_ANIMATION, AnimationTransformAnimation) + +double animation_bounce_animation_get_initial_scale (AnimationBounceAnimation *animation); +void animation_bounce_animation_set_initial_scale (AnimationBounceAnimation *animation, + double initial_scale); + +double animation_bounce_animation_get_maximum_scale (AnimationBounceAnimation *animation); +void animation_bounce_animation_set_maximum_scale (AnimationBounceAnimation *animation, + double maximum_scale); + +unsigned int animation_bounce_animation_get_n_bounce (AnimationBounceAnimation *animation); +void animation_bounce_animation_set_n_bounce (AnimationBounceAnimation *animation, + unsigned int n_bounce); + +void animation_bounce_animation_set_stepper (AnimationBounceAnimation *animation, + AnimationStepper *stepper); +AnimationStepper * animation_bounce_animation_get_stepper (AnimationBounceAnimation *animation); + +void animation_bounce_animation_set_target (AnimationBounceAnimation *animation, + AnimationBoxQuery *target); +AnimationBoxQuery * animation_bounce_animation_get_target (AnimationBounceAnimation *animation); + +AnimationBounceAnimation * animation_bounce_new (double initial_scale, + double maximum_scale, + unsigned int n_bounce, + const AnimationBoxQuery *target, + AnimationStepper *stepper); + +G_END_DECLS + +#pragma once diff --git a/animation-glib/bounce/meson.build b/animation-glib/bounce/meson.build new file mode 100644 index 0000000..44c2279 --- /dev/null +++ b/animation-glib/bounce/meson.build @@ -0,0 +1,32 @@ +# animation-glib/bounce/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (bounce animation component), GObject bindings. + +bounce_introspectable_sources = files([ + 'bounce.cpp' +]) + +bounce_headers = files([ + 'bounce.h' +]) + +animation_glib_introspectable_sources += bounce_introspectable_sources +animation_glib_headers += bounce_headers + +install_headers(bounce_headers, subdir: join_paths(animation_headers_subdir, 'bounce')) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 44863b1..ad9c62a 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -44,6 +44,7 @@ animation_glib_private_sources = files([]) animation_glib_headers = files([]) animation_glib_headers_subdir = 'animation-glib' +subdir('bounce') subdir('query') subdir('stepper') subdir('transform') diff --git a/animation/bounce/bounce.cpp b/animation/bounce/bounce.cpp new file mode 100644 index 0000000..306e977 --- /dev/null +++ b/animation/bounce/bounce.cpp @@ -0,0 +1,226 @@ +/* + * animation/bounce/bounce.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to bounce onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace ab = animation::bounce; +namespace abc = animation::box_calculation; +namespace am = animation::math; +namespace atc = animation::transform_calculation; + +namespace +{ + float SampleSineWave (float progress, + unsigned int nBounces) + { + return ::sin (progress * M_PI * nBounces); + } + + /* This animation simulates a gentle bounce on a sine wave from + * the center of the window outwards. + * + * The best way to think of this animation is to think of an attenuating + * sine wave squeezed inwards by two bounds that converge on a single point. + * + * In thise case those lines are running from the "initialScale" to 1.0 + * and the "maximumScale" down to 1.0. We run the sine wave for 2pi * nBounce + * iterations (scaling it it to fit within the time range), but the effect + * of the naimation is scaled according to where we are on the bunds. + * + * Now, rotate the attenuating sine wave so that it is facing towards you + * on the z-axis (OpenGL co-ordinates). This is essentially what the animation + * is. */ + float ComputeScaleFactorFromProgressParameters (float progress, + float initialScale, + float maximumScale, + unsigned int nBounces) + { + /* Squeeze the sine wave into place by applying a linear + * interpolation to it */ + float const targetScale = 1.0f; + float sampledSine = SampleSineWave (progress, nBounces); + float range = (maximumScale - initialScale) * (1.0f - progress); + float scaleFloor = initialScale + (targetScale - initialScale) * progress; + + return scaleFloor + range * sampledSine; + } + + void ComputeBounceTransform (glm::mat4 &transform, + float progress, + float initialScale, + float maximumScale, + unsigned int nBounces, + animation::Box const &targetBox) + { + animation::Point boxCenterOffset (abc::ComputeBoxCenterOffset (targetBox)); + + float const scaleFactor = ComputeScaleFactorFromProgressParameters (progress, + initialScale, + maximumScale, + nBounces); + auto centerMat = glm::translate (glm::mat4 (1.0), + glm::vec3 (-1 * agd::get <0> (boxCenterOffset), + -1 *agd::get <1> (boxCenterOffset), + 0.0)); + auto scaleMat = glm::scale (glm::mat4 (1.0), + glm::vec3 (scaleFactor, + scaleFactor, + 1.0)); + auto invCenterMat = glm::translate (glm::mat4 (1.0), + glm::vec3 (agd::get <0> (boxCenterOffset), + agd::get <1> (boxCenterOffset), + 0.0)); + + transform = invCenterMat * scaleMat * centerMat; + } +} + +namespace animation +{ + namespace bounce + { + struct BounceAnimation::Private + { + Private (float initialScale, + float maximumScale, + unsigned int nBounce, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper); + + float initialScale; + float maximumScale; + unsigned int nBounce; + animation::BoxQuery ::Shared targetQuery; + + glm::mat4 transform; + float progress; + + animation::stepper::Stepper::Shared stepper; + }; + + BounceAnimation::Private::Private (float initialScale, + float maximumScale, + unsigned int nBounce, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper) : + initialScale (initialScale), + maximumScale (maximumScale), + nBounce (nBounce), + targetQuery (targetQuery), + transform (glm::mat4 (1.0)), + progress ((*stepper) (0)), + stepper (stepper) + { + } + } +} + +std::array +ab::BounceAnimation::Extremes (std::array const &corners) const +{ + return std::array { + atc::TransformFlattened2DPointBy3DMatrix (corners[0], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[1], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[2], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[3], priv->transform) + }; +} + +float +ab::BounceAnimation::Progress () const +{ + return priv->progress; +} + +bool +ab::BounceAnimation::Step (unsigned int ms) +{ + priv->progress = am::clamp ((*priv->stepper) (ms), 0.0f, 1.0f); + + auto target = priv->targetQuery->Geometry (); + + ComputeBounceTransform (priv->transform, + priv->progress, + priv->initialScale, + priv->maximumScale, + priv->nBounce, + target); + + return priv->progress != 0.0f && priv->progress != 1.0f; +} + +float * const +ab::BounceAnimation::Matrix () const +{ + return glm::value_ptr (priv->transform); +} + +ANIMATION_DEFINE_PROPERTY (ab::BounceAnimation, InitialScale, float, priv->initialScale) +ANIMATION_DEFINE_PROPERTY (ab::BounceAnimation, MaximumScale, float, priv->maximumScale) +ANIMATION_DEFINE_PROPERTY (ab::BounceAnimation, NBounce, unsigned int, priv->nBounce) +ANIMATION_DEFINE_PROPERTY (ab::BounceAnimation, Stepper, animation::stepper::Stepper::Shared, priv->stepper) +ANIMATION_DEFINE_PROPERTY (ab::BounceAnimation, Target, animation::BoxQuery ::Shared, priv->targetQuery) + +ab::BounceAnimation::BounceAnimation (float initialScale, + float maximumScale, + unsigned int nBounce, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper) : + priv (std::make_unique (initialScale, + maximumScale, + nBounce, + targetQuery, + stepper)) +{ + Step (0); +} + +ab::BounceAnimation::BounceAnimation () : + BounceAnimation (1.0, + 1.0, + 1, + animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::stepper::Linear (1)) +{ +} + +ab::BounceAnimation::~BounceAnimation () +{ +} diff --git a/animation/bounce/bounce.h b/animation/bounce/bounce.h new file mode 100644 index 0000000..05f6d81 --- /dev/null +++ b/animation/bounce/bounce.h @@ -0,0 +1,67 @@ +/* + * animation/bounce/bounce.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to bounce onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#pragma once + +namespace animation +{ + namespace bounce + { + class BounceAnimation : + public animation::transform::TransformAnimation + { + public: + + BounceAnimation (); + BounceAnimation (float initialScale, + float maximumScale, + unsigned int nBounce, + animation::BoxQuery ::Shared const &target, + animation::stepper::Stepper::Shared const &stepper); + ~BounceAnimation (); + + float * const Matrix () const; + float Progress () const; + bool Step (unsigned int ms); + std::array Extremes (std::array const &corners) const; + + ANIMATION_DECLARE_PROPERTY (BounceAnimation, InitialScale, float) + ANIMATION_DECLARE_PROPERTY (BounceAnimation, MaximumScale, float) + ANIMATION_DECLARE_PROPERTY (BounceAnimation, NBounce, unsigned int) + ANIMATION_DECLARE_PROPERTY (BounceAnimation, Stepper, animation::stepper::Stepper::Shared) + ANIMATION_DECLARE_PROPERTY (BounceAnimation, Target, animation::BoxQuery ::Shared) + + private: + + struct Private; + std::unique_ptr priv; + }; + } +} diff --git a/animation/bounce/meson.build b/animation/bounce/meson.build new file mode 100644 index 0000000..67a2837 --- /dev/null +++ b/animation/bounce/meson.build @@ -0,0 +1,32 @@ +# animation/bounce/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (bounce animation component). + +bounce_sources = files([ + 'bounce.cpp' +]) + +bounce_headers = files([ + 'bounce.h' +]) + +animation_sources += bounce_sources +animation_headers += bounce_headers + +install_headers(bounce_headers, subdir: join_paths(animation_headers_subdir, 'bounce')) diff --git a/animation/meson.build b/animation/meson.build index fdccd16..7812a01 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -24,6 +24,7 @@ animation_sources = files([]) animation_headers = files([]) animation_headers_subdir = 'animation' +subdir('bounce') subdir('query') subdir('stepper') subdir('transform') diff --git a/tests/bounce/bounce_animation_test.cpp b/tests/bounce/bounce_animation_test.cpp new file mode 100644 index 0000000..b75425b --- /dev/null +++ b/tests/bounce/bounce_animation_test.cpp @@ -0,0 +1,217 @@ +/* + * tests/animation/bounce_animation_test.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Tests for the "bounce" animation. + */ +#include // for size_t +#include // for bind, __bind, _1 + +#include +#include +#include + +#include // for AtLeast +#include // for FunctionMocker, etc +#include // for AnythingMatcher, etc +#include // for EXPECT_CALL, etc +#include // for TEST_F, Test, Types, etc + +#include +#include +#include +#include +#include + +#include + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Eq; +using ::testing::Test; + +namespace ab = animation::bounce; +namespace abc = animation::box_calculation; +namespace agd = animation::geometry::dimension; +namespace as = animation::stepper; + +namespace +{ + template + decltype(auto) BQ (animation::Box &&box) + { + return animation::MakeStaticBoxQuery (std::move (box)); + } + + template + decltype(auto) BQ (animation::Box const &box) + { + return animation::MakeStaticBoxQuery (box); + } + + TEST (BounceAnimation, AnimationIncompleteBeforeLengthTimesteps) + { + ab::BounceAnimation anim (0.7f, + 1.5f, + 1, + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + as::Linear (200)); + + EXPECT_TRUE (anim.Step (199)); + } + + TEST (BounceAnimation, AnimationCompleteAtLengthTimesteps) + { + ab::BounceAnimation anim (0.7f, + 1.5f, + 1, + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + as::Linear (200)); + + EXPECT_FALSE (anim.Step (200)); + } + + TEST (BounceAnimation, AnimationStopsAtTargetBox) + { + auto target = animation::Box (animation::Point (100, 100), + animation::Point (200, 200)); + ab::BounceAnimation anim (0.7f, + 1.5f, + 1, + BQ (target), + as::Linear (200)); + + anim.Step (200); + + /* Apply to a shape at (0, 0) which is translated to (100, 100) */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (100, 100, 0))); + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors */ + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should finish at target box */ + EXPECT_THAT (translation * transformation * tl, + Eq (glm::vec4 (agd::get <0> (target.topLeft ()), + agd::get <1> (target.topLeft ()), + 0, + 1))); + EXPECT_THAT (translation * transformation * br, + Eq (glm::vec4 (agd::get <0> (target.bottomRight ()), + agd::get <1> (target.bottomRight ()), + 0, + 1))); + } + + TEST (BounceAnimation, AnimationStartsAtTargetBoxScaledByInitialScale) + { + auto target = animation::Box (animation::Point (100, 100), + animation::Point (200, 200)); + auto width = 100.0f; + auto height = 100.0f; + auto initialScale = 0.5f; + ab::BounceAnimation anim (initialScale, + 1.5f, + 1, + BQ (target), + as::Linear (200)); + + animation::Point boxCenter (abc::ComputeBoxCenter (target)); + + animation::Box scaledBox ( + animation::Point (agd::get <0> (boxCenter) - (width / 2.0f) * initialScale, + agd::get <1> (boxCenter) - (height / 2.0f) * initialScale), + animation::Point (agd::get <0> (boxCenter) + (width / 2.0f) * initialScale, + agd::get <1> (boxCenter) + (height / 2.0f) * initialScale) + ); + + /* Apply to a shape at (0, 0) which is translated to (100, 100) */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (100, 100, 0))); + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors */ + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should finish at target box */ + EXPECT_THAT (translation * transformation * tl, + Eq (glm::vec4 (agd::get <0> (scaledBox.topLeft ()), + agd::get <1> (scaledBox.topLeft ()), + 0, + 1))); + EXPECT_THAT (translation * transformation * br, + Eq (glm::vec4 (agd::get <0> (scaledBox.bottomRight ()), + agd::get <1> (scaledBox.bottomRight ()), + 0, + 1))); + } + + TEST (BounceAnimation, AtHalfwayPointBounceIsAtHighestAttenuatedScale) + { + auto target = animation::Box (animation::Point (100, 100), + animation::Point (200, 200)); + auto width = 100.0f; + auto height = 100.0f; + auto initialScale = 0.5f; + auto maximumScale = 1.5f; + auto progress = 0.5; + auto scaleFloor = (1.0f - initialScale) * progress; + + /* At 0.5f progress, we are at the top of the sine wave, so + * add that to the scale floor. */ + auto expectedScale = scaleFloor + (maximumScale - initialScale); + ab::BounceAnimation anim (initialScale, + maximumScale, + 1, + BQ (target), + as::Linear (200)); + anim.Step (100); + + animation::Point boxCenter (abc::ComputeBoxCenter (target)); + + animation::Box scaledBox ( + animation::Point (agd::get <0> (boxCenter) - (width / 2.0f) * expectedScale, + agd::get <1> (boxCenter) - (height / 2.0f) * expectedScale), + animation::Point (agd::get <0> (boxCenter) + (width / 2.0f) * expectedScale, + agd::get <1> (boxCenter) + (height / 2.0f) * expectedScale) + ); + + /* Apply to a shape at (0, 0) which is translated to (100, 100) */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (100, 100, 0))); + glm::vec4 tl (0, 0, 0, 1); + glm::vec4 br (100, 100, 0, 1); + + /* Apply transformation matrix to vectors */ + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should finish at target box */ + EXPECT_THAT (translation * transformation * tl, + Eq (glm::vec4 (agd::get <0> (scaledBox.topLeft ()), + agd::get <1> (scaledBox.topLeft ()), + 0, + 1))); + EXPECT_THAT (translation * transformation * br, + Eq (glm::vec4 (agd::get <0> (scaledBox.bottomRight ()), + agd::get <1> (scaledBox.bottomRight ()), + 0, + 1))); + } +} diff --git a/tests/js/meson.build b/tests/js/meson.build index abed20d..cce3cae 100644 --- a/tests/js/meson.build +++ b/tests/js/meson.build @@ -19,6 +19,7 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. javascript_tests = [ + 'testBounceAnimation.js', 'testStepper.js', 'testZoomAnimation.js' ] diff --git a/tests/js/testBounceAnimation.js b/tests/js/testBounceAnimation.js new file mode 100644 index 0000000..dc752d6 --- /dev/null +++ b/tests/js/testBounceAnimation.js @@ -0,0 +1,70 @@ +/* + * /tests/js/testBounceAnimation.js + * + * Copyright (C) 2019 Sam Spilsbury . + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Tests for the JavaScript Binding to the Bounce Animation. + */ + +const { Animation } = imports.gi; +const { BoxWrapper, applyCallerTranslation, multiplyMatrixVector } = imports.common; + +describe('Animation', function() { + describe('BounceAnimation', function() { + let animation; + + beforeEach(function() { + animation = new Animation.BounceAnimation({ + initial_scale: 0.7, + maximum_scale: 1.5, + n_bounce: 1, + target: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 0, + y: 0, + }), + bottom_right: new Animation.Vector({ + x: 100, + y: 100, + }) + }), + }), + stepper: new Animation.LinearStepper({ + length: 200 + }), + }); + }); + + it('has properties that were set on construction', function() { + expect(animation.initial_scale).toBeCloseTo(0.7); + expect(animation.maximum_scale).toBeCloseTo(1.5); + expect(animation.n_bounce).toEqual(1); + expect(animation.stepper.length).toEqual(200); + }); + + it('stops at the target box', function() { + animation.step(200); + + expect(multiplyMatrixVector(animation.matrix(), [0, 0, 0, 1])).toEqual([0, 0, 0, 1]); + }); + + it('starts with the target scale', function() { + expect(multiplyMatrixVector(animation.matrix(), [0, 0, 0, 1])).toEqual([15, 15, 0, 1]); + }); + }); +}); diff --git a/tests/meson.build b/tests/meson.build index 9b01527..23a0d81 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -21,6 +21,7 @@ subdir('js') animation_test_sources = [ + 'bounce/bounce_animation_test.cpp', 'glm_ostream_operators.h', 'ostream_point_operator.h', 'wobbly/anchor_test.cpp', From 24eb3127705e3707177d0168723b7616026a8928 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sat, 1 Sep 2018 23:46:41 +0800 Subject: [PATCH 27/60] animation: Add glide animation This animation rotates the surface from some rotated position towards a resting position where its rotation angle is 0, 0, 0 on all three axes. The axis that the surface can rotate on is configurable in unit-cordinates. Rotation on 0.5, 0.5 rotates at the center of the surface, whereas rotating from 0, 0 would rotate from the top left corner. --- animation-glib/glide/glide.cpp | 440 +++++++++++++++++++++++++++ animation-glib/glide/glide.h | 78 +++++ animation-glib/glide/meson.build | 32 ++ animation-glib/meson.build | 1 + animation/glide/glide.cpp | 256 ++++++++++++++++ animation/glide/glide.h | 76 +++++ animation/glide/meson.build | 31 ++ animation/meson.build | 1 + tests/glide/glide_animation_test.cpp | 198 ++++++++++++ tests/js/meson.build | 1 + tests/js/testGlideAnimation.js | 93 ++++++ tests/meson.build | 1 + 12 files changed, 1208 insertions(+) create mode 100644 animation-glib/glide/glide.cpp create mode 100644 animation-glib/glide/glide.h create mode 100644 animation-glib/glide/meson.build create mode 100644 animation/glide/glide.cpp create mode 100644 animation/glide/glide.h create mode 100644 animation/glide/meson.build create mode 100644 tests/glide/glide_animation_test.cpp create mode 100644 tests/js/testGlideAnimation.js diff --git a/animation-glib/glide/glide.cpp b/animation-glib/glide/glide.cpp new file mode 100644 index 0000000..d176286 --- /dev/null +++ b/animation-glib/glide/glide.cpp @@ -0,0 +1,440 @@ +/* + * animation-glib/glide/glide.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject implementation for a "glide" animation. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace ag = animation::glide; +namespace agl = animation::glib; +namespace at = animation::transform; +namespace asg = animation::stepper::glib; + +struct _AnimationGlideAnimation +{ + AnimationTransformAnimation parent_instance; +}; + +typedef struct _AnimationGlideAnimationPrivate +{ +} AnimationGlideAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationGlideAnimation, + animation_glide_animation, + ANIMATION_TYPE_TRANSFORM_ANIMATION) + +enum { + PROP_0, + PROP_INITIAL_DISTANCE, + PROP_X_ROTATION_ANGLE_DEGREES, + PROP_Y_ROTATION_ANGLE_DEGREES, + PROP_X_AXIS_LOCATION_UNIT, + PROP_Y_AXIS_LOCATION_UNIT, + PROP_VIEWPORT, + PROP_TARGET, + PROP_STEPPER, + NPROPS +}; + +static GParamSpec *animation_glide_animation_props [NPROPS] = { NULL, }; + +void +animation_glide_animation_set_initial_distance (AnimationGlideAnimation *animation, + double initial_distance) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->InitialDistance (initial_distance); +} + +double +animation_glide_animation_get_initial_distance (AnimationGlideAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->InitialDistance (); +} + +void +animation_glide_animation_set_x_rotation_angle_degrees (AnimationGlideAnimation *animation, + double x_rotation_angle_degrees) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->XRotationAngleDegrees (x_rotation_angle_degrees); +} + +double +animation_glide_animation_get_x_rotation_angle_degrees (AnimationGlideAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->XRotationAngleDegrees (); +} + +void +animation_glide_animation_set_y_rotation_angle_degrees (AnimationGlideAnimation *animation, + double y_rotation_angle_degrees) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->YRotationAngleDegrees (y_rotation_angle_degrees); +} + +double +animation_glide_animation_get_y_rotation_angle_degrees (AnimationGlideAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->YRotationAngleDegrees (); +} + +void +animation_glide_animation_set_x_axis_location_unit (AnimationGlideAnimation *animation, + double x_axis_location_unit) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->XAxisLocationUnit (x_axis_location_unit); +} + +double +animation_glide_animation_get_x_axis_location_unit (AnimationGlideAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->XAxisLocationUnit (); +} + +void +animation_glide_animation_set_y_axis_location_unit (AnimationGlideAnimation *animation, + double y_axis_location_unit) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->YAxisLocationUnit (y_axis_location_unit); +} + +double +animation_glide_animation_get_y_axis_location_unit (AnimationGlideAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->YAxisLocationUnit (); +} + +void +animation_glide_animation_set_target (AnimationGlideAnimation *animation, + AnimationBoxQuery *target) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Target (std::make_shared (target)); +} + +/** + * animation_glide_animation_get_target: + * @animation: An #AnimationBoxQuery + * + * Returns: (transfer none): Get the #AnimationBoxQuery target for this #AnimationGlideAnimation + */ +AnimationBoxQuery * +animation_glide_animation_get_target (AnimationGlideAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Target ())->BoxQuery (); +} + +void +animation_glide_animation_set_viewport (AnimationGlideAnimation *animation, + AnimationBoxQuery *viewport) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Viewport (std::make_shared (viewport)); +} + +/** + * animation_glide_animation_get_viewport: + * @animation: An #AnimationBoxQuery + * + * Returns: (transfer none): Get the #AnimationBoxQuery for the viewport for this #AnimationGlideAnimation + */ +AnimationBoxQuery * +animation_glide_animation_get_viewport (AnimationGlideAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Viewport ())->BoxQuery (); +} + +void +animation_glide_animation_set_stepper (AnimationGlideAnimation *animation, + AnimationStepper *stepper) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper (std::make_shared (stepper)); +} + +/** + * animation_glide_animation_get_stepper: + * @animation: An #AnimationGlideAnimation + * + * Returns: (transfer full): Get the stepper for this #AnimationGlideAnimation + */ +AnimationStepper * +animation_glide_animation_get_stepper (AnimationGlideAnimation *animation) +{ + auto const &stepper (LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper ()); + + return std::static_pointer_cast (stepper)->BaseStepper (); +} + +static void +animation_glide_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationGlideAnimation *glide_animation = ANIMATION_GLIDE_ANIMATION (object); + + switch (prop_id) + { + case PROP_INITIAL_DISTANCE: + animation_glide_animation_set_initial_distance (glide_animation, g_value_get_double (value)); + break; + case PROP_X_ROTATION_ANGLE_DEGREES: + animation_glide_animation_set_x_rotation_angle_degrees (glide_animation, g_value_get_double (value)); + break; + case PROP_Y_ROTATION_ANGLE_DEGREES: + animation_glide_animation_set_y_rotation_angle_degrees (glide_animation, g_value_get_double (value)); + break; + case PROP_X_AXIS_LOCATION_UNIT: + animation_glide_animation_set_x_axis_location_unit (glide_animation, g_value_get_double (value)); + break; + case PROP_Y_AXIS_LOCATION_UNIT: + animation_glide_animation_set_y_axis_location_unit (glide_animation, g_value_get_double (value)); + break; + case PROP_VIEWPORT: + animation_glide_animation_set_viewport (glide_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_TARGET: + animation_glide_animation_set_target (glide_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_STEPPER: + animation_glide_animation_set_stepper (glide_animation, + ANIMATION_STEPPER (g_value_get_object (value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_glide_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationGlideAnimation *glide_animation = ANIMATION_GLIDE_ANIMATION (object); + + switch (prop_id) + { + case PROP_INITIAL_DISTANCE: + g_value_set_double (value, animation_glide_animation_get_initial_distance (glide_animation)); + break; + case PROP_X_ROTATION_ANGLE_DEGREES: + g_value_set_double (value, animation_glide_animation_get_x_rotation_angle_degrees (glide_animation)); + break; + case PROP_Y_ROTATION_ANGLE_DEGREES: + g_value_set_double (value, animation_glide_animation_get_y_rotation_angle_degrees (glide_animation)); + break; + case PROP_X_AXIS_LOCATION_UNIT: + g_value_set_double (value, animation_glide_animation_get_x_axis_location_unit (glide_animation)); + break; + case PROP_Y_AXIS_LOCATION_UNIT: + g_value_set_double (value, animation_glide_animation_get_y_axis_location_unit (glide_animation)); + break; + case PROP_VIEWPORT: + g_value_set_object (value, animation_glide_animation_get_viewport (glide_animation)); + break; + case PROP_TARGET: + g_value_set_object (value, animation_glide_animation_get_target (glide_animation)); + break; + case PROP_STEPPER: + g_value_set_object (value, animation_glide_animation_get_stepper (glide_animation)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GObject * +animation_glide_animation_constructor (GType type, + unsigned int n_construct_params, + GObjectConstructParam *construct_params) +{ + auto *interface = InterfaceConstructor ::construct (); + auto *transform_interface = static_cast (interface); + + /* We need to also set defaults for certain properties in order + * to ensure that they are bindable later on, in case they are + * not set by the caller. */ + replace_named_pointer_props_in_construct_params_if_null ( + construct_params, + n_construct_params, + { + ReplacePropSpec ("stepper", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_linear_stepper_new (1); + }), + ReplacePropSpec ("target", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_box_query_new (); + }), + ReplacePropSpec ("viewport", g_value_get_object, g_value_set_object, []() -> gpointer { + return animation_box_query_new (); + }) + } + ); + + replace_interface_prop_in_construct_params (construct_params, + n_construct_params, + transform_interface); + + return G_OBJECT_CLASS (animation_glide_animation_parent_class)->constructor (type, + n_construct_params, + construct_params); +} + +static void +animation_glide_animation_constructed (GObject *object) +{ + /* Take a size-zero step, which ensures that we update the + * internal state of the animation with all the properties we + * just set */ + animation_transform_animation_step (ANIMATION_TRANSFORM_ANIMATION (object), 0); +} + +static void +animation_glide_animation_init (AnimationGlideAnimation *model) +{ +} + + +static void +animation_glide_animation_class_init (AnimationGlideAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = animation_glide_animation_constructor; + object_class->constructed = animation_glide_animation_constructed; + object_class->get_property = animation_glide_animation_get_property; + object_class->set_property = animation_glide_animation_set_property; + + animation_glide_animation_props[PROP_INITIAL_DISTANCE] = + g_param_spec_double ("initial-distance", + "Initial Distance", + "The initial distance away from the camera", + -1.0, + 1.0, + -0.3, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_X_ROTATION_ANGLE_DEGREES] = + g_param_spec_double ("x-rotation-angle-degrees", + "X Rotation Angle Degrees", + "Number of degrees on the X axis to rotate", + -360.0, + 360.0, + 0.0, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_Y_ROTATION_ANGLE_DEGREES] = + g_param_spec_double ("y-rotation-angle-degrees", + "Y Rotation Angle Degrees", + "Number of degrees on the Y axis to rotate", + -360.0, + 360.0, + 0.0, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_X_AXIS_LOCATION_UNIT] = + g_param_spec_double ("x-axis-location-unit", + "X Axis Location Unit", + "Unit-coordinates of where the X axis is on the surface", + 0.0, + 1.0, + 0.2, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_Y_AXIS_LOCATION_UNIT] = + g_param_spec_double ("y-axis-location-unit", + "Y Axis Location Unit", + "Unit-coordinates of where the Y axis is on the surface", + 0.0, + 1.0, + 0.5, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_VIEWPORT] = + g_param_spec_object ("viewport", + "Viewport BoxQuery", + "BoxQuery for the viewport dimensions that the surface is in", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_TARGET] = + g_param_spec_object ("target", + "Target BoxQuery", + "BoxQuery for box that we are animating to", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_glide_animation_props[PROP_STEPPER] = + g_param_spec_object ("stepper", + "Stepper", + "Stepper to use to progress the animation", + ANIMATION_TYPE_STEPPER, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_glide_animation_props); +} + +/** + * animation_glide_new: + * @initial_distance: Initial distance frm the camera. + * @x_rotation_angle_degrees: Degrees of rotation towards the X axis. + * @y_rotation_angle_degrees: Degrees of rotation towards the Y axis. + * @y_axis_location_unit: Unit-coordinates of where the X axis is on the surface. + * @x_axis_location_unit: Unit-coordinates of where the Y axis is on the surface. + * @screen_width: Width of the screen, in pixels. + * @target_box: The #AnimationBoxQuery that we are animating to. + * @length: The length of the animation. + * + * Returns: (transfer full): A new #AnimationGlideAnimation. + */ +AnimationGlideAnimation * +animation_glide_new (double initial_distance, + double x_rotation_angle_degrees, + double y_rotation_angle_degrees, + double x_axis_location_unit, + double y_axis_location_unit, + unsigned int screen_width, + const AnimationBoxQuery *target_box, + unsigned int length) +{ + return ANIMATION_GLIDE_ANIMATION (g_object_new (ANIMATION_TYPE_GLIDE_ANIMATION, + "initial-distance", initial_distance, + "x-rotation-angle-degrees", x_rotation_angle_degrees, + "y-rotation-angle-degrees", y_rotation_angle_degrees, + "x-axis-location-unit", x_axis_location_unit, + "y-axis-location-unit", y_axis_location_unit, + "screen-width", screen_width, + "target", target_box, + "length", length, + NULL)); +} diff --git a/animation-glib/glide/glide.h b/animation-glib/glide/glide.h new file mode 100644 index 0000000..1bec121 --- /dev/null +++ b/animation-glib/glide/glide.h @@ -0,0 +1,78 @@ +/* + * animation-glib/glide/glide.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "glide" animation. + */ +#pragma once + +#include + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_GLIDE_ANIMATION animation_glide_animation_get_type () +G_DECLARE_FINAL_TYPE (AnimationGlideAnimation, animation_glide_animation, ANIMATION, GLIDE_ANIMATION, AnimationTransformAnimation) + +void animation_glide_animation_set_initial_distance (AnimationGlideAnimation *animation, + double initial_distance); +double animation_glide_animation_get_initial_distance (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_x_rotation_angle_degrees (AnimationGlideAnimation *animation, + double x_rotation_angle_degrees); +double animation_glide_animation_get_x_rotation_angle_degrees (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_y_rotation_angle_degrees (AnimationGlideAnimation *animation, + double y_rotation_angle_degrees); +double animation_glide_animation_get_y_rotation_angle_degrees (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_x_axis_location_unit (AnimationGlideAnimation *animation, + double x_axis_location_unit); + +double animation_glide_animation_get_x_axis_location_unit (AnimationGlideAnimation *animation); +void animation_glide_animation_set_y_axis_location_unit (AnimationGlideAnimation *animation, + double y_axis_location_unit); + +double animation_glide_animation_get_y_axis_location_unit (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_target (AnimationGlideAnimation *animation, + AnimationBoxQuery *target); +AnimationBoxQuery * animation_glide_animation_get_target (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_viewport (AnimationGlideAnimation *animation, + AnimationBoxQuery *target); +AnimationBoxQuery * animation_glide_animation_get_viewport (AnimationGlideAnimation *animation); + +void animation_glide_animation_set_stepper (AnimationGlideAnimation *animation, + AnimationStepper *stepper); +AnimationStepper * animation_glide_animation_get_stepper (AnimationGlideAnimation *animation); + +AnimationGlideAnimation * animation_glide_new (double initial_distance, + double x_rotation_angle_degrees, + double y_rotation_angle_degrees, + double x_axis_location_unit, + double y_axis_location_unit, + unsigned int screen_width, + const AnimationBoxQuery *target_box, + unsigned int length); + +G_END_DECLS diff --git a/animation-glib/glide/meson.build b/animation-glib/glide/meson.build new file mode 100644 index 0000000..a7e1112 --- /dev/null +++ b/animation-glib/glide/meson.build @@ -0,0 +1,32 @@ +# animation/glide/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (glide animation component), GObject bindings. + +glide_introspectable_sources = files([ + 'glide.cpp' +]) + +glide_headers = files([ + 'glide.h' +]) + +animation_glib_introspectable_sources += glide_introspectable_sources +animation_glib_headers += glide_headers + +install_headers(glide_headers, subdir: join_paths(animation_glib_headers_subdir, 'glide')) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index ad9c62a..5c588a2 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -45,6 +45,7 @@ animation_glib_headers = files([]) animation_glib_headers_subdir = 'animation-glib' subdir('bounce') +subdir('glide') subdir('query') subdir('stepper') subdir('transform') diff --git a/animation/glide/glide.cpp b/animation/glide/glide.cpp new file mode 100644 index 0000000..71a7d31 --- /dev/null +++ b/animation/glide/glide.cpp @@ -0,0 +1,256 @@ +/* + * animation/glide/glide.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to glide onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace abc = animation::box_calculation; +namespace ag = animation::glide; +namespace am = animation::math; +namespace atc = animation::transform_calculation; + +namespace +{ + inline float DegreesToRadians (float degrees) + { + return (M_PI / 180.0f) * degrees; + } + + /* This undoes perspective distortion */ + glm::mat4 CreatePerspectiveDistortionMatrix (unsigned int screenWidth) + { + float v = -1.0 / screenWidth; + + return glm::mat4 (glm::vec4 (1.0, 0.0, 0.0, 0.0), + glm::vec4 (0.0, 1.0, 0.0, 0.0), + glm::vec4 (0.0, 0.0, 0.0, 0.0), + glm::vec4 (0.0, 0.0, v, 1.0)); + } + + /* This animation rotates the surface from some rotated position + * towards a resting position where its rotation angle is + * 0, 0, 0 on all three axes. The axis that the surface can rotate + * on is configurable in unit-cordinates. Rotation on 0.5, 0.5 rotates + * at the center of the surface, whereas rotating from 0, 0 would rotate + * from the top left corner. */ + void ComputeGlideTransform (glm::mat4 &transform, + float progress, + float initialDistance, + float xRotationAngleDegrees, + float yRotationAngleDegrees, + float xAxisLocationUnit, + float yAxisLocationUnit, + unsigned int screenWidth, + animation::Box const &targetBox) + { + animation::Point rotationAxis (abc::ComputeRotationAxisOffset (targetBox, + xAxisLocationUnit, + yAxisLocationUnit)); + + auto centerMat = glm::translate (glm::mat4 (1.0f), + glm::vec3 (-1.0f * agd::get <0> (rotationAxis), + -1.0f * agd::get <1> (rotationAxis), + 0.0f)); + auto xRotationMat = glm::rotate (glm::mat4 (1.0f), + DegreesToRadians (xRotationAngleDegrees) * (1.0f - progress), + glm::vec3 (1.0f, 0.0f, 0.0f)); + auto yRotationMat = glm::rotate (glm::mat4 (1.0f), + DegreesToRadians (yRotationAngleDegrees) * (1.0f - progress), + glm::vec3 (0.0f, 1.0f, 0.0f)); + auto translationMat = glm::translate (glm::mat4 (1.0f), + glm::vec3 (0.0f, + 0.0f, + -1.0f * initialDistance * (1.0f - progress))); + auto invCenterMat = glm::translate (glm::mat4 (1.0f), + glm::vec3 (agd::get <0> (rotationAxis), + agd::get <1> (rotationAxis), + 0.0f)); + + transform = invCenterMat * + translationMat * + xRotationMat * + yRotationMat * + CreatePerspectiveDistortionMatrix (screenWidth) * + centerMat; + } +} + +namespace animation +{ + namespace glide + { + struct GlideAnimation::Private + { + Private (float initialDistance, + float xRotationAngleDegrees, + float yRotationAngleDegrees, + float xAxisLocationUnit, + float yAxisLocationUnit, + animation::BoxQuery ::Shared const &viewportQuery, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper); + + float initialDistance; + float xRotationAngleDegrees; + float yRotationAngleDegrees; + float xAxisLocationUnit; + float yAxisLocationUnit; + animation::BoxQuery ::Shared viewportQuery; + animation::BoxQuery ::Shared targetQuery; + + glm::mat4 transform; + float progress; + + animation::stepper::Stepper::Shared stepper; + }; + + GlideAnimation::Private::Private (float initialDistance, + float xRotationAngleDegrees, + float yRotationAngleDegrees, + float xAxisLocationUnit, + float yAxisLocationUnit, + animation::BoxQuery ::Shared const &viewportQuery, + animation::BoxQuery ::Shared const &targetQuery, + animation::stepper::Stepper::Shared const &stepper) : + initialDistance (initialDistance), + xRotationAngleDegrees (xRotationAngleDegrees), + yRotationAngleDegrees (yRotationAngleDegrees), + xAxisLocationUnit (xAxisLocationUnit), + yAxisLocationUnit (yAxisLocationUnit), + viewportQuery (viewportQuery), + targetQuery (targetQuery), + transform (glm::mat4 (1.0)), + progress ((*stepper) (0)), + stepper (stepper) + { + } + } +} + +std::array +ag::GlideAnimation::Extremes (std::array const &corners) const +{ + return std::array { + atc::TransformFlattened2DPointBy3DMatrix (corners[0], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[1], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[2], priv->transform), + atc::TransformFlattened2DPointBy3DMatrix (corners[3], priv->transform) + }; +} + +float +ag::GlideAnimation::Progress () const +{ + return priv->progress; +} + +bool +ag::GlideAnimation::Step (unsigned int ms) +{ + priv->progress = am::clamp ((*priv->stepper) (ms), 0.0f, 1.0f); + + auto target = priv->targetQuery->Geometry (); + auto viewport = priv->viewportQuery->Geometry (); + + ComputeGlideTransform (priv->transform, + priv->progress, + priv->initialDistance, + priv->xRotationAngleDegrees, + priv->yRotationAngleDegrees, + priv->xAxisLocationUnit, + priv->yAxisLocationUnit, + viewport.bottomRight().x - viewport.topLeft().x, + target); + + return priv->progress != 0.0f && priv->progress != 1.0f; +} + +float * const +ag::GlideAnimation::Matrix () const +{ + return glm::value_ptr (priv->transform); +} + +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, InitialDistance, float, priv->initialDistance) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, XRotationAngleDegrees, float, priv->xRotationAngleDegrees) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, YRotationAngleDegrees, float, priv->yRotationAngleDegrees) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, XAxisLocationUnit, float, priv->xAxisLocationUnit) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, YAxisLocationUnit, float, priv->yAxisLocationUnit) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, Stepper, animation::stepper::Stepper::Shared, priv->stepper) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, Viewport, animation::BoxQuery ::Shared, priv->viewportQuery) +ANIMATION_DEFINE_PROPERTY (ag::GlideAnimation, Target, animation::BoxQuery ::Shared, priv->targetQuery) + +ag::GlideAnimation::GlideAnimation (float initialDistance, + float xRotationAngleDegrees, + float yRotationAngleDegrees, + float xAxisLocationUnit, + float yAxisLocationUnit, + animation::BoxQuery ::Shared const &viewportQuery, + animation::BoxQuery ::Shared const &target, + std::shared_ptr const &stepper) : + priv (new ag::GlideAnimation::Private (initialDistance, + xRotationAngleDegrees, + yRotationAngleDegrees, + xAxisLocationUnit, + yAxisLocationUnit, + viewportQuery, + target, + stepper)) +{ + Step (0); +} + +ag::GlideAnimation::GlideAnimation () : + GlideAnimation (0.0, + 0.0, + 0.0, + 0.5, + 0.5, + animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::stepper::Linear (1)) +{ +} + +ag::GlideAnimation::~GlideAnimation () +{ +} diff --git a/animation/glide/glide.h b/animation/glide/glide.h new file mode 100644 index 0000000..994e933 --- /dev/null +++ b/animation/glide/glide.h @@ -0,0 +1,76 @@ +/* + * animation/glide/glide.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to glide onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#pragma once + +namespace animation +{ + namespace glide + { + class GlideAnimation : + public animation::transform::TransformAnimation + { + public: + + GlideAnimation (); + GlideAnimation (float initialDistance, + float xRotationAngleDegrees, + float yRotationAngleDegrees, + float xAxisLocationUnit, + float yAxisLocationUnit, + animation::BoxQuery ::Shared const &viewportQuery, + animation::BoxQuery ::Shared const &target, + animation::stepper::Stepper::Shared const &stepper); + ~GlideAnimation (); + + float * const Matrix () const; + float Progress () const; + bool Step (unsigned int ms); + std::array Extremes (std::array const &corners) const; + + ANIMATION_DECLARE_PROPERTY (GlideAnimation, InitialDistance, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, XRotationAngleDegrees, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, YRotationAngleDegrees, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, XAxisLocationUnit, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, YAxisLocationUnit, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, MaximumScale, float) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, Target, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, Viewport, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (GlideAnimation, Stepper, animation::stepper::Stepper::Shared) + + private: + + struct Private; + std::unique_ptr priv; + }; + } +} diff --git a/animation/glide/meson.build b/animation/glide/meson.build new file mode 100644 index 0000000..1ec3922 --- /dev/null +++ b/animation/glide/meson.build @@ -0,0 +1,31 @@ +# animation/glide/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (glide animation component). +glide_sources = files([ + 'glide.cpp' +]) + +glide_headers = files([ + 'glide.h' +]) + +animation_sources += glide_sources +animation_headers += glide_headers + +install_headers(glide_headers, subdir: join_paths(animation_headers_subdir, 'glide')) diff --git a/animation/meson.build b/animation/meson.build index 7812a01..1a4f511 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -25,6 +25,7 @@ animation_headers = files([]) animation_headers_subdir = 'animation' subdir('bounce') +subdir('glide') subdir('query') subdir('stepper') subdir('transform') diff --git a/tests/glide/glide_animation_test.cpp b/tests/glide/glide_animation_test.cpp new file mode 100644 index 0000000..c809929 --- /dev/null +++ b/tests/glide/glide_animation_test.cpp @@ -0,0 +1,198 @@ +/* + * tests/wobbly/glide_animation_test.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Tests for the "glide" animation. + */ +#include // for size_t +#include // for bind, __bind, _1 + +#include +#include +#include + +#include // for AtLeast +#include // for FunctionMocker, etc +#include // for AnythingMatcher, etc +#include // for EXPECT_CALL, etc +#include // for TEST_F, Test, Types, etc + +#include +#include +#include + +#include +#include + +#include + +using ::animation::matchers::AlmostEq; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Eq; +using ::testing::Not; +using ::testing::Test; + +namespace agd = animation::geometry::dimension; +namespace ag = animation::glide; +namespace as = animation::stepper; + +namespace animation +{ + namespace matchers + { + template <> + struct AlmostEqTrait + { + typedef float ValueType; + + template + static bool apply (glm::vec4 const &lhs, + glm::vec4 const &rhs, + Comparator &&comparator) + { + return comparator (lhs[0], rhs[0]) && + comparator (lhs[1], rhs[1]) && + comparator (lhs[2], rhs[2]) && + comparator (lhs[3], rhs[3]); + } + }; + } +} + +namespace +{ + template + decltype(auto) BQ (animation::Box &&box) + { + return animation::MakeStaticBoxQuery (std::move (box)); + } + + template + decltype(auto) BQ (animation::Box const &box) + { + return animation::MakeStaticBoxQuery (box); + } + + static const animation::Box MockScreenGeometry = animation::Box ( + animation::Point(0, 0), + animation::Point(1000, 100) + ); + + TEST (GlideAnimation, AnimationIncompleteBeforeLengthTimesteps) + { + ag::GlideAnimation anim (0.5f, + 0.0f, + 20.0f, + 0.5f, + 0.0f, + BQ (MockScreenGeometry), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + as::Linear (200)); + + EXPECT_TRUE (anim.Step (199)); + } + + TEST (GlideAnimation, AnimationCompleteAtLengthTimesteps) + { + ag::GlideAnimation anim (0.5f, + 0.0f, + 20.0f, + 0.5f, + 0.0f, + BQ (MockScreenGeometry), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + as::Linear (200)); + + EXPECT_FALSE (anim.Step (200)); + } + + TEST (GlideAnimation, AnimationStopsAtTargetBox) + { + auto target = animation::Box (animation::Point (100, 100), + animation::Point (200, 200)); + ag::GlideAnimation anim (0.5f, + 0.0f, + 20.0f, + 0.5f, + 0.0f, + BQ (MockScreenGeometry), + BQ (target), + as::Linear (200)); + + anim.Step (200); + + /* Apply to a shape at (0, 0) which is translated to (100, 100) */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (100, 100, 0))); + glm::vec4 tl (translation * glm::vec4 (0, 0, 0, 1)); + glm::vec4 br (translation * glm::vec4 (100, 100, 0, 1)); + + /* Apply transformation matrix to vectors */ + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should finish at target box */ + EXPECT_THAT (transformation * tl, + AlmostEq (glm::vec4 (agd::get <0> (target.topLeft ()), + agd::get <1> (target.topLeft ()), + 0, + 1), + 0.002)); + EXPECT_THAT (transformation * br, + AlmostEq (glm::vec4 (agd::get <0> (target.bottomRight ()), + agd::get <1> (target.bottomRight ()), + 0, + 1), + 0.002)); + } + + TEST (GlideAnimation, AnimationDoesNotStartAtTargetBox) + { + auto target = animation::Box (animation::Point (100, 100), + animation::Point (200, 200)); + ag::GlideAnimation anim (0.5f, + 0.0f, + 20.0f, + 0.5f, + 0.0f, + BQ (MockScreenGeometry), + BQ (target), + as::Linear (200)); + + /* Apply to a shape at (0, 0) which is translated to (100, 100) */ + glm::mat4 translation (glm::translate (glm::mat4 (1.0), glm::vec3 (100, 100, 0))); + glm::vec4 tl (translation * glm::vec4 (0, 0, 0, 1)); + glm::vec4 br (translation * glm::vec4 (100, 100, 0, 1)); + + /* Apply transformation matrix to vectors */ + glm::mat4 transformation (glm::make_mat4x4 (anim.Matrix ())); + + /* Should finish at target box */ + EXPECT_THAT (transformation * tl, + Not (Eq (glm::vec4 (agd::get <0> (target.topLeft ()), + agd::get <1> (target.topLeft ()), + 0, + 1)))); + EXPECT_THAT (transformation * br, + Not (Eq (glm::vec4 (agd::get <0> (target.bottomRight ()), + agd::get <1> (target.bottomRight ()), + 0, + 1)))); + } +} diff --git a/tests/js/meson.build b/tests/js/meson.build index cce3cae..4746254 100644 --- a/tests/js/meson.build +++ b/tests/js/meson.build @@ -20,6 +20,7 @@ javascript_tests = [ 'testBounceAnimation.js', + 'testGlideAnimation.js', 'testStepper.js', 'testZoomAnimation.js' ] diff --git a/tests/js/testGlideAnimation.js b/tests/js/testGlideAnimation.js new file mode 100644 index 0000000..253d000 --- /dev/null +++ b/tests/js/testGlideAnimation.js @@ -0,0 +1,93 @@ +/* + * /tests/js/testGlideAnimation.js + * + * Tests for the JavaScript Binding to the Glide Animation. + * + * Copyright (C) 2019 Sam Spilsbury. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +const { Animation } = imports.gi; +const { BoxWrapper, applyCallerTranslation, multiplyMatrixVector } = imports.common; + +describe('Animation', function() { + describe('GlideAnimation', function() { + let animation; + + beforeEach(function() { + animation = new Animation.GlideAnimation({ + initial_distance: 0.5, + x_rotation_angle_degrees: 0.0, + y_rotation_angle_degrees: 20.0, + x_axis_location_unit: 0.5, + y_axis_location_unit: 0.0, + viewport: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ x: 0, y: 0 }), + bottom_right: new Animation.Vector({ x: 1000, y: 100 }) + }) + }), + target: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 0, + y: 0, + }), + bottom_right: new Animation.Vector({ + x: 100, + y: 100, + }), + }), + }), + stepper: new Animation.LinearStepper({ + length: 200, + }) + }); + }); + + it('has expected properties set on construction', function() { + expect(animation.initial_distance).toEqual(0.5); + expect(animation.x_rotation_angle_degrees).toEqual(0.0); + expect(animation.y_rotation_angle_degrees).toEqual(20.0); + expect(animation.x_axis_location_unit).toEqual(0.5); + expect(animation.y_axis_location_unit).toEqual(0.0); + expect(animation.stepper.length).toEqual(200); + }); + + it('animates to correct position after length timesteps', function() { + expect(animation.step(200)).toBeFalsy(); + + // Note that the animation is applied taking the translation the caller + // would have already applied into account. Since the size does not change, + // at the end of the animation we are in the same position as the caller + // would be. + let target = [100, 100, 0, 1] + multiplyMatrixVector(animation.matrix(), [100, 100, 0, 1]).forEach(function(x, i) { + expect(x).toBeCloseTo(target[i]); + }); + }); + + it('starts in the correct position', function() { + expect(animation.step(200)).toBeFalsy(); + + // Note that the animation is applied taking the translation the caller + // would have already applied into account. Since the size does not change, + // at the end of the animation we are in the same position as the caller + // would be. + expect(multiplyMatrixVector(animation.matrix(), [100, 100, 0, 1])).not.toEqual([100, 100, 0, 1]); + }); + }); +}); diff --git a/tests/meson.build b/tests/meson.build index 23a0d81..e18793a 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -22,6 +22,7 @@ subdir('js') animation_test_sources = [ 'bounce/bounce_animation_test.cpp', + 'glide/glide_animation_test.cpp', 'glm_ostream_operators.h', 'ostream_point_operator.h', 'wobbly/anchor_test.cpp', From 36965072d235f0e50afb6a4ba6a9608c05e73c61 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 22 Aug 2018 06:50:02 +0800 Subject: [PATCH 28/60] grid: Add GridAnimation interface The GridAnimation interface represents transformations that are done on a grid of vertices that a surface is subdivided into. The transformation can be any arbitrary function. Implementations must override three functions: - Step() -> Take a single step on the function according to the number of milliseconds passed. If more steps are needed to complete the animation, return true, otherwise return false. - DeformUVToModelSpace() - Given some co-ordinates between (0, 0) and (1, 1), deform the co-ordinates into model space, that is the space in the scene of the surface as if the function were applied. - Extremes() - Get the geometry of a 2D bounding plane totally encompassing the deformed surface. --- animation-glib/grid/grid.cpp | 259 ++++++++++++++++++++++++++++++++ animation-glib/grid/grid.h | 57 +++++++ animation-glib/grid/meson.build | 32 ++++ animation-glib/meson.build | 1 + animation/grid/grid.h | 46 ++++++ animation/grid/meson.build | 27 ++++ animation/meson.build | 1 + 7 files changed, 423 insertions(+) create mode 100644 animation-glib/grid/grid.cpp create mode 100644 animation-glib/grid/grid.h create mode 100644 animation-glib/grid/meson.build create mode 100644 animation/grid/grid.h create mode 100644 animation/grid/meson.build diff --git a/animation-glib/grid/grid.cpp b/animation-glib/grid/grid.cpp new file mode 100644 index 0000000..df064ac --- /dev/null +++ b/animation-glib/grid/grid.cpp @@ -0,0 +1,259 @@ +/* + * animation-glib/grid/grid.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for grid animations. + */ + +#include + +#include +#include +#include +#include + +namespace agd = animation::geometry::dimension; +namespace agr = animation::grid; + +typedef struct _AnimationGridAnimationPrivate +{ + agr::GridAnimation *interface; +} AnimationGridAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationGridAnimation, + animation_grid_animation, + G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_INTERFACE, + NPROPS +}; + +static GParamSpec *animation_grid_animation_props [NPROPS] = { NULL, }; + +gboolean +animation_grid_animation_step (AnimationGridAnimation *grid_animation, + unsigned int ms) +{ + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + return priv->interface->Step (ms); +} + +/** + * animation_grid_animation_deform_uv_to_model_space: + * @grid_animation: An #AnimationGridAnimation + * @uv: An #AnimationVector representing a point in unit coordinate + * space to be deformed. + * @model_space_point: (out caller-allocates): An #AnimationVector to write + * the deformed co-ordinate to. + * + * Determine where a unit coordinate lies in model space. + */ +void +animation_grid_animation_deform_uv_to_model_space (AnimationGridAnimation *grid_animation, + AnimationVector *uv, + AnimationVector *model_space_point) +{ + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + g_return_if_fail (model_space_point != NULL); + + animation::Point point (uv->x, uv->y); + animation::Point deformed (priv->interface->DeformUVToModelSpace (point)); + + model_space_point->x = agd::get <0> (deformed); + model_space_point->y = agd::get <1> (deformed); +} + +/** + * animation_grid_animation_resolution: + * @grid_animation: An #AnimationGridAnimation + * @out_resolution: (out caller-allocates): An #AnimationVector specifying the + * ideal resolution of the animated surface grid. + * + * Return the expected grid resolution that would be required to + * make this animation look smooth. The renderer should subdivide + * the animated surface into this many equal sized chunks. + */ +void +animation_grid_animation_resolution (AnimationGridAnimation *grid_animation, + AnimationVector *out_resolution) +{ + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + g_return_if_fail (out_resolution != NULL); + + auto resolution_cpp (priv->interface->Resolution ()); + + out_resolution->x = agd::get <0> (resolution_cpp); + out_resolution->y = agd::get <1> (resolution_cpp); +} + +/** + * animation_grid_animation_extremes: (skip) + * @grid_animation: A #AnimationGridAnimation + * @corners: (array fixed-size=4): The four #AnimationVector4D values + * describing the location of the surface corners. + * @out_extremes: (array fixed-size=4) (out): The grided four #AnimationVector + * values describing the location of the grided surface + * surface corners. + * + * Get the four co-ordinates of a 3D plane which bound the animated surface. + */ +void +animation_grid_animation_extremes (AnimationGridAnimation *grid_animation, + AnimationVector const *corners, + AnimationVector4D *out_extremes) +{ + g_return_if_fail (corners != NULL); + g_return_if_fail (out_extremes != NULL); + + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + std::array points = { + animation::Point (corners[0].x, corners[0].y), + animation::Point (corners[1].x, corners[1].y), + animation::Point (corners[2].x, corners[2].y), + animation::Point (corners[3].x, corners[3].y) + }; + + std::array extremes = priv->interface->Extremes (points); + + agd::assign (out_extremes[0], extremes[0]); + agd::assign (out_extremes[1], extremes[1]); + agd::assign (out_extremes[2], extremes[2]); + agd::assign (out_extremes[3], extremes[3]); +} + +/** + * animation_grid_animation_alloc_extremes: (rename-to animation_grid_animation_extremes) + * @grid_animation: A #AnimationGridAnimation + * @corners: (array fixed-size=4): The four #AnimationVector4D values + * describing the location of the surface corners. + * + * Get the four co-ordinates of a 3D plane which bound the animated surface. This + * function exists as a workaround for the fact that some language bindings do not support + * caller allocation for out-array types. + * + * Returns: (array fixed-size=4) (transfer full): The transformed four #AnimationVector + * values describing the location of the transformed surface + * surface corners. + */ +AnimationVector4D * +animation_grid_animation_alloc_extremes (AnimationGridAnimation *grid_animation, + AnimationVector const *corners) +{ + AnimationVector4D *out_array = g_new0 (AnimationVector4D, 4); + + animation_grid_animation_extremes (grid_animation, corners, out_array); + return out_array; +} + +float +animation_grid_animation_progress (AnimationGridAnimation *grid_animation) +{ + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + return priv->interface->Progress (); +} + +static void +animation_grid_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationGridAnimation *grid_animation = ANIMATION_GRID_ANIMATION (object); + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + switch (prop_id) + { + case PROP_INTERFACE: + priv->interface = reinterpret_cast (g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_grid_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationGridAnimation *grid_animation = ANIMATION_GRID_ANIMATION (object); + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + switch (prop_id) + { + case PROP_INTERFACE: + g_value_set_pointer (value, reinterpret_cast (priv->interface)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_grid_animation_finalize (GObject *object) +{ + AnimationGridAnimation *grid_animation = ANIMATION_GRID_ANIMATION (object); + AnimationGridAnimationPrivate *priv = + reinterpret_cast (animation_grid_animation_get_instance_private (grid_animation)); + + delete priv->interface; + priv->interface = nullptr; + + G_OBJECT_CLASS (animation_grid_animation_parent_class)->finalize (object); +} + +static void +animation_grid_animation_init (AnimationGridAnimation *model) +{ +} + + +static void +animation_grid_animation_class_init (AnimationGridAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_grid_animation_get_property; + object_class->set_property = animation_grid_animation_set_property; + object_class->finalize = animation_grid_animation_finalize; + + animation_grid_animation_props[PROP_INTERFACE] = + g_param_spec_pointer ("interface", + "Internal Interface", + "Internal C++ interface that this class wraps", + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_grid_animation_props); +} diff --git a/animation-glib/grid/grid.h b/animation-glib/grid/grid.h new file mode 100644 index 0000000..1666ac4 --- /dev/null +++ b/animation-glib/grid/grid.h @@ -0,0 +1,57 @@ +/* + * animation-glib/grid/grid.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject base class for grid animations. + */ +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_GRID_ANIMATION animation_grid_animation_get_type () +G_DECLARE_DERIVABLE_TYPE (AnimationGridAnimation, animation_grid_animation, ANIMATION, GRID_ANIMATION, GObject) + +struct _AnimationGridAnimationClass { + GObjectClass parent_class; +}; + +gboolean animation_grid_animation_step (AnimationGridAnimation *grid_animation, + unsigned int ms); + +float animation_grid_animation_progress (AnimationGridAnimation *grid_animation); + +void animation_grid_animation_deform_uv_to_model_space (AnimationGridAnimation *grid_animation, + AnimationVector *uv, + AnimationVector *model_space_point); + +void animation_grid_animation_resolution (AnimationGridAnimation *grid_animation, + AnimationVector *out_resolution); + +void animation_grid_animation_extremes (AnimationGridAnimation *grid_animation, + AnimationVector const *corners, + AnimationVector4D *out_extremes); + +AnimationVector4D * animation_grid_animation_alloc_extremes (AnimationGridAnimation *grid_animation, + AnimationVector const *corners); + +G_END_DECLS diff --git a/animation-glib/grid/meson.build b/animation-glib/grid/meson.build new file mode 100644 index 0000000..eeaf92a --- /dev/null +++ b/animation-glib/grid/meson.build @@ -0,0 +1,32 @@ +# animation/grid/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (grid animation base component GObject interface) + +grid_introspectable_sources = files([ + 'grid.cpp' +]) + +grid_headers = files([ + 'grid.h' +]) + +animation_glib_introspectable_sources += grid_introspectable_sources +animation_glib_headers += grid_headers + +install_headers(grid_headers, subdir: join_paths(animation_glib_headers_subdir, 'grid')) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 5c588a2..1ec0bf2 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -46,6 +46,7 @@ animation_glib_headers_subdir = 'animation-glib' subdir('bounce') subdir('glide') +subdir('grid') subdir('query') subdir('stepper') subdir('transform') diff --git a/animation/grid/grid.h b/animation/grid/grid.h new file mode 100644 index 0000000..fd082aa --- /dev/null +++ b/animation/grid/grid.h @@ -0,0 +1,46 @@ +/* + * animation/grid/grid.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Interface definition for animations that perform + * a 2D grid based deformation of surface unit co-ordinates. + */ + +#include + +#include + +#pragma once + +namespace animation +{ + namespace grid + { + class GridAnimation + { + public: + + virtual ~GridAnimation() {}; + virtual animation::Point DeformUVToModelSpace (animation::Point const &) const = 0; + virtual animation::geometry::PointModel Resolution () const = 0; + virtual float Progress () const = 0; + virtual bool Step (unsigned int ms) = 0; + virtual std::array Extremes (std::array const &corners) const = 0; + }; + } +} diff --git a/animation/grid/meson.build b/animation/grid/meson.build new file mode 100644 index 0000000..51ffe0a --- /dev/null +++ b/animation/grid/meson.build @@ -0,0 +1,27 @@ +# animation/grid/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (grid animation base class component). + +grid_headers = files([ + 'grid.h' +]) + +animation_headers += grid_headers + +install_headers(grid_headers, subdir: join_paths(animation_headers_subdir, 'grid')) diff --git a/animation/meson.build b/animation/meson.build index 1a4f511..1eb542d 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -26,6 +26,7 @@ animation_headers_subdir = 'animation' subdir('bounce') subdir('glide') +subdir('grid') subdir('query') subdir('stepper') subdir('transform') From 7ef64586fa3fa49a94cb571cd579e8b1d817f070 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 22 Aug 2018 09:30:50 +0800 Subject: [PATCH 29/60] animation: Add magic lamp animation This animation simulates the "genie" effect on another popular operating system, appearing to "suck" the window into a destination rectangle. The way it works is a little complicated. Basically, we subdivide the surface into a very high resolution grid and from each point in the source geometry to each point in the target geometry, we we have a sigmoid function (S-curve, eg e^x / (1 + ke^px) that each grid point travels down towards its destination. When deforming a UV into the pre-computed grid we must do a linear interpolation along both axes, finding the nearest subdivision source point on a UV mesh, then interpolating into that mesh component on the scene-relative internal mesh. https://phabricator.endlessm.com/T23697 --- animation-glib/magiclamp/magiclamp.cpp | 455 +++++++++++++++++++ animation-glib/magiclamp/magiclamp.h | 73 +++ animation-glib/magiclamp/meson.build | 32 ++ animation-glib/meson.build | 1 + animation/magiclamp/magiclamp.cpp | 353 ++++++++++++++ animation/magiclamp/magiclamp.h | 74 +++ animation/magiclamp/meson.build | 32 ++ animation/meson.build | 1 + tests/js/meson.build | 1 + tests/js/testMagicLampAnimation.js | 107 +++++ tests/magiclamp/magiclamp_animation_test.cpp | 251 ++++++++++ tests/meson.build | 1 + 12 files changed, 1381 insertions(+) create mode 100644 animation-glib/magiclamp/magiclamp.cpp create mode 100644 animation-glib/magiclamp/magiclamp.h create mode 100644 animation-glib/magiclamp/meson.build create mode 100644 animation/magiclamp/magiclamp.cpp create mode 100644 animation/magiclamp/magiclamp.h create mode 100644 animation/magiclamp/meson.build create mode 100644 tests/js/testMagicLampAnimation.js create mode 100644 tests/magiclamp/magiclamp_animation_test.cpp diff --git a/animation-glib/magiclamp/magiclamp.cpp b/animation-glib/magiclamp/magiclamp.cpp new file mode 100644 index 0000000..689a3af --- /dev/null +++ b/animation-glib/magiclamp/magiclamp.cpp @@ -0,0 +1,455 @@ +/* + * animation-glib/magiclamp/magiclamp.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject implementation for a "magiclamp" animation. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ag = animation::grid; +namespace agd = animation::geometry::dimension; +namespace age = animation::geometry; +namespace agl = animation::glib; +namespace aml = animation::magiclamp; +namespace asg = animation::stepper::glib; + +struct _AnimationMagicLampAnimation +{ + AnimationGridAnimation parent_instance; +}; + +typedef struct _AnimationMagicLampAnimationPrivate +{ +} AnimationMagicLampAnimationPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationMagicLampAnimation, + animation_magiclamp_animation, + ANIMATION_TYPE_GRID_ANIMATION) + +enum { + PROP_0, + PROP_SOURCE, + PROP_TARGET, + PROP_RESOLUTION, + PROP_BEND_FACTOR, + PROP_OFFSET_FACTOR, + PROP_STRETCH_FACTOR, + PROP_DEFORM_SPEED_FACTOR, + PROP_STEPPER, + NPROPS +}; + +static GParamSpec *animation_magiclamp_animation_props [NPROPS] = { NULL, }; + +void +animation_magiclamp_animation_set_bend_factor (AnimationMagicLampAnimation *animation, + double bend_factor) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->BendFactor (bend_factor); +} + +double +animation_magiclamp_animation_get_bend_factor (AnimationMagicLampAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->BendFactor (); +} + +void +animation_magiclamp_animation_set_offset_factor (AnimationMagicLampAnimation *animation, + double offset_factor) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->OffsetFactor (offset_factor); +} + +double +animation_magiclamp_animation_get_offset_factor (AnimationMagicLampAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->OffsetFactor (); +} + +void +animation_magiclamp_animation_set_stretch_factor (AnimationMagicLampAnimation *animation, + double stretch_factor) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->StretchFactor (stretch_factor); +} + +double +animation_magiclamp_animation_get_stretch_factor (AnimationMagicLampAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->StretchFactor (); +} + +void +animation_magiclamp_animation_set_deform_speed_factor (AnimationMagicLampAnimation *animation, + double deform_speed_factor) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->DeformSpeedFactor (deform_speed_factor); +} + +double +animation_magiclamp_animation_get_deform_speed_factor (AnimationMagicLampAnimation *animation) +{ + return LookupTypedInterfaceProp (G_OBJECT (animation))->DeformSpeedFactor (); +} + +void +animation_magiclamp_animation_set_resolution (AnimationMagicLampAnimation *animation, + AnimationVector *vector) +{ + auto resolution = age::PointModel (vector->x, vector->y); + LookupTypedInterfaceProp (G_OBJECT (animation))->GridResolution (resolution); +} + +/** + * animation_magiclamp_animation_get_resolution: + * @animation: An #AnimationMagicLampAnimation + * @vector: (out): The #AnimationVector to write the resolution into. + */ +void +animation_magiclamp_animation_get_resolution (AnimationMagicLampAnimation *animation, + AnimationVector *vector) +{ + auto resolution = LookupTypedInterfaceProp (G_OBJECT (animation))->GridResolution (); + + vector->x = static_cast (resolution.x); + vector->y = static_cast (resolution.y); +} + +void +animation_magiclamp_animation_set_source (AnimationMagicLampAnimation *animation, + AnimationBoxQuery *source) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Source (std::make_shared (source)); +} + +/** + * animation_magiclamp_animation_get_source: + * @animation: A #AnimationMagicLampAnimation + * + * Returns: (transfer none): Get the #AnimationBoxQuery source for this #AnimationMagicLampAnimation + */ +AnimationBoxQuery * +animation_magiclamp_animation_get_source (AnimationMagicLampAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Source ())->BoxQuery (); +} + +void +animation_magiclamp_animation_set_target (AnimationMagicLampAnimation *animation, + AnimationBoxQuery *target) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Target (std::make_shared (target)); +} + +/** + * animation_magiclamp_animation_get_target: + * @animation: A #AnimationMagicLampAnimation + * + * Returns: (transfer none): Get the #AnimationBoxQuery target for this #AnimationMagicLampAnimation + */ +AnimationBoxQuery * +animation_magiclamp_animation_get_target (AnimationMagicLampAnimation *animation) +{ + return std::static_pointer_cast (LookupTypedInterfaceProp (G_OBJECT (animation))->Target ())->BoxQuery (); +} + +void +animation_magiclamp_animation_set_stepper (AnimationMagicLampAnimation *animation, + AnimationStepper *stepper) +{ + LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper (std::make_shared (stepper)); +} + +/** + * animation_magiclamp_animation_get_stepper: + * @animation: An #AnimationMagicLampAnimation + * + * Returns: (transfer full): Get the stepper for this #AnimationMagicLampAnimation + */ +AnimationStepper * +animation_magiclamp_animation_get_stepper (AnimationMagicLampAnimation *animation) +{ + auto const &stepper (LookupTypedInterfaceProp (G_OBJECT (animation))->Stepper ()); + + return std::static_pointer_cast (stepper)->BaseStepper (); +} + +static void +animation_magiclamp_animation_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationMagicLampAnimation *magiclamp_animation = ANIMATION_MAGIC_LAMP_ANIMATION (object); + + switch (prop_id) + { + case PROP_SOURCE: + animation_magiclamp_animation_set_source (magiclamp_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_TARGET: + animation_magiclamp_animation_set_target (magiclamp_animation, + ANIMATION_BOX_QUERY (g_value_get_object (value))); + break; + case PROP_RESOLUTION: + animation_magiclamp_animation_set_resolution (magiclamp_animation, + reinterpret_cast (g_value_get_boxed (value))); + break; + case PROP_BEND_FACTOR: + animation_magiclamp_animation_set_bend_factor (magiclamp_animation, g_value_get_double (value)); + break; + case PROP_OFFSET_FACTOR: + animation_magiclamp_animation_set_offset_factor (magiclamp_animation, g_value_get_double (value)); + break; + case PROP_STRETCH_FACTOR: + animation_magiclamp_animation_set_stretch_factor (magiclamp_animation, g_value_get_double (value)); + break; + case PROP_DEFORM_SPEED_FACTOR: + animation_magiclamp_animation_set_deform_speed_factor (magiclamp_animation, g_value_get_double (value)); + break; + case PROP_STEPPER: + animation_magiclamp_animation_set_stepper (magiclamp_animation, + ANIMATION_STEPPER (g_value_get_object (value))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_magiclamp_animation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationMagicLampAnimation *magiclamp_animation = ANIMATION_MAGIC_LAMP_ANIMATION (object); + + switch (prop_id) + { + case PROP_SOURCE: + g_value_set_object (value, animation_magiclamp_animation_get_source (magiclamp_animation)); + break; + case PROP_TARGET: + g_value_set_object (value, animation_magiclamp_animation_get_target (magiclamp_animation)); + break; + case PROP_RESOLUTION: + { + AnimationVector vector; + animation_magiclamp_animation_get_resolution (magiclamp_animation, &vector); + g_value_set_boxed (value, reinterpret_cast (&vector)); + } + break; + case PROP_BEND_FACTOR: + g_value_set_double (value, animation_magiclamp_animation_get_bend_factor (magiclamp_animation)); + break; + case PROP_STRETCH_FACTOR: + g_value_set_double (value, animation_magiclamp_animation_get_stretch_factor (magiclamp_animation)); + break; + case PROP_OFFSET_FACTOR: + g_value_set_double (value, animation_magiclamp_animation_get_offset_factor (magiclamp_animation)); + break; + case PROP_DEFORM_SPEED_FACTOR: + g_value_set_double (value, animation_magiclamp_animation_get_deform_speed_factor (magiclamp_animation)); + break; + case PROP_STEPPER: + g_value_set_object (value, animation_magiclamp_animation_get_stepper (magiclamp_animation)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static GObject * +animation_magiclamp_animation_constructor (GType type, + unsigned int n_construct_params, + GObjectConstructParam *construct_params) +{ + auto *interface = InterfaceConstructor ::construct (); + auto *grid_interface = static_cast (interface); + + /* We need to also set defaults for certain properties in order + * to ensure that they are bindable later on, in case they are + * not set by the caller. */ + replace_named_pointer_props_in_construct_params_if_null ( + construct_params, + n_construct_params, + { + ReplacePropSpec ("stepper", g_value_get_object, g_value_take_object, []() -> gpointer { + return animation_linear_stepper_new (1); + }), + ReplacePropSpec ("source", g_value_get_object, g_value_take_object, []() -> gpointer { + return animation_box_query_new (); + }), + ReplacePropSpec ("target", g_value_get_object, g_value_take_object, []() -> gpointer { + return animation_box_query_new (); + }) + } + ); + + replace_interface_prop_in_construct_params (construct_params, + n_construct_params, + grid_interface); + + return G_OBJECT_CLASS (animation_magiclamp_animation_parent_class)->constructor (type, + n_construct_params, + construct_params); +} + +static void +animation_magiclamp_animation_constructed (GObject *object) +{ + /* Take a size-zero step, which ensures that we update the + * internal state of the animation with all the properties we + * just set */ + animation_grid_animation_step (ANIMATION_GRID_ANIMATION (object), 0); +} + +static void +animation_magiclamp_animation_init (AnimationMagicLampAnimation *model) +{ +} + + +static void +animation_magiclamp_animation_class_init (AnimationMagicLampAnimationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = animation_magiclamp_animation_constructor; + object_class->constructed = animation_magiclamp_animation_constructed; + object_class->get_property = animation_magiclamp_animation_get_property; + object_class->set_property = animation_magiclamp_animation_set_property; + + animation_magiclamp_animation_props[PROP_SOURCE] = + g_param_spec_object ("source", + "Source Box", + "BoxQuery for box that we are animating from", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_TARGET] = + g_param_spec_object ("target", + "Target Box", + "BoxQuery for box that we are animating to", + ANIMATION_TYPE_BOX_QUERY, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_RESOLUTION] = + g_param_spec_boxed ("resolution", + "Resolution", + "Grid Resolution", + ANIMATION_TYPE_VECTOR, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_BEND_FACTOR] = + g_param_spec_double ("bend-factor", + "Bend Factor", + "How much the window should bend", + 1.0, + 20.0, + 10.0, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_OFFSET_FACTOR] = + g_param_spec_double ("offset-factor", + "Offset Factor", + "How big the curves of the animation should be", + 0.1, + 1.0, + 0.5, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_STRETCH_FACTOR] = + g_param_spec_double ("stretch-factor", + "Stretch Factor", + "How much the window should stretch when animating", + 0.2, + 1.0, + 0.45, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_DEFORM_SPEED_FACTOR] = + g_param_spec_double ("deform-speed-factor", + "Deform Speed Factor", + "How quickly the deformation phase should happen", + 1.0, + 4.0, + 2.3, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + animation_magiclamp_animation_props[PROP_STEPPER] = + g_param_spec_object ("stepper", + "Stepper", + "Stepper to use to progress the animation", + ANIMATION_TYPE_STEPPER, + static_cast (G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_properties (object_class, + NPROPS, + animation_magiclamp_animation_props); +} + +/** + * animation_magiclamp_new: + * @source_box: The #AnimationBoxQuery that we are animating from. + * @target_box: The #AnimationBoxQuery that we are animating to. + * @resolution: The #AnimationVector representing the grid resolution + * @bend_factor: How much the window should bend + * @offset_factor: How big the curves of the animation should be + * @deform_speed_factor: How quickly the deformation should complete. + * @stepper: An #AnimationStepper used for progressing the animation. + * + * Returns: (transfer full): A new #AnimationMagicLampAnimation. + */ +AnimationMagicLampAnimation * +animation_magiclamp_new (const AnimationBoxQuery *source_box, + const AnimationBoxQuery *target_box, + const AnimationVector *resolution, + double bend_factor, + double offset_factor, + double stretch_factor, + double deform_speed_factor, + AnimationStepper *stepper) +{ + return ANIMATION_MAGIC_LAMP_ANIMATION (g_object_new (ANIMATION_TYPE_MAGIC_LAMP_ANIMATION, + "source", source_box, + "target", target_box, + "resolution", resolution, + "bend-factor", bend_factor, + "offset-factor", offset_factor, + "stretch-factor", stretch_factor, + "deform-speed-factor", deform_speed_factor, + "stepper", stepper, + NULL)); +} diff --git a/animation-glib/magiclamp/magiclamp.h b/animation-glib/magiclamp/magiclamp.h new file mode 100644 index 0000000..674289a --- /dev/null +++ b/animation-glib/magiclamp/magiclamp.h @@ -0,0 +1,73 @@ +/* + * animation-glib/magiclamp/magiclamp.h + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * GObject Interface for "magiclamp" animation. + */ +#pragma once + +#include + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_TYPE_MAGIC_LAMP_ANIMATION animation_magiclamp_animation_get_type () +G_DECLARE_FINAL_TYPE (AnimationMagicLampAnimation, animation_magiclamp_animation, ANIMATION, MAGIC_LAMP_ANIMATION, AnimationGridAnimation) + +void animation_magiclamp_animation_set_bend_factor (AnimationMagicLampAnimation *animation, + double bend_factor); +double animation_magiclamp_animation_get_bend_factor (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_offset_factor (AnimationMagicLampAnimation *animation, + double offset_factor); +double animation_magiclamp_animation_get_offset_factor (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_stretch_factor (AnimationMagicLampAnimation *animation, + double stretch_factor); +double animation_magiclamp_animation_get_stretch_factor (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_deform_speed_factor (AnimationMagicLampAnimation *animation, + double deform_speed_factor); +double animation_magiclamp_animation_get_deform_speed_factor (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_source (AnimationMagicLampAnimation *animation, + AnimationBoxQuery *source); +AnimationBoxQuery * animation_magiclamp_animation_get_source (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_target (AnimationMagicLampAnimation *animation, + AnimationBoxQuery *target); +AnimationBoxQuery * animation_magiclamp_animation_get_target (AnimationMagicLampAnimation *animation); + +void animation_magiclamp_animation_set_stepper (AnimationMagicLampAnimation *animation, + AnimationStepper *stepper); +AnimationStepper * animation_magiclamp_animation_get_stepper (AnimationMagicLampAnimation *animation); + +AnimationMagicLampAnimation * animation_magiclamp_new (const AnimationBox *source_box, + const AnimationBox *target_box, + const AnimationVector *resolution, + double bend_factor, + double offset_factor, + double stretch_factor, + double deform_speed_factor, + AnimationStepper *stepper); + +G_END_DECLS diff --git a/animation-glib/magiclamp/meson.build b/animation-glib/magiclamp/meson.build new file mode 100644 index 0000000..0e9e139 --- /dev/null +++ b/animation-glib/magiclamp/meson.build @@ -0,0 +1,32 @@ +# /animation-glib/magiclamp/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (magiclamp animation component), GObject binding. + +magiclamp_introspectable_sources = files([ + 'magiclamp.cpp' +]) + +magiclamp_headers = files([ + 'magiclamp.h' +]) + +animation_glib_introspectable_sources += magiclamp_introspectable_sources +animation_glib_headers += magiclamp_headers + +install_headers(magiclamp_headers, subdir: join_paths(animation_glib_headers_subdir, 'magiclamp')) diff --git a/animation-glib/meson.build b/animation-glib/meson.build index 1ec0bf2..2238e3c 100644 --- a/animation-glib/meson.build +++ b/animation-glib/meson.build @@ -47,6 +47,7 @@ animation_glib_headers_subdir = 'animation-glib' subdir('bounce') subdir('glide') subdir('grid') +subdir('magiclamp') subdir('query') subdir('stepper') subdir('transform') diff --git a/animation/magiclamp/magiclamp.cpp b/animation/magiclamp/magiclamp.cpp new file mode 100644 index 0000000..2fff3e0 --- /dev/null +++ b/animation/magiclamp/magiclamp.cpp @@ -0,0 +1,353 @@ +/* + * animation/magiclamp/magiclamp.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to magiclamp onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace abc = animation::box_calculation; +namespace ag = animation::geometry; +namespace agd = animation::geometry::dimension; +namespace agr = animation::grid; +namespace am = animation::math; +namespace aml = animation::magiclamp; +namespace atc = animation::transform_calculation; + +namespace +{ + std::vector InitializeGridObjects (ag::PointModel const &resolution) + { + size_t const gridWidth = agd::get <0> (resolution); + size_t const gridHeight = agd::get <1> (resolution); + return std::vector (gridWidth * gridHeight); + } + + float Sigmoid (float x, float bend, float offset) + { + /* A slightly stronger sigmoid curve */ + return (1.0 / (1.0 + exp (-bend * ((x) - offset)))); + } + + /* Moves from source to target, with the assumption that target + * represents the normal geometry of the surface + * + * This animation simulates the "genie" effect on another popular + * operating system, appearing to "suck" the window into a destination + * rectangle. + * + * The way it works is a little complicated. Basically, we subdivide + * the surface into a very high resolution grid and from each point + * in the source geometry to each point in the target geometry, we + * we have a sigmoid function (S-curve, eg e^x / (1 + ke^px) that each + * grid point travels down towards its destination. */ + void MoveGridVerticallyTowardsTargetWithXSigmoidDeformation (animation::Box const &source, + animation::Box const &target, + std::vector &grid, + ag::PointModel const &gridResolution, + float bendFactor, + float offsetFactor, + float stretchFactor, + float deformSpeedFactor, + float progress) + { + size_t const gridWidth = agd::get <0> (gridResolution); + size_t const gridHeight = agd::get <1> (gridResolution); + float const sourceWidth = agd::get <0> (source.bottomRight ()) - agd::get <0> (source.topLeft ()); + float const sourceHeight = agd::get <1> (source.bottomRight ()) - agd::get <1> (source.topLeft ()); + float const targetWidth = agd::get <0> (target.bottomRight ()) - agd::get <0> (target.topLeft ()); + float const targetHeight = agd::get <1> (target.bottomRight ()) - agd::get <1> (target.topLeft ()); + + animation::Point topLeftTargetPoint (target.topLeft ()); + animation::Point topLeftSourcePoint (source.topLeft ()); + float const topLeftTargetY = agd::get <1> (topLeftTargetPoint); + float const topLeftSourceY = agd::get <1> (topLeftSourcePoint); + float const yDistance = topLeftSourceY - topLeftTargetY; + float const invProgress = 1.0f - progress; + + float const sigmoid0 = Sigmoid (0.0f, bendFactor, offsetFactor); + float const sigmoid1 = Sigmoid (1.0f, bendFactor, offsetFactor); + float const sigmoid1SubSigmoid0 = sigmoid1 - sigmoid0; + + for (size_t j = 0; j < gridHeight; ++j) + { + for (size_t i = 0; i < gridWidth; ++i) + { + animation::Point sourcePoint (agd::get <0> (source.topLeft ()) + + i * sourceWidth / (gridWidth - 1), + agd::get <1> (source.topLeft ()) + + j * sourceHeight / (gridHeight - 1)); + animation::Point targetPoint (agd::get <0> (target.topLeft ()) + + i * targetWidth / (gridWidth - 1), + agd::get <1> (target.topLeft ()) + + j * targetHeight / (gridHeight - 1)); + + /* On the y-axis the grid object moves linearly towards + * its y-destination on the target */ + float yStretchFactor = 1.0f / (1.0f - (j / static_cast (gridHeight - 1)) * stretchFactor); + float yStretchProgress = std::min (invProgress * yStretchFactor, 1.0f); + float yDelta = agd::get <1> (sourcePoint) - agd::get <1> (targetPoint); + float stretchedYCoord = agd::get <1> (targetPoint) + yDelta * yStretchProgress; + float xDeformationProgress = std::min (1.0f - ((topLeftSourceY - stretchedYCoord) / yDistance), 1.0f) * + std::min (invProgress * deformSpeedFactor, 1.0f); + float xDeformationFactor = (Sigmoid (xDeformationProgress, bendFactor, offsetFactor) - sigmoid0) / sigmoid1SubSigmoid0; + float stretchedXCoord = agd::get <0> (targetPoint) + + (agd::get <0> (sourcePoint) - agd::get <0> (targetPoint)) * + xDeformationFactor; + + agd::set <0> (grid[j * gridWidth + i], stretchedXCoord); + agd::set <1> (grid[j * gridWidth + i], stretchedYCoord); + } + } + } + + /* When deforming a UV into the pre-computed grid we must do a linear + * interpolation along both axes, finding the nearest subdivision source + * point on a UV mesh, then interpolating into that mesh component + * on the scene-relative internal mesh. */ + animation::Point InterpolateIntoGrid (animation::Point const &uv, + ag::PointModel const &gridResolution, + std::vector const &grid) + { + animation::Point clampedUV (am::clamp (agd::get <0> (uv), 0.0, 1.0), + am::clamp (agd::get <1> (uv), 0.0, 1.0)); + size_t const gridWidth = agd::get <0> (gridResolution); + size_t const gridHeight = agd::get <1> (gridResolution); + size_t nearestLeftX = static_cast (agd::get <0> (uv) * (gridWidth - 1)); + size_t nearestTopY = static_cast (agd::get <1> (uv) * (gridHeight - 1)); + + size_t nearestRightX = std::min (gridWidth - 1, nearestLeftX + 1); + size_t nearestBottomY = std::min (gridHeight - 1, nearestTopY + 1); + + float unitGridCellWidth = 1.0f / (gridWidth - 1); + float unitGridCellHeight = 1.0f / (gridHeight - 1); + + float xUnit = (agd::get <0> (uv) - unitGridCellWidth * nearestLeftX) / unitGridCellWidth; + float yUnit = (agd::get <1> (uv) - unitGridCellHeight * nearestTopY) / unitGridCellHeight; + + float xGridInterpLeft = agd::get <0> (grid[nearestLeftX + nearestTopY * gridWidth]) * (1.0f - yUnit) + + agd::get <0> (grid[nearestLeftX + nearestBottomY * gridWidth]) * yUnit; + float xGridInterpRight = agd::get <0> (grid[nearestRightX + nearestTopY * gridWidth]) * (1.0f - yUnit) + + agd::get <0> (grid[nearestRightX + nearestBottomY * gridWidth]) * yUnit; + float xGridInterp = xGridInterpLeft + (xGridInterpRight - xGridInterpLeft) * xUnit; + + float yGridInterpTop = agd::get <1> (grid[nearestLeftX + nearestTopY * gridWidth]) * (1.0f - xUnit) + + agd::get <1> (grid[nearestRightX + nearestTopY * gridWidth]) * xUnit; + float yGridInterpBottom = agd::get <1> (grid[nearestLeftX + nearestBottomY * gridWidth]) * (1.0f - xUnit) + + agd::get <1> (grid[nearestRightX + nearestBottomY * gridWidth]) * xUnit; + float yGridInterp = yGridInterpTop + (yGridInterpBottom - yGridInterpTop) * yUnit; + + return animation::Point (xGridInterp, yGridInterp); + } +} + +namespace animation +{ + namespace magiclamp + { + struct MagicLampAnimation::Private + { + Private (animation::BoxQuery ::Shared const &source, + animation::BoxQuery ::Shared const &target, + ag::PointModel const &resolution, + float bendFactor, + float offsetFactor, + float stretchFactor, + float deformSpeedFactor, + animation::stepper::Stepper::Shared const &stepper); + + animation::BoxQuery ::Shared sourceQuery; + animation::BoxQuery ::Shared targetQuery; + + std::vector grid; + ag::PointModel gridResolution; + + float bendFactor; + float offsetFactor; + float stretchFactor; + float deformSpeedFactor; + + float progress; + + animation::stepper::Stepper::Shared stepper; + }; + + MagicLampAnimation::Private::Private (animation::BoxQuery ::Shared const &sourceQuery, + animation::BoxQuery ::Shared const &targetQuery, + ag::PointModel const &resolution, + float bendFactor, + float offsetFactor, + float stretchFactor, + float deformSpeedFactor, + animation::stepper::Stepper::Shared const &stepper) : + sourceQuery (sourceQuery), + targetQuery (targetQuery), + grid (InitializeGridObjects (resolution)), + gridResolution (resolution), + bendFactor (bendFactor), + offsetFactor (offsetFactor), + stretchFactor (stretchFactor), + deformSpeedFactor (deformSpeedFactor), + progress ((*stepper) (0)), + stepper (stepper) + { + } + } +} + +std::array +aml::MagicLampAnimation::Extremes (std::array const &corners) const +{ + size_t const gridWidth = agd::get <0> (priv->gridResolution); + size_t const gridHeight = agd::get <1> (priv->gridResolution); + + size_t topLeftIndex = 0; + size_t topRightIndex = gridWidth - 1; + size_t bottomLeftIndex = (gridHeight - 1) * gridWidth; + size_t bottomRightIndex = bottomLeftIndex + gridWidth - 1; + + return std::array { + animation::Vector4D (agd::get <0> (priv->grid[topLeftIndex]), + agd::get <1> (priv->grid[topLeftIndex]), 0, 1), + animation::Vector4D (agd::get <0> (priv->grid[topRightIndex]), + agd::get <1> (priv->grid[topRightIndex]), 0, 1), + animation::Vector4D (agd::get <0> (priv->grid[bottomLeftIndex]), + agd::get <1> (priv->grid[bottomLeftIndex]), 0, 1), + animation::Vector4D (agd::get <0> (priv->grid[bottomRightIndex]), + agd::get <1> (priv->grid[bottomRightIndex]), 0, 1) + }; +} + +float +aml::MagicLampAnimation::Progress () const +{ + return priv->progress; +} + +bool +aml::MagicLampAnimation::Step (unsigned int ms) +{ + priv->progress = am::clamp ((*priv->stepper) (ms), 0.0f, 1.0f); + + auto source = priv->sourceQuery->Geometry (); + auto target = priv->targetQuery->Geometry (); + + MoveGridVerticallyTowardsTargetWithXSigmoidDeformation (source, + target, + priv->grid, + priv->gridResolution, + priv->bendFactor, + priv->offsetFactor, + priv->stretchFactor, + priv->deformSpeedFactor, + priv->progress); + + return priv->progress != 0.0f && priv->progress != 1.0f; +} + +animation::Point +aml::MagicLampAnimation::DeformUVToModelSpace (animation::Point const &uv) const +{ + return InterpolateIntoGrid (uv, priv->gridResolution, priv->grid); +} + +animation::geometry::PointModel +aml::MagicLampAnimation::Resolution () const +{ + return priv->gridResolution; +} + +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, Source, animation::BoxQuery ::Shared, priv->sourceQuery) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, Target, animation::BoxQuery ::Shared, priv->targetQuery) +ANIMATION_DEFINE_PROPERTY_WITH_HOOKS (aml::MagicLampAnimation, + GridResolution, + animation::geometry::PointModel , + [this]() -> animation::geometry::PointModel const & { + return this->priv->gridResolution; + }, + [this](animation::geometry::PointModel const &v) { + /* Once we update the grid resolution, we also need to re-initialize + * the stored grid and adjust its objects for the animation state, because + * priv->gridResolution and priv->grid must agree on the grid layout */ + this->priv->gridResolution = v; + this->priv->grid = InitializeGridObjects (this->priv->gridResolution); + this->Step (0); + }) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, BendFactor, float, priv->bendFactor) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, OffsetFactor, float, priv->offsetFactor) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, StretchFactor, float, priv->stretchFactor) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, DeformSpeedFactor, float, priv->deformSpeedFactor) +ANIMATION_DEFINE_PROPERTY (aml::MagicLampAnimation, Stepper, animation::stepper::Stepper::Shared, priv->stepper) + +aml::MagicLampAnimation::MagicLampAnimation (animation::BoxQuery ::Shared const &source, + animation::BoxQuery ::Shared const &target, + ag::PointModel const &resolution, + float bendFactor, + float offsetFactor, + float stretchFactor, + float deformSpeedFactor, + animation::stepper::Stepper::Shared const &stepper) : + priv (new aml::MagicLampAnimation::Private (source, + target, + resolution, + bendFactor, + offsetFactor, + stretchFactor, + deformSpeedFactor, + stepper)) +{ + Step (0); +} + +aml::MagicLampAnimation::MagicLampAnimation () : + MagicLampAnimation (animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + animation::MakeStaticBoxQuery (animation::Box (animation::Point (0, 0), + animation::Point (1, 1))), + ag::PointModel (1, 1), + 1.0, + 1.0, + 1.0, + 1.0, + animation::stepper::Linear (1)) +{ +} + +aml::MagicLampAnimation::~MagicLampAnimation () +{ +} diff --git a/animation/magiclamp/magiclamp.h b/animation/magiclamp/magiclamp.h new file mode 100644 index 0000000..e6d5d8d --- /dev/null +++ b/animation/magiclamp/magiclamp.h @@ -0,0 +1,74 @@ +/* + * animation/magiclamp/magiclamp.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * An animation that causes a surface to bounce onto screen, gently + * following an attenuating sine wave. + */ + +#include +#include + +#include +#include +#include +#include +#include + +#pragma once + +namespace animation +{ + namespace magiclamp + { + class MagicLampAnimation : + public animation::grid::GridAnimation + { + public: + + MagicLampAnimation (); + MagicLampAnimation (animation::BoxQuery ::Shared const &source, + animation::BoxQuery ::Shared const &target, + animation::geometry::PointModel const &resolution, + float bendFactor, + float offsetFactor, + float stretchFactor, + float deformSpeedFactor, + animation::stepper::Stepper::Shared const &stepper); + ~MagicLampAnimation (); + + animation::Point DeformUVToModelSpace (animation::Point const &) const; + animation::geometry::PointModel Resolution () const; + float Progress () const; + bool Step (unsigned int ms); + std::array Extremes (std::array const &corners) const; + + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, Source, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, Target, animation::BoxQuery ::Shared) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, GridResolution, animation::geometry::PointModel ) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, BendFactor, float) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, OffsetFactor, float) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, StretchFactor, float) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, DeformSpeedFactor, float) + ANIMATION_DECLARE_PROPERTY (MagicLampAnimation, Stepper, animation::stepper::Stepper::Shared) + + private: + + struct Private; + std::unique_ptr priv; + }; + } +} diff --git a/animation/magiclamp/meson.build b/animation/magiclamp/meson.build new file mode 100644 index 0000000..97a87d6 --- /dev/null +++ b/animation/magiclamp/meson.build @@ -0,0 +1,32 @@ +# /animation/magiclamp/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Build the libanimation library (magiclamp animation component). + +magiclamp_sources = files([ + 'magiclamp.cpp' +]) + +magiclamp_headers = files([ + 'magiclamp.h' +]) + +animation_sources += magiclamp_sources +animation_headers += magiclamp_headers + +install_headers(magiclamp_headers, subdir: join_paths(animation_headers_subdir, 'magiclamp')) diff --git a/animation/meson.build b/animation/meson.build index 1eb542d..b103b8f 100644 --- a/animation/meson.build +++ b/animation/meson.build @@ -27,6 +27,7 @@ animation_headers_subdir = 'animation' subdir('bounce') subdir('glide') subdir('grid') +subdir('magiclamp') subdir('query') subdir('stepper') subdir('transform') diff --git a/tests/js/meson.build b/tests/js/meson.build index 4746254..5f3b021 100644 --- a/tests/js/meson.build +++ b/tests/js/meson.build @@ -21,6 +21,7 @@ javascript_tests = [ 'testBounceAnimation.js', 'testGlideAnimation.js', + 'testMagicLampAnimation.js', 'testStepper.js', 'testZoomAnimation.js' ] diff --git a/tests/js/testMagicLampAnimation.js b/tests/js/testMagicLampAnimation.js new file mode 100644 index 0000000..949e7d2 --- /dev/null +++ b/tests/js/testMagicLampAnimation.js @@ -0,0 +1,107 @@ +/* + * /tests/js/testMagicLampAnimation.js + * + * Tests for the JavaScript Binding to the Zoom Animation. + * + * Copyright (C) 2019 Sam Spilsbury. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +const { Animation } = imports.gi; +const { BoxWrapper } = imports.common; + +describe('Animation', function() { + describe('MagicLampAnimation', function() { + let animation; + let bendFactor = 10; + let offsetFactor = 0.5; + let stretchFactor = 0.45; + let deformationSpeedFactor = 2.3; + let resolution = new Animation.Vector({ x: 4, y: 4 }); + let source = new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 100, + y: 100, + }), + bottom_right: new Animation.Vector({ + x: 150, + y: 150, + }), + }), + }); + let target = new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: 0, + y: 0, + }), + bottom_right: new Animation.Vector({ + x: 100, + y: 100, + }), + }), + }); + + beforeEach(function() { + animation = new Animation.MagicLampAnimation({ + source: source, + target: target, + resolution: resolution, + bend_factor: bendFactor, + offset_factor: offsetFactor, + stretch_factor: stretchFactor, + deform_speed_factor: deformationSpeedFactor, + stepper: new Animation.LinearStepper({ + length: 200, + }), + }); + }); + + it('has expected properties set on construction', function() { + expect(animation.resolution.x).toEqual(4); + expect(animation.resolution.y).toEqual(4); + expect(animation.bend_factor).toBeCloseTo(bendFactor); + expect(animation.stretch_factor).toBeCloseTo(stretchFactor); + expect(animation.deform_speed_factor).toBeCloseTo(deformationSpeedFactor); + expect(animation.stepper.length).toEqual(200); + }); + + it('animates to correct position after length timesteps', function() { + expect(animation.step(200)).toBeFalsy(); + + let corners = [ + new Animation.Vector({ x: 0, y: 0 }), + new Animation.Vector({ x: 100, y: 0 }), + new Animation.Vector({ x: 0, y: 100 }), + new Animation.Vector({ x: 100, y: 100 }), + ]; + let extremes = animation.extremes (corners); + + expect(extremes[0].x).toBeCloseTo(0); + expect(extremes[0].y).toBeCloseTo(0); + + expect(extremes[1].x).toBeCloseTo(100); + expect(extremes[1].y).toBeCloseTo(0); + + expect(extremes[2].x).toBeCloseTo(0); + expect(extremes[2].y).toBeCloseTo(100); + + expect(extremes[3].x).toBeCloseTo(100); + expect(extremes[3].y).toBeCloseTo(100); + }); + }); +}); diff --git a/tests/magiclamp/magiclamp_animation_test.cpp b/tests/magiclamp/magiclamp_animation_test.cpp new file mode 100644 index 0000000..24e69ee --- /dev/null +++ b/tests/magiclamp/magiclamp_animation_test.cpp @@ -0,0 +1,251 @@ +/* + * tests/wobbly/magiclamp_animation_test.cpp + * + * Copyright 2018 Endless Mobile, Inc. + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Tests for the "magic lamp" animation. + */ +#include // for size_t +#include // for bind, __bind, _1 + +#include +#include +#include + +#include // for AtLeast +#include // for FunctionMocker, etc +#include // for AnythingMatcher, etc +#include // for EXPECT_CALL, etc +#include // for TEST_F, Test, Types, etc + +#include +#include +#include + +#include +#include +#include +#include +#include + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Eq; +using ::testing::Test; + +namespace am = animation::matchers; +namespace aml = animation::magiclamp; +namespace as = animation::stepper; + +namespace animation +{ + namespace matchers + { + template <> + struct AlmostEqTrait + { + typedef double ValueType; + + template + static bool apply (animation::Point const &lhs, + animation::Point const &rhs, + Comparator &&comparator) + { + return comparator (agd::get <0> (lhs), agd::get <0> (rhs)) && + comparator (agd::get <1> (lhs), agd::get <1> (rhs)); + } + }; + } +} + +namespace +{ + template + decltype(auto) BQ (animation::Box &&box) + { + return animation::MakeStaticBoxQuery (std::move (box)); + } + + template + decltype(auto) BQ (animation::Box const &box) + { + return animation::MakeStaticBoxQuery (box); + } + + static animation::geometry::PointModel const Resolution (4, 4); + static double const BendFactor = 10; + static double const OffsetFactor = 0.5; + static double const StretchFactor = 0.45; + static double const DeformationSpeedFactor = 2.3; + + TEST (MagicLampAnimation, AnimationIncompleteBeforeLengthTimesteps) + { + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Linear (200)); + + EXPECT_TRUE (anim.Step (199)); + } + + TEST (MagicLampAnimation, AnimationCompleteAfterLengthTimesteps) + { + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Linear (200)); + + EXPECT_FALSE (anim.Step (200)); + } + + TEST (MagicLampAnimation, StartsAtCorrectPosition) + { + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Linear (200)); + + std::array corners = {{ + animation::Point (0, 0), + animation::Point (100, 0), + animation::Point (0, 100), + animation::Point (100, 100) + }}; + std::array extremes = anim.Extremes (corners); + + EXPECT_THAT (extremes[0], am::Eq (animation::Vector4D (100, 100, 0, 1))); + EXPECT_THAT (extremes[1], am::Eq (animation::Vector4D (150, 100, 0, 1))); + EXPECT_THAT (extremes[2], am::Eq (animation::Vector4D (100, 150, 0, 1))); + EXPECT_THAT (extremes[3], am::Eq (animation::Vector4D (150, 150, 0, 1))); + } + + TEST (MagicLampAnimation, CorrectLinearInterpolatedVertexDeformation) + { + std::vector clientGrid; + clientGrid.reserve(128); + + double const x = 0; + double const y = 0; + double const width = 100; + double const height = 100; + + double const iconX = 100; + double const iconY = 100; + double const iconWidth = 50; + double const iconHeight = 50; + + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (iconX, iconY), + animation::Point (iconX + iconWidth, iconY + iconHeight))), + BQ (animation::Box (animation::Point (x, y), + animation::Point (width, height))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Linear (200)); + + size_t const gridWidth = 4; + size_t const gridHeight = 100; + + for (size_t i = 0; i < gridWidth; ++i) + { + for (size_t j = 0; j < gridHeight; ++j) + { + EXPECT_THAT (anim.DeformUVToModelSpace (animation::Point (i / static_cast (gridWidth - 1), + j / static_cast (gridHeight - 1))), + am::AlmostEq (animation::Point (iconX + (iconWidth / (gridWidth - 1)) * i, + iconY + (iconHeight / (gridHeight - 1)) * j), + 0.002)); + } + } + } + + TEST (MagicLampAnimation, AnimatesToCorrectPositionAfterLengthTimesteps) + { + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Linear (200)); + anim.Step (200); + + std::array corners = {{ + animation::Point (0, 0), + animation::Point (100, 0), + animation::Point (0, 100), + animation::Point (100, 100) + }}; + std::array extremes = anim.Extremes (corners); + + EXPECT_THAT (extremes[0], am::Eq (animation::Vector4D (0, 0, 0, 1))); + EXPECT_THAT (extremes[1], am::Eq (animation::Vector4D (100, 0, 0, 1))); + EXPECT_THAT (extremes[2], am::Eq (animation::Vector4D (0, 100, 0, 1))); + EXPECT_THAT (extremes[3], am::Eq (animation::Vector4D (100, 100, 0, 1))); + } + + TEST (MagicLampAnimation, AnimatesToCorrectPositionAfterLengthTimestepsReverse) + { + aml::MagicLampAnimation anim (BQ (animation::Box (animation::Point (100, 100), + animation::Point (150, 150))), + BQ (animation::Box (animation::Point (0, 0), + animation::Point (100, 100))), + Resolution, + BendFactor, + OffsetFactor, + StretchFactor, + DeformationSpeedFactor, + as::Reverse (as::Linear (200))); + anim.Step (200); + + std::array corners = {{ + animation::Point (0, 0), + animation::Point (100, 0), + animation::Point (0, 100), + animation::Point (100, 100) + }}; + std::array extremes = anim.Extremes (corners); + + EXPECT_THAT (extremes[0], am::Eq (animation::Vector4D (100, 100, 0, 1))); + EXPECT_THAT (extremes[1], am::Eq (animation::Vector4D (150, 100, 0, 1))); + EXPECT_THAT (extremes[2], am::Eq (animation::Vector4D (100, 150, 0, 1))); + EXPECT_THAT (extremes[3], am::Eq (animation::Vector4D (150, 150, 0, 1))); + } +} diff --git a/tests/meson.build b/tests/meson.build index e18793a..f12a990 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -25,6 +25,7 @@ animation_test_sources = [ 'glide/glide_animation_test.cpp', 'glm_ostream_operators.h', 'ostream_point_operator.h', + 'magiclamp/magiclamp_animation_test.cpp', 'wobbly/anchor_test.cpp', 'wobbly/constrainment_test.cpp', 'wobbly/euler_integration_test.cpp', From abc0141baceac23768c026d3f4356843d560441b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:27:54 +0300 Subject: [PATCH 30/60] animation-clutter: Add animation-clutter library and common helpers This library will be used to bind libanimation to Clutter, so that it can be used in GNOME-Shell. animation-clutter-common is a set of helper functions that are used to translate boxes into the correct space (eg, expanding paint boxes to cover the FBO size for a deform effect, determining the paint box extents of an actor, etc). --- .../animation-clutter-common-private.h | 31 +++ animation-clutter/animation-clutter-common.c | 253 ++++++++++++++++++ animation-clutter/animation-clutter-common.h | 47 ++++ animation-clutter/meson.build | 100 +++++++ meson.build | 1 + 5 files changed, 432 insertions(+) create mode 100644 animation-clutter/animation-clutter-common-private.h create mode 100644 animation-clutter/animation-clutter-common.c create mode 100644 animation-clutter/animation-clutter-common.h create mode 100644 animation-clutter/meson.build diff --git a/animation-clutter/animation-clutter-common-private.h b/animation-clutter/animation-clutter-common-private.h new file mode 100644 index 0000000..72fe04f --- /dev/null +++ b/animation-clutter/animation-clutter-common-private.h @@ -0,0 +1,31 @@ +/* + * animation-clutter/animation-clutter-common-private.h + * + * Copyright © 2013-2016 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +void animation_clutter_actor_box_enlarge_for_effects (ClutterActorBox *box); + +G_END_DECLS + diff --git a/animation-clutter/animation-clutter-common.c b/animation-clutter/animation-clutter-common.c new file mode 100644 index 0000000..8557e13 --- /dev/null +++ b/animation-clutter/animation-clutter-common.c @@ -0,0 +1,253 @@ +/* + * animation-clutter/animation-clutter-common.c + * + * Copyright © 2013-2018 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#include + +#include + +#include "animation-clutter-common.h" + +/* This code was borrowed from clutter-actor-box.c + * + * Copyright (c) 2006, 2007, 2008, OpenedHand Ltd. + * Copyright (c) 2009, 2010, 2011, 2012, Intel Corporation. + * + * The reason why we need this code is that for the wobbly effect, + * both the model and the renderer must agree on the geometry of the + * surface to be animated. In the case of clutter, ClutterDeformEffect + * is a subclass of ClutterOffscreenEffect, such that the actor is first + * painted into an FBO, then the FBO is rendered with the specified mesh. + * + * However, "behind our back", clutter will make the FBO a little bit + * bigger than the actual actor geometry, meaning that if we were to + * deform vertices as though the surface size was the same as the actor + * size, we would actually be "pinching" the FBO every so slightly, leading + * to an irritating visual glitch where the window gets slightly smaller + * as soon as it starts moving. + * + * Therefore, we tell clutter that the actor paint box is the normal actor + * paint box extents without any effects applied as usual, but we tell + * libanimation that the model is the same size that clutter would have + * computed the FBO to be, such that the FBO size and the model size + * agree and there is no "pinching" effect. + */ +static inline int +nearby_int (float x) +{ + return (int) (x < 0.0f ? x - 0.5f : x + 0.5f); +} + +void +animation_clutter_actor_box_enlarge_for_effects (ClutterActorBox *box) +{ + float width, height; + + /* The aim here is that for a given rectangle defined with floating point + * coordinates we want to determine a stable quantized size in pixels + * that doesn't vary due to the original box's sub-pixel position. + * + * The reason this is important is because effects will use this + * API to determine the size of offscreen framebuffers and so for + * a fixed-size object that may be animated accross the screen we + * want to make sure that the stage paint-box has an equally stable + * size so that effects aren't made to continuously re-allocate + * a corresponding fbo. + * + * The other thing we consider is that the calculation of this box is + * subject to floating point precision issues that might be slightly + * different to the precision issues involved with actually painting the + * actor, which might result in painting slightly leaking outside the + * user's calculated paint-volume. For this we simply aim to pad out the + * paint-volume by at least half a pixel all the way around. + */ + width = box->x2 - box->x1; + height = box->y2 - box->y1; + width = nearby_int (width); + height = nearby_int (height); + /* XXX: NB the width/height may now be up to 0.5px too small so we + * must also pad by 0.25px all around to account for this. In total we + * must padd by at least 0.75px around all sides. */ + + /* XXX: The furthest that we can overshoot the bottom right corner by + * here is 1.75px in total if you consider that the 0.75 padding could + * just cross an integer boundary and so ceil will effectively add 1. + */ + box->x2 = ceilf (box->x2 + 0.75); + box->y2 = ceilf (box->y2 + 0.75); + + /* Now we redefine the top-left relative to the bottom right based on the + * rounded width/height determined above + a constant so that the overall + * size of the box will be stable and not dependant on the box's + * position. + * + * Adding 3px to the width/height will ensure we cover the maximum of + * 1.75px padding on the bottom/right and still ensure we have > 0.75px + * padding on the top/left. + */ + box->x1 = box->x2 - width - 3; + box->y1 = box->y2 - height - 3; +} + +/* This constant is used to deal with rounding error in computing + * paint boxes. See also https://gitlab.gnome.org/GNOME/mutter/blob/master/clutter/clutter/clutter-paint-volume.c#L1212 */ +#define PAINT_BOX_OFFSET 1 + +/** + * animation_clutter_get_untransformed_paint_box_from_existing_volume: + * @actor: A #ClutterActor + * @volume: A #ClutterPaintVolume + * @box: (out caller-allocates): A #ClutterActorBox to write extents to + * + * Assuming that the paint volume is orthogonal, get the stage-relative 2D + * extents of the paint volume and write them to the @box + * out parameter. + */ +void +animation_clutter_get_untransformed_paint_box_from_existing_volume (ClutterActor *actor, + const ClutterPaintVolume *volume, + ClutterActorBox *box) +{ + ClutterVertex origin; + + /* We don't have access to the stage projection matrix + * so the best we can do is hope here that the volume is + * two dimensional and orthogonal. */ + clutter_paint_volume_get_origin (volume, &origin); + + box->x1 = floor (origin.x + clutter_actor_get_x (actor)) - PAINT_BOX_OFFSET; + box->y1 = floor (origin.y + clutter_actor_get_y (actor)) - PAINT_BOX_OFFSET; + box->x2 = box->x1 + ceil (clutter_paint_volume_get_width (volume)) + PAINT_BOX_OFFSET * 2; + box->y2 = box->y1 + ceil (clutter_paint_volume_get_height (volume)) + PAINT_BOX_OFFSET * 2; +} + +/** + * animation_clutter_get_best_known_paint_extents_box: + * @actor: A #ClutterActor + * @box: (out caller-allocates): A #ClutterActorBox + * + * Get the paint box if possible, otherwise fall back to + * using the actor geometry. + */ +void +animation_clutter_get_best_known_paint_extents_box (ClutterActor *actor, + ClutterActorBox *box) +{ + float x, y, width, height; + + g_return_if_fail (box != NULL); + + if (clutter_actor_get_paint_box (actor, box)) + return; + + clutter_actor_get_position (actor, &x, &y); + clutter_actor_get_size (actor, &width, &height); + + box->x1 = x; + box->y1 = y; + box->x2 = x + width; + box->y2 = y + height; +} + +/** + * animation_clutter_compute_corners_from_paint_volume: + * @actor: A #ClutterActor that we're computing corners for. + * @volume: An existing #ClutterPaintVolume. + * @out_corners: (out caller-allocates) (array fixed-size=4): An array + * of corners representing a 3D bounding plane for this + * paint area. + * @offset: (out caller-allocates): A 2D vector representing the offset + * from the actor paint box corners to the real actor's real + * position. + */ +void +animation_clutter_compute_corners_from_untransformed_paint_volume (ClutterActor *actor, + ClutterPaintVolume *volume, + AnimationVector *out_corners, + AnimationVector *out_offset) +{ + ClutterActorBox box; + float actor_x, actor_y; + float actor_paint_box_x, actor_paint_box_y; + float actor_paint_box_width, actor_paint_box_height; + + g_return_if_fail (out_corners != NULL); + g_return_if_fail (out_offset != NULL); + + animation_clutter_get_untransformed_paint_box_from_existing_volume (actor, volume, &box); + clutter_actor_get_position (actor, &actor_x, &actor_y); + + actor_paint_box_x = box.x1; + actor_paint_box_y = box.y1; + actor_paint_box_width = box.x2 - box.x1; + actor_paint_box_height = box.y2 - box.y1; + + out_corners[0].x = actor_paint_box_x; + out_corners[0].y = actor_paint_box_y; + out_corners[1].x = actor_paint_box_x + actor_paint_box_width; + out_corners[1].y = actor_paint_box_y; + out_corners[2].x = actor_paint_box_x; + out_corners[2].y = actor_paint_box_y + actor_paint_box_height; + out_corners[3].x = actor_paint_box_x + actor_paint_box_width; + out_corners[3].y = actor_paint_box_y + actor_paint_box_height; + + out_offset->x = actor_paint_box_x - actor_x; + out_offset->y = actor_paint_box_y - actor_y; +} + +/** + * animation_clutter_expand_paint_volume_with_extremes: + * @volume: A #ClutterPaintVolume + * @extremes: (array fixed-size=4): An array + * of corners representing a 3D bounding plane for this + * paint area. + * @offset: A 2D vector representing the offset from the actor paint + * box corners to the real actor's real position. + */ +void +animation_clutter_expand_paint_volume_with_extremes (ClutterPaintVolume *volume, + AnimationVector4D *extremes, + AnimationVector *offset) +{ + float x1, y1, x2, y2, z1, z2; + + g_return_if_fail (extremes != NULL); + g_return_if_fail (offset != NULL); + + x1 = MIN (extremes[0].x, extremes[2].x) - offset->x; + y1 = MIN (extremes[0].y, extremes[1].y) - offset->y; + x2 = MAX (extremes[1].x, extremes[3].x) + offset->x; + y2 = MAX (extremes[2].y, extremes[3].y) + offset->y; + z1 = MIN (MIN (extremes[0].z, extremes[1].z), + MIN (extremes[2].z, extremes[3].z)); + z2 = MAX (MAX (extremes[0].z, extremes[1].z), + MAX (extremes[2].z, extremes[3].z)); + + g_autoptr(ClutterPaintVolume) extremes_volume = + clutter_paint_volume_copy (volume); + + ClutterVertex const origin = { x1, y1, z1 }; + clutter_paint_volume_set_origin (extremes_volume, &origin); + clutter_paint_volume_set_width (extremes_volume, x2 - x1); + clutter_paint_volume_set_height (extremes_volume, y2 - y1); + clutter_paint_volume_set_depth (extremes_volume, z2 - z1); + + clutter_paint_volume_union (volume, extremes_volume); +} diff --git a/animation-clutter/animation-clutter-common.h b/animation-clutter/animation-clutter-common.h new file mode 100644 index 0000000..2310f4b --- /dev/null +++ b/animation-clutter/animation-clutter-common.h @@ -0,0 +1,47 @@ +/* + * animation-clutter/animation-clutter-common.h + * + * Copyright © 2013-2016 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#pragma once + +#include +#include +#include +#include + +G_BEGIN_DECLS + +void animation_clutter_get_untransformed_paint_box_from_existing_volume (ClutterActor *actor, + const ClutterPaintVolume *volume, + ClutterActorBox *box); + +void animation_clutter_get_best_known_paint_extents_box (ClutterActor *actor, + ClutterActorBox *box); + +void animation_clutter_compute_corners_from_untransformed_paint_volume (ClutterActor *actor, + ClutterPaintVolume *volume, + AnimationVector *out_corners, + AnimationVector *out_offset); + +void animation_clutter_expand_paint_volume_with_extremes (ClutterPaintVolume *volume, + AnimationVector4D *extremes, + AnimationVector *offset); + +G_END_DECLS diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build new file mode 100644 index 0000000..cf7efc0 --- /dev/null +++ b/animation-clutter/meson.build @@ -0,0 +1,100 @@ +# animation-clutter/meson.build +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Meson build definitions for clutter backend to libanimation. + +api_version = '0' + +animation_clutter_introspectable_sources = files([]) +animation_clutter_private_headers = files([]) +animation_clutter_private_sources = files([]) +animation_clutter_headers = files([]) +animation_clutter_headers_subdir = 'animation-clutter' +animation_clutter_sources = files([]) + +animation_clutter_toplevel_private_headers = files([ + 'animation-clutter-common-private.h' +]) +animation_clutter_toplevel_headers = files([ + 'animation-clutter-common.h' +]) + +animation_clutter_toplevel_private_sources = files([]) +animation_clutter_toplevel_introspectable_sources = files([ + 'animation-clutter-common.c' +]) + +animation_clutter_introspectable_sources += animation_clutter_toplevel_introspectable_sources +animation_clutter_private_sources += animation_clutter_toplevel_private_sources +animation_clutter_headers += animation_clutter_toplevel_headers +animation_clutter_private_headers += animation_clutter_toplevel_private_headers + +install_headers(animation_clutter_toplevel_headers, subdir: animation_clutter_headers_subdir) + +animation_clutter_sources = animation_clutter_introspectable_sources + animation_clutter_private_sources + +glib = dependency('glib-2.0') +gobject = dependency('gobject-2.0') +clutter = dependency('mutter-clutter-4') +mutter = dependency('libmutter-4') + +mutter_typelibdir = mutter.get_pkgconfig_variable('typelibdir') +install_rpath = mutter_typelibdir + +animation_clutter_lib = shared_library( + 'animation-clutter', + animation_clutter_sources, + soversion: api_version, + install: true, + include_directories: [ animation_inc ], + dependencies: [ clutter, glib, gobject, mutter, animation_dep, animation_glib_dep ], + build_rpath: mutter_typelibdir, + install_rpath: mutter_typelibdir +) + +animation_clutter_dep = declare_dependency( + link_with: animation_clutter_lib, + include_directories: [ animation_inc ], +) + +introspection_sources = [ animation_clutter_introspectable_sources, animation_clutter_headers ] + +gnome = import('gnome') +animation_clutter_gir = gnome.generate_gir( + animation_clutter_lib, + extra_args: ['--warn-all', '--warn-error'], + identifier_prefix: 'AnimationClutter', + include_directories: animation_inc, + includes: [animation_glib_gir, 'Clutter-4', 'GLib-2.0', 'GObject-2.0'], + install: true, + namespace: 'AnimationClutter', + nsversion: api_version, + sources: introspection_sources, + symbol_prefix: 'animation_clutter' +)[0] + +pkg = import('pkgconfig') +pkg.generate( + description: 'Library to provide 2D surface animations (Clutter Implementation)', + name: 'libanimation-clutter', + filebase: 'libanimation-clutter-' + api_version, + version: meson.project_version(), + libraries: animation_clutter_lib, + install_dir: join_paths(get_option('libdir'), 'pkgconfig') +) + diff --git a/meson.build b/meson.build index c1c6695..22acbd4 100644 --- a/meson.build +++ b/meson.build @@ -41,5 +41,6 @@ tests_inc = include_directories('tests') subdir('animation') subdir('animation-glib') +subdir('animation-clutter') subdir('matchers') subdir('tests') From ce2fbb57e1bf176a1391de6750320f2acf202136 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:29:56 +0300 Subject: [PATCH 31/60] animation-clutter: Add AnimationClutterActorBoxQuery This class extends AnimationBoxQuery by attaching to an actor and listening for changes in the actor geometry, updating the underlying stored geometry in the box as appropriate. The caller can then query the geometry directly from the box as opposed to having to reach all the way into Clutter to get the geometry of the animated surface. --- .../animation-clutter-actor-box-query.c | 179 ++++++++++++++++++ .../animation-clutter-actor-box-query.h | 36 ++++ animation-clutter/meson.build | 2 + 3 files changed, 217 insertions(+) create mode 100644 animation-clutter/animation-clutter-actor-box-query.c create mode 100644 animation-clutter/animation-clutter-actor-box-query.h diff --git a/animation-clutter/animation-clutter-actor-box-query.c b/animation-clutter/animation-clutter-actor-box-query.c new file mode 100644 index 0000000..df58dd9 --- /dev/null +++ b/animation-clutter/animation-clutter-actor-box-query.c @@ -0,0 +1,179 @@ +/* + * animation-clutter/animation-clutter-actor-box-query.cpp + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * AnimationClutterActorBoxQuery subclass for ClutterActor + */ + +#include + +struct _AnimationClutterActorBoxQuery +{ + GObject parent_instance; +}; + +typedef struct _AnimationClutterActorBoxQueryPrivate +{ + ClutterActor *actor; +} AnimationClutterActorBoxQueryPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterActorBoxQuery, + animation_clutter_actor_box_query, + ANIMATION_TYPE_BOX_QUERY) + + +enum { + PROP_0, + PROP_ACTOR, + NPROPS +}; + +static GParamSpec *animation_clutter_actor_box_query_props[NPROPS] = { NULL, }; + +static void +update_box_for_actor_geometry (AnimationClutterActorBoxQuery *box_query, + ClutterActor *actor) +{ + float x, y, width, height; + + clutter_actor_get_position (actor, &x, &y); + clutter_actor_get_size (actor, &width, &height); + + const AnimationBox box = { + .top_left = { x, y }, + .bottom_right = { x + width, y + height } + }; + + animation_box_query_update (ANIMATION_BOX_QUERY (box_query), &box); +} + +static void +actor_geometry_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + ClutterActor *actor = CLUTTER_ACTOR (object); + AnimationClutterActorBoxQuery *box_query = ANIMATION_CLUTTER_ACTOR_BOX_QUERY (user_data); + + update_box_for_actor_geometry (box_query, actor); +} + +static void +disconnect_signals_and_unref (gpointer data) +{ + ClutterActor *actor = data; + + g_signal_handlers_disconnect_by_data (actor, actor); + g_object_unref (actor); +} + +static ClutterActor * +connect_signals_and_update (ClutterActor *actor, + AnimationClutterActorBoxQuery *box_query) +{ + g_signal_connect_object (actor, "notify::x", G_CALLBACK (actor_geometry_changed), box_query, G_CONNECT_AFTER); + g_signal_connect_object (actor, "notify::y", G_CALLBACK (actor_geometry_changed), box_query, G_CONNECT_AFTER); + g_signal_connect_object (actor, "notify::width", G_CALLBACK (actor_geometry_changed), box_query, G_CONNECT_AFTER); + g_signal_connect_object (actor, "notify::height", G_CALLBACK (actor_geometry_changed), box_query, G_CONNECT_AFTER); + + update_box_for_actor_geometry (box_query, actor); + + return actor; +} + +static void +animation_clutter_actor_box_query_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationClutterActorBoxQuery *box_query = ANIMATION_CLUTTER_ACTOR_BOX_QUERY (object); + AnimationClutterActorBoxQueryPrivate *priv = animation_clutter_actor_box_query_get_instance_private (box_query); + + switch (prop_id) + { + case PROP_ACTOR: + g_clear_pointer (&priv->actor, (GDestroyNotify) disconnect_signals_and_unref); + priv->actor = connect_signals_and_update (CLUTTER_ACTOR (g_value_dup_object (value)), box_query); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_clutter_actor_box_query_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AnimationClutterActorBoxQuery *box_query = ANIMATION_CLUTTER_ACTOR_BOX_QUERY (object); + AnimationClutterActorBoxQueryPrivate *priv = animation_clutter_actor_box_query_get_instance_private (box_query); + + switch (prop_id) + { + case PROP_ACTOR: + g_value_set_object (value, priv->actor); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +animation_clutter_actor_box_query_dispose (GObject *object) +{ + AnimationClutterActorBoxQuery *box_query = ANIMATION_CLUTTER_ACTOR_BOX_QUERY (object); + AnimationClutterActorBoxQueryPrivate *priv = animation_clutter_actor_box_query_get_instance_private (box_query); + + g_clear_object (&priv->actor); + + G_OBJECT_CLASS (animation_clutter_actor_box_query_parent_class)->dispose (object); +} + +static void +animation_clutter_actor_box_query_class_init (AnimationClutterActorBoxQueryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = animation_clutter_actor_box_query_get_property; + object_class->set_property = animation_clutter_actor_box_query_set_property; + object_class->dispose = animation_clutter_actor_box_query_dispose; + + animation_clutter_actor_box_query_props[PROP_ACTOR] = + g_param_spec_object ("actor", + "Clutter Actor", + "The ClutterActor to observe", + CLUTTER_TYPE_ACTOR, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, + NPROPS, + animation_clutter_actor_box_query_props); +} + +static void +animation_clutter_actor_box_query_init (AnimationClutterActorBoxQuery *box_query) +{ +} + +AnimationBoxQuery * +animation_clutter_actor_box_query_new_for_actor (ClutterActor *actor) +{ + return ANIMATION_BOX_QUERY (g_object_new (ANIMATION_CLUTTER_TYPE_ACTOR_BOX_QUERY, + "actor", actor, + NULL)); +} diff --git a/animation-clutter/animation-clutter-actor-box-query.h b/animation-clutter/animation-clutter-actor-box-query.h new file mode 100644 index 0000000..9da591e --- /dev/null +++ b/animation-clutter/animation-clutter-actor-box-query.h @@ -0,0 +1,36 @@ +/* + * animation-clutter/animation-clutter-actor-box-query.h + * + * libanimation is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * libanimation is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with eos-companion-app-service. If not, see + * . + * + * Geometry query subclass for ClutterActor + */ +#pragma once + +#include + +#include + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_CLUTTER_TYPE_ACTOR_BOX_QUERY animation_clutter_actor_box_query_get_type () +G_DECLARE_FINAL_TYPE (AnimationClutterActorBoxQuery, animation_clutter_actor_box_query, ANIMATION_CLUTTER, ACTOR_BOX_QUERY, GObject) + +AnimationBoxQuery * animation_clutter_actor_box_query_new_for_actor (ClutterActor *actor); + +G_END_DECLS diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index cf7efc0..b50a9c4 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -31,11 +31,13 @@ animation_clutter_toplevel_private_headers = files([ 'animation-clutter-common-private.h' ]) animation_clutter_toplevel_headers = files([ + 'animation-clutter-actor-box-query.h', 'animation-clutter-common.h' ]) animation_clutter_toplevel_private_sources = files([]) animation_clutter_toplevel_introspectable_sources = files([ + 'animation-clutter-actor-box-query.c', 'animation-clutter-common.c' ]) From 065c81f3a211d4618257b01db49051f76b4509c2 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:31:35 +0300 Subject: [PATCH 32/60] animation-clutter: Add AnimationClutterAffineEffect This wraps any AnimationTransformAnimation subclass in a ClutterEffect and uses the underlying AnimationTransformAnimation to apply some sort of linear transformation to the actor when it is painted depending on the state of the animation. --- .../animation-clutter-affine-effect.c | 311 ++++++++++++++++++ .../animation-clutter-affine-effect.h | 44 +++ animation-clutter/meson.build | 2 + 3 files changed, 357 insertions(+) create mode 100644 animation-clutter/animation-clutter-affine-effect.c create mode 100644 animation-clutter/animation-clutter-affine-effect.h diff --git a/animation-clutter/animation-clutter-affine-effect.c b/animation-clutter/animation-clutter-affine-effect.c new file mode 100644 index 0000000..5519531 --- /dev/null +++ b/animation-clutter/animation-clutter-affine-effect.c @@ -0,0 +1,311 @@ +/* + * animation-clutter/animation-clutter-affine-effect.c + * + * Copyright © 2013-2018 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "animation-clutter-affine-effect.h" +#include "animation-clutter-common.h" + +struct _AnimationClutterAffineEffect { + ClutterEffect parent_instance; +}; + +typedef struct +{ + AnimationTransformAnimation *transform_animation; + gint64 last_usecs; + guint timeout_id; +} AnimationClutterAffineEffectPrivate; + +enum +{ + PROP_0, + PROP_TRANSFORM_ANIMATION, + PROP_LAST +}; + +static GParamSpec *object_properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterAffineEffect, + animation_clutter_affine_effect, + CLUTTER_TYPE_EFFECT) + +static gboolean +animation_clutter_affine_effect_get_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) +{ + ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); + ClutterActor *actor = clutter_actor_meta_get_actor (meta); + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (effect); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + /* We assume that the parent's get_paint_volume method always returns + * TRUE here. */ + CLUTTER_EFFECT_CLASS (animation_clutter_affine_effect_parent_class)->get_paint_volume (effect, volume); + + if (priv->transform_animation && clutter_actor_meta_get_enabled (meta)) + { + AnimationVector corners[4]; + AnimationVector4D extremes[4]; + AnimationVector offset; + + animation_clutter_compute_corners_from_untransformed_paint_volume (actor, + volume, + corners, + &offset); + + animation_transform_animation_extremes (priv->transform_animation, + corners, + extremes); + + animation_clutter_expand_paint_volume_with_extremes (volume, extremes, &offset); + } + + return TRUE; +} + +static gboolean +animation_clutter_affine_effect_new_frame (gpointer user_data) +{ + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (user_data); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + ClutterActorMeta *meta = CLUTTER_ACTOR_META (affine_effect); + ClutterActor *actor = clutter_actor_meta_get_actor (meta); + gint64 usecs = g_get_monotonic_time (); + + static const unsigned int ms_to_us = 1000; + + g_assert (priv->transform_animation); + + /* Wraparound, priv->last_usecs -= G_MAXINT64. + * We make priv->last_usecs negative so that subtracting it + * from usecs results in the correct delta */ + if (G_UNLIKELY (priv->last_usecs > usecs)) + priv->last_usecs -= G_MAXINT64; + + gint64 msecs_delta = (usecs - priv->last_usecs) / ms_to_us; + priv->last_usecs = usecs; + + /* If there was no time movement, then we can't really step or remove + * models in a way that makes sense, so don't do it */ + if (msecs_delta == 0) + return G_SOURCE_CONTINUE; + + if (!animation_transform_animation_step (priv->transform_animation, msecs_delta)) + { + /* Reset the transform back to an identity matrix. This will + * also cause the transform-set property to be unset. We + * need to do this before the actor effect is disabled, since + * disabling it may cause the actor to be destroyed + * and the actor to be detached from the effect. */ + CoglMatrix matrix; + cogl_matrix_init_identity (&matrix); + clutter_actor_set_opacity (actor, 255); + clutter_actor_set_transform (actor, + (const ClutterMatrix *) &matrix); + + /* Disable the effect */ + clutter_actor_meta_set_enabled (meta, FALSE); + + /* Finally, return false so that we don't keep animating */ + priv->timeout_id = 0; + return G_SOURCE_REMOVE; + } + else + { + /* We need to immediately set the opacity of the actor + * since if it is zero, then clutter_actor_paint will + * never get called, causing us to never be able to + * update the opacity of the actor */ + float scaled_opacity = animation_transform_animation_progress (priv->transform_animation) * 255.0; + guint8 opacity = (guint8) (scaled_opacity); + + clutter_actor_set_opacity (actor, opacity); + clutter_effect_queue_repaint (CLUTTER_EFFECT (affine_effect)); + } + + /* We always want to return true even if there was no time delta */ + return G_SOURCE_CONTINUE; +} + +static void +animation_clutter_affine_effect_paint (ClutterEffect *effect, + ClutterEffectPaintFlags flags) +{ + ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); + ClutterActor *actor = clutter_actor_meta_get_actor (meta); + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (effect); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + /* Apply the transform to the actor */ + ClutterMatrix matrix; + clutter_matrix_init_from_array (&matrix, + animation_transform_animation_matrix (priv->transform_animation)); + + clutter_actor_set_pivot_point (actor, 0, 0); + clutter_actor_set_transform (actor, &matrix); + clutter_actor_continue_paint (actor); +} + +static void +animation_clutter_affine_effect_ensure_timeline (AnimationClutterAffineEffect *affine_effect) +{ + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + if (priv->timeout_id == 0) + { + static const unsigned int frame_length_ms = 16; /* 1000 / 60; */ + + priv->last_usecs = g_get_monotonic_time (); + priv->timeout_id = g_timeout_add (frame_length_ms, animation_clutter_affine_effect_new_frame, affine_effect); + + /* We need to show the actor and set the initial transform now to prevent flicker */ + ClutterMatrix matrix; + clutter_matrix_init_from_array (&matrix, + animation_transform_animation_matrix (priv->transform_animation)); + + float scaled_opacity = animation_transform_animation_progress (priv->transform_animation) * 255.0; + guint8 opacity = (guint8) (scaled_opacity); + + ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (affine_effect)); + + clutter_actor_set_opacity (actor, opacity); + clutter_actor_set_pivot_point (actor, 0, 0); + clutter_actor_set_transform (actor, &matrix); + clutter_actor_show (actor); + } +} + +static void +animation_clutter_affine_effect_notify (GObject *object, + GParamSpec *pspec) +{ + ClutterActorMeta *actor_meta = CLUTTER_ACTOR_META (object); + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (object); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + if (g_strcmp0 (pspec->name, "enabled") == 0) + { + if (clutter_actor_meta_get_enabled (actor_meta)) + animation_clutter_affine_effect_ensure_timeline (affine_effect); + else + g_clear_handle_id (&priv->timeout_id, g_source_remove); + } + + G_OBJECT_CLASS (animation_clutter_affine_effect_parent_class)->notify (object, pspec); +} + +static void +animation_clutter_affine_effect_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (object); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + switch (prop_id) + { + case PROP_TRANSFORM_ANIMATION: + g_set_object (&priv->transform_animation, g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +animation_clutter_affine_effect_dispose (GObject *object) +{ + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (object); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + g_clear_object (&priv->transform_animation); + + G_OBJECT_CLASS (animation_clutter_affine_effect_parent_class)->dispose (object); +} + +static void +animation_clutter_affine_effect_finalize (GObject *object) +{ + AnimationClutterAffineEffect *affine_effect = ANIMATION_CLUTTER_AFFINE_EFFECT (object); + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (affine_effect); + + g_clear_handle_id (&priv->timeout_id, g_source_remove); + + G_OBJECT_CLASS (animation_clutter_affine_effect_parent_class)->finalize (object); +} + +static void +animation_clutter_affine_effect_init (AnimationClutterAffineEffect *effect) +{ + AnimationClutterAffineEffectPrivate *priv = + animation_clutter_affine_effect_get_instance_private (effect); + + priv->timeout_id = 0; +} + +static void +animation_clutter_affine_effect_class_init (AnimationClutterAffineEffectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + + object_class->notify = animation_clutter_affine_effect_notify; + object_class->set_property = animation_clutter_affine_effect_set_property; + object_class->dispose = animation_clutter_affine_effect_dispose; + object_class->finalize = animation_clutter_affine_effect_finalize; + effect_class->get_paint_volume = animation_clutter_affine_effect_get_paint_volume; + effect_class->paint = animation_clutter_affine_effect_paint; + + object_properties[PROP_TRANSFORM_ANIMATION] = + g_param_spec_object ("transform-animation", + "Transform Animation", + "The underlying transform animation", + ANIMATION_TYPE_TRANSFORM_ANIMATION, + G_PARAM_WRITABLE); + + g_object_class_install_properties (object_class, PROP_LAST, object_properties); +} + +ClutterEffect * +animation_clutter_affine_effect_new (AnimationTransformAnimation *transform_animation) +{ + return g_object_new (ANIMATION_CLUTTER_TYPE_AFFINE_EFFECT, + "transform-animation", transform_animation, + NULL); +} diff --git a/animation-clutter/animation-clutter-affine-effect.h b/animation-clutter/animation-clutter-affine-effect.h new file mode 100644 index 0000000..e41da71 --- /dev/null +++ b/animation-clutter/animation-clutter-affine-effect.h @@ -0,0 +1,44 @@ +/* + * animation-clutter/clutter-animation-affine-effect.h + * + * Copyright © 2013-2016 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_CLUTTER_TYPE_AFFINE_EFFECT (animation_clutter_affine_effect_get_type ()) +G_DECLARE_FINAL_TYPE (AnimationClutterAffineEffect, animation_clutter_affine_effect, ANIMATION_CLUTTER, AFFINE_EFFECT, ClutterEffect) + +/** + * animation_clutter_affine_effect_new: + * @transform_animation: An #AnimationTransformAnimation to wrap. + * + * Creates a new #ClutterEffect which uses the underlying + * AnimationZoomAnimation to apply a linear transformation to the actor. + * + * Returns: (transfer full): A new #ClutterEffect + */ +ClutterEffect * animation_clutter_affine_effect_new (AnimationTransformAnimation *transform_animation); + +G_END_DECLS diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index b50a9c4..dc778a6 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -31,12 +31,14 @@ animation_clutter_toplevel_private_headers = files([ 'animation-clutter-common-private.h' ]) animation_clutter_toplevel_headers = files([ + 'animation-clutter-affine-effect.h', 'animation-clutter-actor-box-query.h', 'animation-clutter-common.h' ]) animation_clutter_toplevel_private_sources = files([]) animation_clutter_toplevel_introspectable_sources = files([ + 'animation-clutter-affine-effect.c', 'animation-clutter-actor-box-query.c', 'animation-clutter-common.c' ]) From ad7d0ced9156e574ace2d97915900585ff540f72 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:32:54 +0300 Subject: [PATCH 33/60] animation-clutter: Add AnimationClutterGridEffect AnimationClutterGridEffect extends ClutterDeformEffect and uses the underlying AnimationGridAnimation subclass to deform the vertices of the actor based on the state of the underlying animation. --- .../animation-clutter-grid-effect.c | 333 ++++++++++++++++++ .../animation-clutter-grid-effect.h | 45 +++ animation-clutter/meson.build | 6 +- 3 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 animation-clutter/animation-clutter-grid-effect.c create mode 100644 animation-clutter/animation-clutter-grid-effect.h diff --git a/animation-clutter/animation-clutter-grid-effect.c b/animation-clutter/animation-clutter-grid-effect.c new file mode 100644 index 0000000..0bde879 --- /dev/null +++ b/animation-clutter/animation-clutter-grid-effect.c @@ -0,0 +1,333 @@ +/* + * animation-clutter/animation-clutter-grid-effect.c + * + * Copyright © 2013-2018 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "animation-clutter-grid-effect.h" +#include "animation-clutter-common.h" + +struct _AnimationClutterGridEffect { + ClutterDeformEffect parent_instance; +}; + +typedef struct +{ + AnimationGridAnimation *grid_animation; + gint64 last_usecs; + guint timeout_id; +} AnimationClutterGridEffectPrivate; + +enum +{ + PROP_0, + PROP_GRID_ANIMATION, + PROP_LAST +}; + +static GParamSpec *object_properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterGridEffect, + animation_clutter_grid_effect, + CLUTTER_TYPE_DEFORM_EFFECT) + +static gboolean +animation_clutter_grid_effect_get_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) +{ + ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); + ClutterActor *actor = clutter_actor_meta_get_actor (meta); + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (effect); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + /* We assume that the parent's get_paint_volume method always returns + * TRUE here. */ + CLUTTER_EFFECT_CLASS (animation_clutter_grid_effect_parent_class)->get_paint_volume (effect, volume); + + if (priv->grid_animation && clutter_actor_meta_get_enabled (meta)) + { + ClutterActorBox box; + float actor_x, actor_y; + + animation_clutter_get_untransformed_paint_box_from_existing_volume (actor, volume, &box); + clutter_actor_get_position (actor, &actor_x, &actor_y); + + AnimationVector corners[4] = { + { box.x1, box.y1 }, + { box.x2, box.y1 }, + { box.x1, box.y2 }, + { box.x2, box.y2 } + }; + AnimationVector4D extremes[4]; + + animation_grid_animation_extremes (priv->grid_animation, + corners, + extremes); + + float x1 = MIN (extremes[0].x, extremes[2].x); + float y1 = MIN (extremes[0].y, extremes[1].y); + float z1 = MIN (MIN (extremes[0].z, extremes[1].z), + MIN (extremes[2].z, extremes[3].z)); + float x2 = MAX (extremes[1].x, extremes[3].x); + float y2 = MAX (extremes[2].y, extremes[3].y); + float z2 = MAX (MAX (extremes[0].z, extremes[1].z), + MAX (extremes[2].z, extremes[3].z)); + + g_autoptr(ClutterPaintVolume) extremes_volume = + clutter_paint_volume_copy (volume); + + ClutterVertex const origin = { x1 - actor_x, y1 - actor_y, z1 }; + clutter_paint_volume_set_origin (extremes_volume, &origin); + clutter_paint_volume_set_width (extremes_volume, MAX (x2 - x1, 1.0)); + clutter_paint_volume_set_height (extremes_volume, MAX (y2 - y1, 1.0)); + clutter_paint_volume_set_depth (extremes_volume, z2 - z1); + + clutter_paint_volume_union (volume, extremes_volume); + } + + return TRUE; +} + +static void +animation_clutter_grid_effect_deform_vertex (ClutterDeformEffect *effect, + gfloat width G_GNUC_UNUSED, + gfloat height G_GNUC_UNUSED, + CoglTextureVertex *vertex) +{ + ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (effect); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + AnimationVector uv = { vertex->tx, vertex->ty }; + AnimationVector deformed; + float x, y; + + animation_grid_animation_deform_uv_to_model_space (priv->grid_animation, + &uv, + &deformed); + + clutter_actor_get_position (actor, &x, &y); + + vertex->x = deformed.x - x; + vertex->y = deformed.y - y; +} + + +static gboolean +animation_clutter_grid_effect_new_frame (gpointer user_data) +{ + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (user_data); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + ClutterActorMeta *meta = CLUTTER_ACTOR_META (grid_effect); + gint64 msecs = g_get_monotonic_time (); + + static const unsigned int ms_to_us = 1000; + + g_assert (priv->grid_animation); + + /* Wraparound, priv->last_usecs -= G_MAXINT64. + * We make priv->last_usecs negative so that subtracting it + * from msecs results in the correct delta */ + if (G_UNLIKELY (priv->last_usecs > msecs)) + priv->last_usecs -= G_MAXINT64; + + gint64 msecs_delta = (msecs - priv->last_usecs) / ms_to_us; + priv->last_usecs = msecs; + + /* If there was no time movement, then we can't really step or remove + * models in a way that makes sense, so don't do it */ + if (msecs_delta == 0) + return G_SOURCE_CONTINUE; + + if (!animation_grid_animation_step (priv->grid_animation, msecs_delta)) + { + /* Disable the effect */ + clutter_actor_meta_set_enabled (meta, FALSE); + + /* Finally, return false so that we don't keep animating */ + priv->timeout_id = 0; + return G_SOURCE_REMOVE; + } + else + { + clutter_actor_meta_set_enabled (meta, TRUE); + clutter_deform_effect_invalidate (CLUTTER_DEFORM_EFFECT (grid_effect)); + } + + /* We always want to return true even if there was no time delta */ + return G_SOURCE_CONTINUE; +} + +static void +animation_clutter_grid_effect_ensure_timeline (AnimationClutterGridEffect *grid_effect) +{ + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + if (priv->timeout_id == 0) + { + static const unsigned int frame_length_ms = 16; /* 1000 / 60 */; + + priv->last_usecs = g_get_monotonic_time (); + priv->timeout_id = g_timeout_add (frame_length_ms, animation_clutter_grid_effect_new_frame, grid_effect); + + ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (grid_effect)); + + /* Make sure to show the actor so that we can see the animation + * if the animation was on open or unminimize */ + clutter_actor_show (actor); + } +} + +static void +animation_clutter_grid_effect_notify (GObject *object, + GParamSpec *pspec) +{ + ClutterActorMeta *actor_meta = CLUTTER_ACTOR_META (object); + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (object); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + if (g_strcmp0 (pspec->name, "enabled") == 0) + { + if (clutter_actor_meta_get_enabled (actor_meta)) + animation_clutter_grid_effect_ensure_timeline (grid_effect); + else + g_clear_handle_id (&priv->timeout_id, g_source_remove); + } + + G_OBJECT_CLASS (animation_clutter_grid_effect_parent_class)->notify (object, pspec); +} + +static void +animation_clutter_grid_effect_set_actor (ClutterActorMeta *actor_meta, + ClutterActor *actor) +{ + CLUTTER_ACTOR_META_CLASS (animation_clutter_grid_effect_parent_class)->set_actor (actor_meta, actor); + + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (actor_meta); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + /* Disable the effect before we do anything else - this ensures that + * for instance, we get the right paint box because we don't have applied + * effects that haven't had their paint boxes computed. */ + clutter_actor_meta_set_enabled (actor_meta, FALSE); + + g_clear_handle_id (&priv->timeout_id, g_source_remove); +} + +static void +animation_clutter_grid_effect_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (object); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + switch (prop_id) + { + case PROP_GRID_ANIMATION: + g_set_object (&priv->grid_animation, g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +animation_clutter_grid_effect_dispose (GObject *object) +{ + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (object); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + g_clear_object (&priv->grid_animation); + + G_OBJECT_CLASS (animation_clutter_grid_effect_parent_class)->dispose (object); +} + +static void +animation_clutter_grid_effect_finalize (GObject *object) +{ + AnimationClutterGridEffect *grid_effect = ANIMATION_CLUTTER_GRID_EFFECT (object); + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (grid_effect); + + g_clear_handle_id (&priv->timeout_id, g_source_remove); + + G_OBJECT_CLASS (animation_clutter_grid_effect_parent_class)->finalize (object); +} + +static void +animation_clutter_grid_effect_init (AnimationClutterGridEffect *effect) +{ + AnimationClutterGridEffectPrivate *priv = + animation_clutter_grid_effect_get_instance_private (effect); + + priv->timeout_id = 0; +} + +static void +animation_clutter_grid_effect_class_init (AnimationClutterGridEffectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + ClutterDeformEffectClass *deform_class = CLUTTER_DEFORM_EFFECT_CLASS (klass); + + object_class->notify = animation_clutter_grid_effect_notify; + object_class->set_property = animation_clutter_grid_effect_set_property; + object_class->dispose = animation_clutter_grid_effect_dispose; + object_class->finalize = animation_clutter_grid_effect_finalize; + meta_class->set_actor = animation_clutter_grid_effect_set_actor; + effect_class->get_paint_volume = animation_clutter_grid_effect_get_paint_volume; + deform_class->deform_vertex = animation_clutter_grid_effect_deform_vertex; + + object_properties[PROP_GRID_ANIMATION] = + g_param_spec_object ("grid-animation", + "Grid Animation", + "The underlying grid animation", + ANIMATION_TYPE_GRID_ANIMATION, + G_PARAM_WRITABLE); + + g_object_class_install_properties (object_class, PROP_LAST, object_properties); +} + +ClutterEffect * +animation_clutter_grid_effect_new (AnimationGridAnimation *grid_animation) +{ + return g_object_new (ANIMATION_CLUTTER_TYPE_GRID_EFFECT, + "grid-animation", grid_animation, + NULL); +} diff --git a/animation-clutter/animation-clutter-grid-effect.h b/animation-clutter/animation-clutter-grid-effect.h new file mode 100644 index 0000000..9af2b5c --- /dev/null +++ b/animation-clutter/animation-clutter-grid-effect.h @@ -0,0 +1,45 @@ +/* + * animation-clutter/animation-clutter-grid-effect.h + * + * Copyright © 2013-2016 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_CLUTTER_TYPE_GRID_EFFECT (animation_clutter_grid_effect_get_type ()) +G_DECLARE_FINAL_TYPE (AnimationClutterGridEffect, animation_clutter_grid_effect, ANIMATION_CLUTTER, GRID_EFFECT, ClutterDeformEffect) + + +/** + * animation_clutter_grid_effect_new: + * @grid_animation: An #AnimationGridAnimation to wrap. + * + * Creates a new #ClutterEffect which uses the underlying + * AnimationGridAnimation to apply grid based effects to the actor. + * + * Returns: (transfer full): A new #ClutterEffect + */ +ClutterEffect * animation_clutter_grid_effect_new (AnimationGridAnimation *grid_animation); + +G_END_DECLS diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index dc778a6..5f68f5c 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -33,14 +33,16 @@ animation_clutter_toplevel_private_headers = files([ animation_clutter_toplevel_headers = files([ 'animation-clutter-affine-effect.h', 'animation-clutter-actor-box-query.h', - 'animation-clutter-common.h' + 'animation-clutter-common.h', + 'animation-clutter-grid-effect.h' ]) animation_clutter_toplevel_private_sources = files([]) animation_clutter_toplevel_introspectable_sources = files([ 'animation-clutter-affine-effect.c', 'animation-clutter-actor-box-query.c', - 'animation-clutter-common.c' + 'animation-clutter-common.c', + 'animation-clutter-grid-effect.c' ]) animation_clutter_introspectable_sources += animation_clutter_toplevel_introspectable_sources From 48a98f4c38fe6d1baeafd7ba53311d6f172b7764 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:34:18 +0300 Subject: [PATCH 34/60] animation-clutter: Add AnimationClutterWobblyEffect This extends ClutterDeformExtent and wraps AnimationWobblyModel to deform the actor geometry in accordance with the underlying model state. --- .../animation-clutter-wobbly-effect.c | 530 ++++++++++++++++++ .../animation-clutter-wobbly-effect.h | 90 +++ animation-clutter/meson.build | 6 +- 3 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 animation-clutter/animation-clutter-wobbly-effect.c create mode 100644 animation-clutter/animation-clutter-wobbly-effect.h diff --git a/animation-clutter/animation-clutter-wobbly-effect.c b/animation-clutter/animation-clutter-wobbly-effect.c new file mode 100644 index 0000000..a9957cf --- /dev/null +++ b/animation-clutter/animation-clutter-wobbly-effect.c @@ -0,0 +1,530 @@ +/* + * animation-clutter/animation-clutter-wobbly-effect.c + * + * Copyright © 2013-2018 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "animation-clutter-common.h" +#include "animation-clutter-common-private.h" +#include "animation-clutter-wobbly-effect.h" + +struct _AnimationClutterWobblyEffect { + ClutterDeformEffect parent_instance; +}; + +typedef struct _AnimationClutterWobblyEffectPrivate +{ + float slowdown_factor; + double spring_constant; + double friction; + double movement_range; + AnimationWobblyModel *model; + AnimationWobblyAnchor *anchor; + gint64 last_msecs; + guint timeout_id; + guint width_changed_signal; + guint height_changed_signal; + gboolean ungrab_pending; +} AnimationClutterWobblyEffectPrivate; + +enum +{ + PROP_0, + + PROP_SPRING_K, + PROP_FRICTION, + PROP_SLOWDOWN_FACTOR, + PROP_OBJECT_MOVEMENT_RANGE, + + PROP_LAST +}; + +static GParamSpec *object_properties[PROP_LAST]; + +G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterWobblyEffect, + animation_clutter_wobbly_effect, + CLUTTER_TYPE_DEFORM_EFFECT) + +static gboolean +animation_clutter_wobbly_effect_get_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) +{ + ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); + ClutterActor *actor = clutter_actor_meta_get_actor (meta); + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (effect); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + /* We assume that the parent's get_paint_volume method always returns + * TRUE here. */ + CLUTTER_EFFECT_CLASS (animation_clutter_wobbly_effect_parent_class)->get_paint_volume (effect, volume); + + if (priv->model && clutter_actor_meta_get_enabled (meta)) + { + ClutterActorBox box; + float actor_x, actor_y; + + animation_clutter_get_untransformed_paint_box_from_existing_volume (actor, volume, &box); + clutter_actor_get_position (actor, &actor_x, &actor_y); + + AnimationVector offset = { box.x1 - actor_x, box.y1 - actor_y }; + AnimationVector extremes[4]; + + animation_wobbly_model_query_extremes (priv->model, + &extremes[0], + &extremes[1], + &extremes[2], + &extremes[3]); + + float x1 = MIN (extremes[0].x, extremes[2].x); + float y1 = MIN (extremes[0].y, extremes[1].y); + float x2 = MAX (extremes[1].x, extremes[3].x); + float y2 = MAX (extremes[2].y, extremes[3].y); + + ClutterActorBox const extremesBox = + { + floor (x1 + offset.x), + floor (y1 + offset.y), + ceil (x2 + offset.x), + ceil (y2 + offset.x) + }; + + clutter_paint_volume_union_box (volume, &extremesBox); + } + + return TRUE; +} + +static void +animation_clutter_wobbly_effect_deform_vertex (ClutterDeformEffect *effect, + gfloat x G_GNUC_UNUSED, + gfloat y G_GNUC_UNUSED, + CoglTextureVertex *vertex) +{ + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (effect); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + /* The reversal of ty and tx here is intentional */ + AnimationVector uv = { vertex->ty, vertex->tx }; + AnimationVector deformed; + animation_wobbly_model_deform_texcoords (priv->model, + uv, + &deformed); + + vertex->x = deformed.x; + vertex->y = deformed.y; +} + +static void +remove_anchor_if_pending (AnimationClutterWobblyEffectPrivate *priv) +{ + if (priv->ungrab_pending) + { + g_clear_object (&priv->anchor); + priv->ungrab_pending = FALSE; + } +} + +/* It turns out that clutter doesn't contain any mechanism whatsoever + * to do timeline-less animations. We're just using a timeout here + * to keep performing animations on the actor */ +static gboolean +animation_clutter_wobbly_effect_new_frame (gpointer user_data) +{ + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (user_data); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + gint64 msecs = g_get_monotonic_time (); + + static const unsigned int ms_to_us = 1000; + + g_assert (priv->model); + + /* Wraparound, priv->last_msecs -= G_MAXINT64. + * We make priv->last_msecs negative so that subtracting it + * from msecs results in the correct delta */ + if (G_UNLIKELY (priv->last_msecs > msecs)) + priv->last_msecs -= G_MAXINT64; + + gint64 msecs_delta = (msecs - priv->last_msecs ) / ms_to_us; + priv->last_msecs = msecs; + + /* If there was no time movement, then we can't really step or remove + * models in a way that makes sense, so don't do it */ + if (msecs_delta) + { + if (animation_wobbly_model_step (priv->model, msecs_delta / priv->slowdown_factor)) + { + clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (wobbly_effect), TRUE); + clutter_deform_effect_invalidate (CLUTTER_DEFORM_EFFECT (wobbly_effect)); + } + else + { + remove_anchor_if_pending (priv); + + /* Also disable the effect */ + clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (wobbly_effect), FALSE); + + /* Finally, return false so that we don't keep animating */ + priv->timeout_id = -1; + return FALSE; + } + } + + /* We always want to return true even if there was no time delta */ + return TRUE; +} + +static void +animation_clutter_wobbly_effect_ensure_timeline (AnimationClutterWobblyEffect *wobbly_effect) +{ + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + if (priv->timeout_id == -1) + { + static const unsigned int frame_length_ms = 16; // 60 / 1000; + + priv->last_msecs = g_get_monotonic_time (); + priv->timeout_id = g_timeout_add (frame_length_ms, animation_clutter_wobbly_effect_new_frame, wobbly_effect); + } +} + +void +animation_clutter_wobbly_effect_grab (AnimationClutterWobblyEffect *effect, + double x, + double y) +{ + ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (effect); + + g_assert (!priv->anchor || priv->ungrab_pending); + + /* Either ungrab here or at the end of the animation */ + remove_anchor_if_pending (priv); + + if (priv->model) + { + /* Make sure to update the model geometry and move + * to the right position, it may have changed + * in the meantime */ + ClutterActorBox box; + float actor_paint_box_width, actor_paint_box_height; + animation_clutter_get_best_known_paint_extents_box (actor, &box); + + /* See animation-clutter-common.c for why we need to do this */ + animation_clutter_actor_box_enlarge_for_effects (&box); + + actor_paint_box_width = box.x2 - box.x1; + actor_paint_box_height = box.y2 - box.y1; + + AnimationVector position = { 0, 0 }; + AnimationVector size = { actor_paint_box_width, actor_paint_box_height }; + + animation_wobbly_model_resize (priv->model, size); + animation_wobbly_model_move_to (priv->model, position); + + animation_clutter_wobbly_effect_ensure_timeline (effect); + + float actor_x, actor_y; + clutter_actor_get_position (actor, &actor_x, &actor_y); + + AnimationVector anchor_position = { x - actor_x, y - actor_y }; + + priv->anchor = animation_wobbly_model_grab_anchor (priv->model, anchor_position); + } +} + +void +animation_clutter_wobbly_effect_ungrab (AnimationClutterWobblyEffect *effect) +{ + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (effect); + + g_assert (priv->anchor && !priv->ungrab_pending); + + /* Don't immediately ungrab. We can be a little bit more + * clever here and make the ungrab pending on the completion + * of the animation */ + if (priv->timeout_id != -1) + priv->ungrab_pending = TRUE; + else + g_clear_object (&priv->anchor); +} + +void +animation_clutter_wobbly_effect_move_by (AnimationClutterWobblyEffect *effect, + double dx, + double dy) +{ + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (effect); + + if (priv->anchor) + { + AnimationVector delta = { dx, dy }; + + animation_clutter_wobbly_effect_ensure_timeline (effect); + animation_wobbly_anchor_move_by (priv->anchor, delta); + + AnimationVector reverse_delta = delta; + reverse_delta.x *= -1; + reverse_delta.y *= -1; + + /* Now move the entire model back - this ensures that + * we stay in sync with the actor's relative position */ + animation_wobbly_model_move_by (priv->model, reverse_delta); + } +} + +static void +animation_clutter_wobbly_effect_size_changed (GObject *object, + GParamSpec *spec G_GNUC_UNUSED, + gpointer user_data) +{ + ClutterActor *actor = CLUTTER_ACTOR (object); + AnimationClutterWobblyEffect *effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (user_data); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (effect); + + /* We don't ensure a timeline here because we only want to redistribute + * non-anchor points if we're already grabbed, which the wobbly effect will + * do internally anyways */ + if (priv->model) + { + ClutterActorBox box; + float actor_paint_box_width, actor_paint_box_height; + animation_clutter_get_best_known_paint_extents_box (actor, &box); + + /* See animation-clutter-common.c for why we need to do this */ + animation_clutter_actor_box_enlarge_for_effects (&box); + + actor_paint_box_width = box.x2 - box.x1; + actor_paint_box_height = box.y2 - box.y1; + + /* If we have any pending anchors, we should release them now - + * the model move and resize code explicitly does not move + * anchors around (because that'd put them out of sync with + * the cursor) */ + remove_anchor_if_pending (priv); + + AnimationVector actor_size = { actor_paint_box_width, actor_paint_box_height }; + AnimationVector actor_position = { 0.0, 0.0 }; + + g_message ("Actor paint box size %f %f %f %f\n", box.x1, box.y1, actor_paint_box_width, actor_paint_box_height); + animation_wobbly_model_resize (priv->model, actor_size); + animation_wobbly_model_move_to (priv->model, actor_position); + } +} + +static void +animation_clutter_wobbly_effect_set_actor (ClutterActorMeta *actor_meta, + ClutterActor *actor) +{ + ClutterActor *prev_actor = clutter_actor_meta_get_actor (actor_meta); + + CLUTTER_ACTOR_META_CLASS (animation_clutter_wobbly_effect_parent_class)->set_actor (actor_meta, actor); + + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (actor_meta); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + g_clear_object (&priv->anchor); + g_clear_object (&priv->model); + + priv->ungrab_pending = FALSE; + + if (priv->timeout_id != -1) + { + g_source_remove (priv->timeout_id); + priv->timeout_id = -1; + } + + if (prev_actor) + { + g_signal_handler_disconnect (prev_actor, priv->width_changed_signal); + priv->width_changed_signal = 0; + + g_signal_handler_disconnect (prev_actor, priv->height_changed_signal); + priv->height_changed_signal = 0; + } + + if (actor) + { + ClutterActorBox box; + float actor_paint_box_width, actor_paint_box_height; + animation_clutter_get_best_known_paint_extents_box (actor, &box); + + actor_paint_box_width = box.x2 - box.x1; + actor_paint_box_height = box.y2 - box.y1; + + AnimationVector actor_position = { 0, 0 }; + AnimationVector actor_size = { actor_paint_box_width, actor_paint_box_height }; + + priv->model = animation_wobbly_model_new (actor_position, + actor_size, + priv->spring_constant, + priv->friction, + priv->movement_range); + + priv->width_changed_signal = + g_signal_connect_object (actor, + "notify::width", + G_CALLBACK (animation_clutter_wobbly_effect_size_changed), + wobbly_effect, + G_CONNECT_AFTER); + priv->height_changed_signal = + g_signal_connect_object (actor, + "notify::height", + G_CALLBACK (animation_clutter_wobbly_effect_size_changed), + wobbly_effect, + G_CONNECT_AFTER); + } + + /* Whatever the actor, ensure that the effect is disabled at this point */ + clutter_actor_meta_set_enabled (actor_meta, FALSE); +} + +static void +animation_clutter_wobbly_effect_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (object); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + switch (prop_id) + { + case PROP_SPRING_K: + priv->spring_constant = g_value_get_double (value); + + if (priv->model != NULL) + animation_wobbly_model_set_spring_k (priv->model, priv->spring_constant); + break; + case PROP_FRICTION: + priv->friction = g_value_get_double (value); + + if (priv->model != NULL) + animation_wobbly_model_set_friction (priv->model, priv->friction); + break; + case PROP_SLOWDOWN_FACTOR: + priv->slowdown_factor = g_value_get_double (value); + break; + case PROP_OBJECT_MOVEMENT_RANGE: + priv->movement_range = g_value_get_double (value); + + if (priv->model != NULL) + animation_wobbly_model_set_maximum_range (priv->model, priv->movement_range); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +animation_clutter_wobbly_effect_finalize (GObject *object) +{ + AnimationClutterWobblyEffect *wobbly_effect = ANIMATION_CLUTTER_WOBBLY_EFFECT (object); + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); + + g_clear_object (&priv->model); + + if (priv->timeout_id != -1) + { + g_source_remove (priv->timeout_id); + priv->timeout_id = -1; + } + + G_OBJECT_CLASS (animation_clutter_wobbly_effect_parent_class)->finalize (object); +} + +static void +animation_clutter_wobbly_effect_init (AnimationClutterWobblyEffect *effect) +{ + AnimationClutterWobblyEffectPrivate *priv = + animation_clutter_wobbly_effect_get_instance_private (effect); + + priv->timeout_id = -1; +} + +static void +animation_clutter_wobbly_effect_class_init (AnimationClutterWobblyEffectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); + ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); + ClutterDeformEffectClass *deform_class = CLUTTER_DEFORM_EFFECT_CLASS (klass); + + object_class->set_property = animation_clutter_wobbly_effect_set_property; + object_class->finalize = animation_clutter_wobbly_effect_finalize; + meta_class->set_actor = animation_clutter_wobbly_effect_set_actor; + effect_class->get_paint_volume = animation_clutter_wobbly_effect_get_paint_volume; + deform_class->deform_vertex = animation_clutter_wobbly_effect_deform_vertex; + + object_properties[PROP_SPRING_K] = + g_param_spec_double ("spring-k", + "Spring Constant", + "How springy the model is", + 2.0, 10.0, 8.0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + object_properties[PROP_FRICTION] = + g_param_spec_double ("friction", + "Friction Constant", + "How much friction force should be applied to moving objects", + 2.0, 10.0, 3.0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + object_properties[PROP_SLOWDOWN_FACTOR] = + g_param_spec_double ("slowdown-factor", + "Slowdown Factor", + "How much to slow the model's timesteps down", + 1.0, 5.0, 1.0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + object_properties[PROP_OBJECT_MOVEMENT_RANGE] = + g_param_spec_double ("object-movement-range", + "Object Movement Range", + "How much objects are allowed to move around", + 10.0, 500.0, 100.0, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, PROP_LAST, object_properties); +} + +ClutterEffect * +animation_clutter_wobbly_effect_new (void) +{ + return g_object_new (ANIMATION_CLUTTER_TYPE_WOBBLY, NULL); +} diff --git a/animation-clutter/animation-clutter-wobbly-effect.h b/animation-clutter/animation-clutter-wobbly-effect.h new file mode 100644 index 0000000..e68eb8a --- /dev/null +++ b/animation-clutter/animation-clutter-wobbly-effect.h @@ -0,0 +1,90 @@ +/* + * animation-clutter/animation-clutter-wobbly-effect.h + * + * Copyright © 2013-2016 Endless Mobile, Inc. + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * licence or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Sam Spilsbury + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define ANIMATION_CLUTTER_TYPE_WOBBLY animation_clutter_wobbly_effect_get_type () +G_DECLARE_FINAL_TYPE (AnimationClutterWobblyEffect, animation_clutter_wobbly_effect, ANIMATION_CLUTTER, WOBBLY_EFFECT, ClutterDeformEffect) + +/** + * animation_clutter_wobbly_effect_grab: + * @effect: An #AnimationClutterWobblyEffect + * @x: The x-coordinate on the mesh to grab, specified relative to the + * upper-left corner of the mesh + * @y: The y-coordinate on the mesh to grab, specified relative to the + * upper-left corner of the mesh. + * + * Grabs the anchor specified by @x and @y on the mesh. While + * the mesh is in this state, this point will move immediately, + * causing spring forces to be applied to other points on the mesh + * + * It is a precondition violation to call this function when the mesh is + * already grabbed. + * + */ +void animation_clutter_wobbly_effect_grab (AnimationClutterWobblyEffect *effect, + double x, + double y); + +/** + * animation_clutter_wobbly_effect_ungrab: + * @effect: An #AnimationClutterWobblyEffect + * Removes the current grab. When the actor is moved, the mesh will + * move uniformly. + * + * It is a precondition violation to call this function when the mesh is + * not grabbed. + */ +void animation_clutter_wobbly_effect_ungrab (AnimationClutterWobblyEffect *effect); + +/** + * animation_clutter_wobbly_effect_move_by: + * @effect: An #AnimationClutterWobblyEffect + * @dx: A delta-x coordinate to move the mesh by + * @dy: A delta-y coordinate to move the mesh by + * + * Moves the mesh by @dx and @dy + * + * If the mesh is grabbed, then spring forces will be applied causing + * some points on the mesh to move more slowly than others. The nature + * of the moment will depend on the window's maximization state. + * + */ +void animation_clutter_wobbly_effect_move_by (AnimationClutterWobblyEffect *effect, + double dx, + double dy); + +/** + * animation_clutter_wobbly_effect_new: + * + * Creates a new #ClutterEffect which makes the window "wobble" + * on a spring mesh for the actor + * + * Returns: (transfer full): A new #ClutterEffect + */ +ClutterEffect * animation_clutter_wobbly_effect_new (void); + +G_END_DECLS diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index 5f68f5c..2df0bfd 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -34,7 +34,8 @@ animation_clutter_toplevel_headers = files([ 'animation-clutter-affine-effect.h', 'animation-clutter-actor-box-query.h', 'animation-clutter-common.h', - 'animation-clutter-grid-effect.h' + 'animation-clutter-grid-effect.h', + 'animation-clutter-wobbly-effect.h' ]) animation_clutter_toplevel_private_sources = files([]) @@ -42,7 +43,8 @@ animation_clutter_toplevel_introspectable_sources = files([ 'animation-clutter-affine-effect.c', 'animation-clutter-actor-box-query.c', 'animation-clutter-common.c', - 'animation-clutter-grid-effect.c' + 'animation-clutter-grid-effect.c', + 'animation-clutter-wobbly-effect.c' ]) animation_clutter_introspectable_sources += animation_clutter_toplevel_introspectable_sources From 45c9ae2311a7bb7160c1aa694cb07d4a02a0d977 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:36:08 +0300 Subject: [PATCH 35/60] debian: Add build-dependency on libmutter-4-dev --- debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index cd1086b..58364a3 100644 --- a/debian/control +++ b/debian/control @@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 8.0.0), libgirepository1.0-dev, libglib2.0-dev, libgtest-dev, + libmutter-4-dev, meson Standards-Version: 3.9.4 Section: libs From 719897c4c0e9d92d5195564ee51473635f8da070 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:36:29 +0300 Subject: [PATCH 36/60] debian: Move gir1.2-animation-glib to libs from non-free/libs --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 58364a3..a0ca64d 100644 --- a/debian/control +++ b/debian/control @@ -52,7 +52,7 @@ Description: 2D Surfaces Animations (GLib API) headers Replaces: libwobbly-glib-dev (<= 0.3.2) Package: gir1.2-animation-glib-0 -Section: non-free/libs +Section: libs Architecture: any Depends: ${gir:Depends}, ${misc:Depends}, From 66b231c2b950ed3c255808f23501191b5a97e460 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:37:01 +0300 Subject: [PATCH 37/60] debian: Add debian packages for libanimation-clutter0 --- debian/control | 26 +++++++++++++++++++++++ debian/gir1.2-animation-clutter-0.install | 1 + debian/libanimation-clutter-dev.install | 4 ++++ debian/libanimation-clutter0.install | 1 + 4 files changed, 32 insertions(+) create mode 100644 debian/gir1.2-animation-clutter-0.install create mode 100644 debian/libanimation-clutter-dev.install create mode 100644 debian/libanimation-clutter0.install diff --git a/debian/control b/debian/control index a0ca64d..a8999aa 100644 --- a/debian/control +++ b/debian/control @@ -61,3 +61,29 @@ Depends: ${gir:Depends}, Description: 2D Surfaces Animations library (GObject Introspection) files 2D Surfaces Animations library (GObject Introspection) files Replaces: gir1.2-libwobbly-glib0 (<= 0.3.2) + +Package: libanimation-clutter0 +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + libanimation-glib0 (= ${binary:Version}) +Description: 2D Surfaces Animations library (Clutter Implementation API) + 2D Surfaces Animations library + +Package: libanimation-clutter-dev +Section: libs +Architecture: any +Depends: libanimation-clutter0 (= ${binary:Version}), +Description: 2D Surfaces Animations (Clutter Implementation API) headers + 2D Surfaces Animations (Clutter Implementation API) headers + +Package: gir1.2-animation-clutter-0 +Section: libs +Architecture: any +Depends: ${gir:Depends}, + ${misc:Depends}, + ${shlibs:Depends}, + libanimation-clutter0 (= ${binary:Version}) +Description: 2D Surfaces Animations library, Clutter Implementation (GObject Introspection) files + 2D Surfaces Animations library, Clutter Implementation (GObject Introspection) files diff --git a/debian/gir1.2-animation-clutter-0.install b/debian/gir1.2-animation-clutter-0.install new file mode 100644 index 0000000..d3811bc --- /dev/null +++ b/debian/gir1.2-animation-clutter-0.install @@ -0,0 +1 @@ +usr/lib/*/girepository-1.0/AnimationClutter-0.typelib diff --git a/debian/libanimation-clutter-dev.install b/debian/libanimation-clutter-dev.install new file mode 100644 index 0000000..b182e31 --- /dev/null +++ b/debian/libanimation-clutter-dev.install @@ -0,0 +1,4 @@ +usr/include/animation-clutter/* +usr/lib/*/libanimation-clutter.so +usr/lib/*/pkgconfig/libanimation-clutter-0.pc +usr/share/gir-1.0/AnimationClutter-0.gir diff --git a/debian/libanimation-clutter0.install b/debian/libanimation-clutter0.install new file mode 100644 index 0000000..70887f7 --- /dev/null +++ b/debian/libanimation-clutter0.install @@ -0,0 +1 @@ +usr/lib/*/libanimation-clutter.so.* From 97148a4d9cb47a1ad1e439509a0776028af9db7b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:38:06 +0300 Subject: [PATCH 38/60] compositor-plugins/gs: Add GNOME-Shell extension for libanimation This uses AnimationClutter to provide animations for GNOME-Shell. It listens for certain windows events, then runs animations according to the settings in the org.gnome.shell.extensions.animation schema. --- compositor-plugins/gnome-shell/extension.js | 775 ++++++++++++++++++ compositor-plugins/gnome-shell/meson.build | 44 + compositor-plugins/gnome-shell/metadata.json | 6 + ...ome.shell.extensions.animation.gschema.xml | 277 +++++++ compositor-plugins/gnome-shell/stylesheet.css | 9 + compositor-plugins/meson.build | 22 + meson.build | 1 + 7 files changed, 1134 insertions(+) create mode 100644 compositor-plugins/gnome-shell/extension.js create mode 100644 compositor-plugins/gnome-shell/meson.build create mode 100644 compositor-plugins/gnome-shell/metadata.json create mode 100644 compositor-plugins/gnome-shell/org.gnome.shell.extensions.animation.gschema.xml create mode 100644 compositor-plugins/gnome-shell/stylesheet.css create mode 100644 compositor-plugins/meson.build diff --git a/compositor-plugins/gnome-shell/extension.js b/compositor-plugins/gnome-shell/extension.js new file mode 100644 index 0000000..742df79 --- /dev/null +++ b/compositor-plugins/gnome-shell/extension.js @@ -0,0 +1,775 @@ +/* + * /tests/js/extension.js + * + * Copyright (C) 2019 Sam Spilsbury. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Shell extension which implements libanimation to provide nice window + * animations. + */ + +const Animation = imports.gi.Animation; +const AnimationClutter = imports.gi.AnimationClutter; +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const Main = imports.ui.main; +const Tweener = imports.ui.tweener; + +const settings = new Gio.Settings({ + schema: 'org.gnome.shell.extensions.animation' +}); + +const BoxWrapper = GObject.registerClass({ + Properties: { + 'box': GObject.ParamSpec.boxed('box', + 'AnimationBox', + 'AnimationBox to animate to (static)', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Animation.Box) + } +}, class BoxWrapper extends Animation.BoxQuery { + _init(props) { + super._init(props); + this.update(this.box); + } +}); + +const bindObjectPropsToSettings = (animation, props, settings) => { + Object.keys(props).forEach(key => { + settings.bind(props[key], + animation, + key, + Gio.SettingsBindFlags.GET) + }); + + return animation; +}; + +const ZoomAnimation = GObject.registerClass({ +}, class ZoomAnimation extends AnimationClutter.AffineEffect { + constructor(props) { + super(props); + this._enabledId = 0; + } + + _startAnimation(animation, done) { + this.transform_animation = animation; + this.enabled = true; + + this._enabledId = this.connect('notify::enabled', () => { + if (!this.enabled) { + done(); + this.disconnect(this._enabledId); + this._enabledId = 0; + } + }); + } + + activate(event, detail, done) { + let [x, y] = this.actor.get_position(); + let [width, height] = this.actor.get_size(); + + switch(event) { + case 'open': + this._startAnimation(new Animation.ZoomAnimation({ + source: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + target: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.pointer[0], + y: detail.pointer[1] + }), + bottom_right: new Animation.Vector({ + x: detail.pointer[0], + y: detail.pointer[1] + }) + }) + }), + stepper: bindObjectPropsToSettings(new Animation.LinearStepper(), { + length: 'zoom-length' + }, settings) + }), done); + return true; + case 'close': + this._startAnimation(new Animation.ZoomAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.pointer[0], + y: detail.pointer[1] + }), + bottom_right: new Animation.Vector({ + x: detail.pointer[0], + y: detail.pointer[1] + }) + }) + }), + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: new Animation.ReverseStepper({ + base_stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'zoom-length' + }, settings) + }) + }), done); + return true; + case 'minimize': + this._startAnimation(new Animation.ZoomAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.icon.x, + y: detail.icon.y + }), + bottom_right: new Animation.Vector({ + x: detail.icon.x + detail.icon.width, + y: detail.icon.y + detail.icon.height + }) + }) + }), + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: new Animation.ReverseStepper({ + base_stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'zoom-length' + }, settings) + }) + }), done); + return true; + case 'unminimize': + this._startAnimation(new Animation.ZoomAnimation({ + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.icon.x, + y: detail.icon.y + }), + bottom_right: new Animation.Vector({ + x: detail.icon.x + detail.icon.width, + y: detail.icon.y + detail.icon.height + }) + }) + }), + stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'zoom-length' + }, settings) + }), done); + return true; + default: + return false; + } + } + + remove() { + if (this.actor) { + this.actor.remove_effect(this); + } + + this.set_enabled(false); + } +}); + +const BounceAnimation = GObject.registerClass({ +}, class BounceAnimation extends AnimationClutter.AffineEffect { + constructor(params) { + super(params); + this._enabledId = 0; + } + + _startAnimation(animation, done) { + this.transform_animation = animation; + this.enabled = true; + + this._enabledId = this.connect('notify::enabled', () => { + if (!this.enabled) { + done(); + this.disconnect(this._enabledId); + this._enabledId = 0; + } + }); + } + + activate(event, detail, done) { + let [x, y] = this.actor.get_position(); + let [width, height] = this.actor.get_size(); + + switch (event) { + case 'open': + this._startAnimation(bindObjectPropsToSettings(new Animation.BounceAnimation({ + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'bounce-length' + }, settings) + }), { + initial_scale: 'bounce-initial-scale', + maximum_scale: 'bounce-maximum-scale', + n_bounce: 'bounce-n-bounce', + }, settings), done); + return true; + case 'close': + this._startAnimation(bindObjectPropsToSettings(new Animation.BounceAnimation({ + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: new Animation.ReverseStepper({ + base_stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'bounce-length' + }, settings) + }) + }), { + initial_scale: 'bounce-initial-scale', + maximum_scale: 'bounce-maximum-scale', + n_bounce: 'bounce-n-bounce', + }, settings), done); + return true; + default: + return false; + } + } + + remove() { + if (this.actor) { + this.actor.remove_effect(this); + } + + this.set_enabled(false); + } +}); + + // A boune animation as attached to an actor. +const GlideAnimation = GObject.registerClass({ +}, class GlideAnimation extends AnimationClutter.AffineEffect { + constructor(params) { + super(params); + this._enabledId = 0; + } + + _startAnimation(animation, done) { + this.transform_animation = animation; + this.enabled = true; + + this._enabledId = this.connect('notify::enabled', () => { + if (!this.enabled) { + done(); + this.disconnect(this._enabledId); + this._enabledId = 0; + } + }); + } + + activate(event, detail, done) { + let [x, y] = this.actor.get_position(); + let [width, height] = this.actor.get_size(); + + switch (event) { + case 'open': + this._startAnimation(bindObjectPropsToSettings(new Animation.GlideAnimation({ + viewport: new AnimationClutter.ActorBoxQuery({ + actor: global.get_stage() + }), + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'glide-length' + }, settings) + }), { + x_rotation_angle_degrees: 'glide-x-rotation-angle-degrees', + y_rotation_angle_degrees: 'glide-y-rotation-angle-degrees', + x_axis_location_unit: 'glide-x-axis-location-unit', + y_axis_location_unit: 'glide-y-axis-location-unit', + }, settings), done); + return true; + case 'close': + this._startAnimation(bindObjectPropsToSettings(new Animation.GlideAnimation({ + viewport: new AnimationClutter.ActorBoxQuery({ + actor: global.get_stage() + }), + target: new AnimationClutter.ActorBoxQuery({ + actor: this.actor + }), + stepper: new Animation.ReverseStepper({ + base_stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'glide-length' + }, settings) + }) + }), { + x_rotation_angle_degrees: 'glide-x-rotation-angle-degrees', + y_rotation_angle_degrees: 'glide-y-rotation-angle-degrees', + x_axis_location_unit: 'glide-x-axis-location-unit', + y_axis_location_unit: 'glide-y-axis-location-unit', + }, settings), done); + return true; + default: + return false; + } + } + + remove() { + if (this.actor) { + this.actor.remove_effect(this); + } + } +}); + +const MetaWindowOffsetActorBoxQuery = GObject.registerClass({ + Properties: { + 'actor': GObject.ParamSpec.object('actor', + 'ClutterActor', + 'The ClutterActor', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Clutter.Actor), + 'window': GObject.ParamSpec.object('window', + 'MetaWindow', + 'The MetaWindow', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Meta.Window) + } +}, class MetaWindowOffsetActorBoxQuery extends Animation.BoxQuery { + _init(params) { + super._init(params); + + this.actor.connect('notify::x', () => this._updateGeometry()); + this.actor.connect('notify::y', () => this._updateGeometry()); + this.actor.connect('notify::width', () => this._updateGeometry()); + this.actor.connect('notify::height', () => this._updateGeometry()); + + this.window.connect('position-changed', () => this._updateGeometry()); + this.window.connect('size-changed', () => this._updateGeometry()); + + this._updateGeometry(); + } + + _updateGeometry() { + let [x, y] = this.actor.get_position(); + let [width, height] = this.actor.get_size(); + let actorGeometry = this.actor.get_meta_window().get_frame_rect(); + + // Need to know the difference between the window size with shadows + // and without shadows in order to offset the window box correctly. + let offsetX = Math.ceil((width - actorGeometry.width) / 2); + let offsetY = Math.ceil((height - actorGeometry.height) / 2); + + let box = { + x1: x - offsetX, + y1: y - offsetY, + x2: x - offsetX + width, + y2: y - offsetY + height + }; + + this.update(new Animation.Box({ + top_left: new Animation.Vector({ x: box.x1, y: box.y1 }), + bottom_right: new Animation.Vector({ x: box.x2, y: box.y2 }) + })); + } +}); + +// MagicLampAnimation +// +// A magic lamp animation as attached to an actor. +const MagicLampAnimation = GObject.registerClass({ +}, class MagicLampAnimation extends AnimationClutter.GridEffect { + constructor(params) { + super(params); + this._enabledId = 0; + } + + _startAnimation(animation, done) { + this.grid_animation = animation; + this.x_tiles = 2; + this.y_tiles = 100; + this.enabled = true; + + this._enabledId = this.connect('notify::enabled', () => { + if (!this.enabled) { + done(); + this.disconnect(this._enabledId); + this._enabledId = 0; + } + }); + } + + activate(event, detail, done) { + let [x, y] = this.actor.get_position(); + let [width, height] = this.actor.get_size(); + let actorGeometry = this.actor.get_meta_window().get_frame_rect(); + + // Need to know the difference between the window size with shadows + // and without shadows in order to offset the window box correctly. + let offsetX = Math.ceil((width - actorGeometry.width) / 2); + let offsetY = Math.ceil((height - actorGeometry.height) / 2); + + let box = { + x1: x - offsetX, + y1: y - offsetY, + x2: x - offsetX + width, + y2: y - offsetY + height + }; + + switch (event) { + case 'minimize': + this._startAnimation(bindObjectPropsToSettings(new Animation.MagicLampAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.icon.x, + y: detail.icon.y + }), + bottom_right: new Animation.Vector({ + x: detail.icon.x + detail.icon.width, + y: detail.icon.y + detail.icon.height + }) + }) + }), + target: new MetaWindowOffsetActorBoxQuery({ + actor: this.actor, + window: this.actor.get_meta_window() + }), + resolution: new Animation.Vector({ x: 2, y: 100 }), + stepper: new Animation.ReverseStepper({ + base_stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'magiclamp-length' + }, settings) + }) + }), { + bend_factor: 'magiclamp-bend-factor', + offset_factor: 'magiclamp-offset-factor', + stretch_factor: 'magiclamp-stretch-factor', + deform_speed_factor: 'magiclamp-deform-speed-factor', + }, settings), done); + return true; + case 'unminimize': + this._startAnimation(bindObjectPropsToSettings(new Animation.MagicLampAnimation({ + source: new BoxWrapper({ + box: new Animation.Box({ + top_left: new Animation.Vector({ + x: detail.icon.x, + y: detail.icon.y + }), + bottom_right: new Animation.Vector({ + x: detail.icon.x + detail.icon.width, + y: detail.icon.y + detail.icon.height + }) + }) + }), + target: new MetaWindowOffsetActorBoxQuery({ + actor: this.actor, + window: this.actor.get_meta_window() + }), + resolution: new Animation.Vector({ x: 2, y: 100 }), + stepper: bindObjectPropsToSettings(new Animation.LinearStepper({}), { + length: 'magiclamp-length' + }, settings) + }), { + bend_factor: 'magiclamp-bend-factor', + offset_factor: 'magiclamp-offset-factor', + stretch_factor: 'magiclamp-stretch-factor', + deform_speed_factor: 'magiclamp-deform-speed-factor', + }, settings), done); + return true; + default: + return false; + } + } + + remove() { + if (this.actor) { + this.actor.remove_effect(this); + } + + this.set_enabled(false); + } +}); + +const WobblyEffect = GObject.registerClass({ +}, class WobblyEffect extends AnimationClutter.WobblyEffect { + constructor(params) { + super(params); + bindObjectPropertiesToSettings(this, { + object_movement_range: 'wobbly-object-movement-range', + spring_k: 'wobbly-spring-k', + friction: 'wobbly-friction', + slowdown_factor: 'wobbly-slowdown-factor' + }, settings); + } + + activate(event, detail) { + switch (event) { + case 'move': + detail.grabbed ? this._grabbedByMouse() : this._ungrabbedByMouse(); + return true; + default: + return false; + } + } + + remove() { + if (this.actor) { + this.actor.remove_effect(this); + this.actor = null; + } + } + + _grabbedByMouse() { + let position = global.get_pointer(); + let actor = this.get_actor(); + this.grab(position[0], position[1]); + + this._lastPosition = actor.get_position(); + this._positionChangedId = + actor.connect('allocation-changed', (actor) => { + let position = actor.get_position(); + let dx = position[0] - this._lastPosition[0]; + let dy = position[1] - this._lastPosition[1]; + + this.move_by(dx, dy); + this._lastPosition = position; + }); + } + + _ungrabbedByMouse() { + // Only continue if we have an active grab and change notification + // on movement + if (!this._positionChangedId) + return; + + let actor = this.get_actor(); + this.ungrab(); + + actor.disconnect(this._positionChangedId); + this._positionChangedId = null; + } +}); + + +function init() { +} + +let connections = { +}; + +function disable() { + Object.keys(connections).forEach(k => { + let [signal, object] = connections[k]; + object.disconnect(signal); + delete connections[k]; + }); +} + +const ANIMATION_NAME_TO_CLASS = { + 'bounce': BounceAnimation, + 'zoom': ZoomAnimation, + 'magiclamp': MagicLampAnimation, + 'glide': GlideAnimation, +} + +const DEFAULT_EFFECTS = { + 'open': 'glide', + 'close': 'glide', + 'minimize': 'zoom', + 'unminimize': 'zoom', + 'move': 'none' +} + +function createAnimationForEvent(eventName) { + let effectName = settings.get_string(`${eventName}-effect`); + + if (effectName === 'default') { + effectName = DEFAULT_EFFECTS[eventName]; + } + + return [new ANIMATION_NAME_TO_CLASS[effectName]({}), effectName]; +} + +function enable() { + let wm = global.window_manager; + connections['minimize'] = [wm.connect_after('minimize', (shellwm, actor) => { + let [hasIcon, icon] = actor.meta_window.get_icon_geometry(); + + if (!hasIcon) + return; + + // Remove all existing tweens and minimize animations first first + if (Main.wm._removeEffect(Main.wm._minimizing, actor)) { + Tweener.removeTweens(actor); + actor.set_pivot_point(0, 0); + actor.scale_y = 1; + actor.scale_x = 1; + actor.translation_y = 0; + actor.translation_x = 0; + actor.opacity = 255; + } + + // Use libanimation to run an animation + let [animation, effectName] = createAnimationForEvent('minimize'); + actor.add_effect_with_name(`animation::${effectName}`, animation); + animation.activate('minimize', { + // Guaranteed to work, since the minimize effect is + // only available if this window has an icon + icon: icon, + }, () => { + shellwm.completed_minimize(actor); + actor.remove_effect(animation); + }); + }), wm]; + connections['unminimize'] = [wm.connect_after('unminimize', (shellwm, actor) => { + let [hasIcon, icon] = actor.meta_window.get_icon_geometry(); + + if (!hasIcon) + return; + + // Remove all existing tweens and minimize animations first first + if (Main.wm._removeEffect(Main.wm._unminimizing, actor)) { + Tweener.removeTweens(actor); + actor.set_pivot_point(0, 0); + actor.scale_y = 1; + actor.scale_x = 1; + actor.translation_y = 0; + actor.translation_x = 0; + actor.opacity = 255; + + // Also undo actor translation and return to frame rect + let rect = actor.meta_window.get_frame_rect(); + actor.set_position(rect.x, rect.y); + } + + // Use libanimation to run an animation + let [animation, effectName] = createAnimationForEvent('unminimize'); + actor.add_effect_with_name(`animation::${effectName}`, animation); + animation.activate('unminimize', { + // Guaranteed to work, since the minimize effect is + // only available if this window has an icon + icon: icon, + }, () => { + shellwm.completed_unminimize(actor); + actor.remove_effect(animation); + }); + }), wm]; + connections['map'] = [wm.connect_after('map', (shellwm, actor) => { + let types = [Meta.WindowType.NORMAL, + Meta.WindowType.DIALOG, + Meta.WindowType.MODAL_DIALOG]; + if (!Main.wm._shouldAnimateActor(actor, types) || actor._windowType != Meta.WindowType.NORMAL) { + // We weren't going to animate this actor anyway + return; + } + + // Remove all existing tweens and open animations first + if (Main.wm._removeEffect(Main.wm._mapping, actor)) { + Tweener.removeTweens(actor); + actor.set_pivot_point(0, 0); + actor.scale_y = 1; + actor.scale_x = 1; + actor.translation_y = 0; + actor.translation_x = 0; + actor.opacity = 0; + } + + // Use libanimation to run an animation + let [animation, effectName] = createAnimationForEvent('open'); + actor.add_effect_with_name(`animation::${effectName}`, animation); + animation.activate('open', {}, () => { + shellwm.completed_map(actor); + actor.remove_effect(animation); + }); + }), 'wm']; + connections['destroy'] = [wm.connect_after('destroy', (shellwm, actor) => { + let types = [Meta.WindowType.NORMAL, + Meta.WindowType.DIALOG, + Meta.WindowType.MODAL_DIALOG]; + if (!Main.wm._shouldAnimateActor(actor, types) || + (actor._windowType && actor._windowType != Meta.WindowType.NORMAL)) { + // We weren't going to animate this actor anyway + return; + } + + // Remove all existing tweens and destroy animations first + if (Main.wm._removeEffect(Main.wm._destroying, actor)) { + Tweener.removeTweens(actor); + actor.set_pivot_point(0, 0); + actor.scale_y = 1; + actor.scale_x = 1; + actor.translation_y = 0; + actor.translation_x = 0; + actor.opacity = 255; + } + + // Use libanimation to run an animation + let [animation, effectName] = createAnimationForEvent('close'); + actor.add_effect_with_name(`animation::${effectName}`, animation); + animation.activate('close', {}, () => { + shellwm.completed_destroy(actor); + // Can't remove the effect since the actor is already gone + }); + }), wm]; + + connections['grab-op-begin'] = [global.display.connect('grab-op-begin', (display, screen, window, op) => { + // Occassionally, window can be null, in cases where grab-op-begin + // was emitted on a window from shell-toolkit. Ignore these grabs. + if (!window) + return; + + if (window.is_override_redirect() || + op != Meta.GrabOp.MOVING) + return; + + // Right now we only have the wobbly effect for moving windows, + // so we just check directly if we were enabled. + if (settings.get_string('move-effect') !== 'wobbly') + return; + + let actor = window.get_compositor_private(); + + log(`Grab ${actor.x} ${actor.y} ${actor.width} ${actor.height}`); + + if (!actor._wobblyEffect) { + actor._wobblyEffect = new WobblyEffect({}); + actor.add_effect_with_name('animation::wobbly', actor._wobblyEffect); + } + + actor._wobblyEffect.activate('move', { + grabbed: true + }); + }), global.display]; + + connections['grab-op-end'] = [global.display.connect('grab-op-end', (display, screen, window, op) => { + // Occassionally, window can be null, in cases where grab-op-begin + // was emitted on a window from shell-toolkit. Ignore these grabs. + if (!window) + return; + + let actor = window.get_compositor_private(); + + if (!actor._wobblyEffect) { + return; + } + + actor._wobblyEffect.activate('move', { + grabbed: false + }); + }), global.display]; +} diff --git a/compositor-plugins/gnome-shell/meson.build b/compositor-plugins/gnome-shell/meson.build new file mode 100644 index 0000000..6f285a3 --- /dev/null +++ b/compositor-plugins/gnome-shell/meson.build @@ -0,0 +1,44 @@ +# /compositor-plugins/gnome-shell/meson.build +# +# Install the GNOME-Shell extension. +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +datadir = get_option('datadir') + +extension_name = 'animation@endlessm.com' + +extension_files = files([ + 'extension.js', + 'metadata.json', + 'stylesheet.css' +]) + +schemas_files = files([ + 'org.gnome.shell.extensions.animation.gschema.xml' +]) + +install_data( + schemas_files, + install_dir: join_paths(datadir, 'glib-2.0', 'schemas') +) + +install_data( + extension_files, + install_dir: join_paths(datadir, 'gnome-shell', 'extensions', extension_name) +) + diff --git a/compositor-plugins/gnome-shell/metadata.json b/compositor-plugins/gnome-shell/metadata.json new file mode 100644 index 0000000..9f7ea28 --- /dev/null +++ b/compositor-plugins/gnome-shell/metadata.json @@ -0,0 +1,6 @@ +{ + "name": "Animations", + "description": "Use libanimation to provide nice window animations", + "uuid": "animation@endlessm.com", + "shell-version": ["3.32.2"] +} diff --git a/compositor-plugins/gnome-shell/org.gnome.shell.extensions.animation.gschema.xml b/compositor-plugins/gnome-shell/org.gnome.shell.extensions.animation.gschema.xml new file mode 100644 index 0000000..fae1354 --- /dev/null +++ b/compositor-plugins/gnome-shell/org.gnome.shell.extensions.animation.gschema.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'default' + + Open Window Effect + + + Animation to use when opening a window. + + + + 'default' + + Close Window Effect + + + Animation to use when closing a window. + + + + 'default' + + Minimize Window Effect + + + Animation to use when minimize a window. + + + + 'default' + + Unminimize Window Effect + + + Animation to use when unminimize a window. + + + + 'default' + + Move Window Effect + + + Animation to use when moving a window. + + + + + 200 + + Glide animation length + + + How long the glide animation lasts + + + + + -0.3 + + Initial distance + + + Initial distance away from the camera + + + + + -20.0 + + X Rotation Angle + + + Angle to rotate around the x-axis + + + + + 0.0 + + Y Rotation Angle + + + Angle to rotate around the y-axis + + + + + 0.2 + + X Axis Location + + + Unit co-ordinates for X axis location + + + + + 200 + + Magic Lamp animation length + + + How long the Magic Lamp animation lasts + + + + + 10.0 + + Bend Factor + + + How much to bend the window during the animation + + + + + 0.5 + + Offset Factor + + + How much the waves curve during the animation + + + + + 0.45 + + Stretch Factor + + + How much the window stretches during the animation + + + + + 2.3 + + Deform Speed Factor + + + How quickly to deform the window during the animation + + + + + 0.5 + + Y Axis Location + + + Unit co-ordinates for Y axis location + + + + + 200 + + Zoom animation length + + + How long the zoom animation lasts + + + + + 200 + + Bounce animation length + + + How long the bounce animation lasts + + + + + 0.7 + + Initial scale + + + Initial scale of the surface being bounced + + + + + 1.3 + + Maximum scale + + + Maximum scale of the surface being bounced + + + + + 2 + + Number of Bounces + + + How many times the window oscillates from its original position + + + + + 8.0 + + Wobbly effect Spring Constant + + + Springiness of wobbly effect + + + + + 3.0 + + Wobbly effect Friction + + + Friction of wobbly effect + + + + + 1.0 + + Wobbly effect slowdown factor + + + Slowdown factor of wobbly effect (1.0 being normal speed) + + + + + 100.0 + + Wobbly effect object movement range + + + How much objects are allowed to move in the mesh. A higher range + allows for a more pronounced effect. + + + + diff --git a/compositor-plugins/gnome-shell/stylesheet.css b/compositor-plugins/gnome-shell/stylesheet.css new file mode 100644 index 0000000..9a2a332 --- /dev/null +++ b/compositor-plugins/gnome-shell/stylesheet.css @@ -0,0 +1,9 @@ + +.helloworld-label { + font-size: 36px; + font-weight: bold; + color: #ffffff; + background-color: rgba(10,10,10,0.7); + border-radius: 5px; + padding: .5em; +} diff --git a/compositor-plugins/meson.build b/compositor-plugins/meson.build new file mode 100644 index 0000000..02c22a6 --- /dev/null +++ b/compositor-plugins/meson.build @@ -0,0 +1,22 @@ +# /compositor-plugins/meson.build +# +# Install compositor plugins. +# +# Copyright (C) 2017, 2018 Endless Mobile, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +subdir('gnome-shell') + diff --git a/meson.build b/meson.build index 22acbd4..e4044d5 100644 --- a/meson.build +++ b/meson.build @@ -42,5 +42,6 @@ tests_inc = include_directories('tests') subdir('animation') subdir('animation-glib') subdir('animation-clutter') +subdir('compositor-plugins') subdir('matchers') subdir('tests') From c8d5c4b0b0097ff623e90490079bbc022653012a Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 15:39:07 +0300 Subject: [PATCH 39/60] debian: Add debian packaging for gnome-shell-extension-animation --- debian/control | 11 +++++++++++ debian/gnome-shell-extension-animation.install | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 debian/gnome-shell-extension-animation.install diff --git a/debian/control b/debian/control index a8999aa..46e3444 100644 --- a/debian/control +++ b/debian/control @@ -87,3 +87,14 @@ Depends: ${gir:Depends}, libanimation-clutter0 (= ${binary:Version}) Description: 2D Surfaces Animations library, Clutter Implementation (GObject Introspection) files 2D Surfaces Animations library, Clutter Implementation (GObject Introspection) files + +Package: gnome-shell-extension-animation +Section: libs +Architecture: any +Depends: ${gir:Depends}, + ${misc:Depends}, + ${shlibs:Depends}, + libanimation-clutter0 (= ${binary:Version}), + gir1.2-animation-clutter-0, + gnome-shell +Description: GNOME-Shell extension using 2D Surface Animation Library to provide window animations. diff --git a/debian/gnome-shell-extension-animation.install b/debian/gnome-shell-extension-animation.install new file mode 100644 index 0000000..abb1dd6 --- /dev/null +++ b/debian/gnome-shell-extension-animation.install @@ -0,0 +1,5 @@ +usr/share/glib-2.0/schemas/org.gnome.shell.extensions.animation.gschema.xml +usr/share/gnome-shell/extensions/animation@endlessm.com/extension.js +usr/share/gnome-shell/extensions/animation@endlessm.com/metadata.json +usr/share/gnome-shell/extensions/animation@endlessm.com/stylesheet.css + From 72ac20c369d74e13eefaef38a64a304090d2de3f Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Tue, 9 Jul 2019 23:55:46 +0300 Subject: [PATCH 40/60] travis: Add libmutter-4-dev to .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index df5b8a2..032c4d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM ubuntu:bionic > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev >> Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Db_sanitize=address,undefined -Dwerror=true builddir && ninja -C builddir && G_SLICE=always-malloc ./builddir/tests/animation_test --gtest_color=yes"; fi From 134e6a7adbf23462e64411012b3b82ea66fbc730 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 00:11:27 +0300 Subject: [PATCH 41/60] travis: Upgrade docker image to cosmic We need this for libmutter-4-dev --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 032c4d2..dc29e25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,9 @@ language: cpp services: - docker before_install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker pull ubuntu:bionic; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker pull ubuntu:disco; fi script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM ubuntu:bionic > Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM ubuntu:disco > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev >> Dockerfile; fi From 547bb57ce3f6f8d1117b839e21be725a858bd46b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 00:12:50 +0300 Subject: [PATCH 42/60] travis: Add repository for jasmine-gjs and install it --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dc29e25..cf8eb92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,9 @@ before_install: script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM ubuntu:disco > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "RUN echo 'deb [allow-insecure=yes] http://download.opensuse.org/repositories/home:/ptomato/xUbuntu_16.10/ /' > /etc/apt/sources.list.d/home:ptomato.list" >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev >> Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install --allow-unauthenticated -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev jasmine-gjs >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Db_sanitize=address,undefined -Dwerror=true builddir && ninja -C builddir && G_SLICE=always-malloc ./builddir/tests/animation_test --gtest_color=yes"; fi From a1f11c98a26923e45fe5a61be0bcae59912c97a8 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 11:07:06 +0300 Subject: [PATCH 43/60] travis: Disable sanitizers Seems like newer ubuntu images complain about the way that meson sets these up and linking with asan seems to hang, so we need to disable it for now. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cf8eb92..773d414 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,5 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install --allow-unauthenticated -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev jasmine-gjs >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Db_sanitize=address,undefined -Dwerror=true builddir && ninja -C builddir && G_SLICE=always-malloc ./builddir/tests/animation_test --gtest_color=yes"; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Dwerror=true builddir && ninja -C builddir && G_SLICE=always-malloc ./builddir/tests/animation_test --gtest_color=yes"; fi From f45bed2fad959839c3a0fa39bb9f4abc20cb2311 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 12:51:04 +0300 Subject: [PATCH 44/60] travis: Use meson test as opposed to test binary directly We now have additional JS tests that we would like to run --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 773d414..90c71cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,5 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install --allow-unauthenticated -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev jasmine-gjs >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Dwerror=true builddir && ninja -C builddir && G_SLICE=always-malloc ./builddir/tests/animation_test --gtest_color=yes"; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Dwerror=true builddir && meson test -C builddir -v --num-processes=1"; fi From c1753141c28873d50c6d3b9ce10ca9d642445a58 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 13:42:15 +0300 Subject: [PATCH 45/60] tests: Fix headerblock file path comment --- tests/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index f12a990..33fbeb3 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,4 +1,4 @@ -# /matchers/meson.build +# tests/meson.build # # Copyright (C) 2017, 2018 Endless Mobile, Inc. # From aeac19c3c82d2703a115678c858b86528c0ed05a Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Wed, 10 Jul 2019 13:48:42 +0300 Subject: [PATCH 46/60] travis: Just build jasmine-gjs instead of using Ubuntu package The built package on OBS is out of date and not compatible with mozjs60 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 90c71cb..8bede8d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,9 @@ before_install: script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo FROM ubuntu:disco > Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo ADD . /root >> Dockerfile; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "RUN echo 'deb [allow-insecure=yes] http://download.opensuse.org/repositories/home:/ptomato/xUbuntu_16.10/ /' > /etc/apt/sources.list.d/home:ptomato.list" >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get update >> Dockerfile; fi - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install --allow-unauthenticated -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev jasmine-gjs >> Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo RUN apt-get install --allow-unauthenticated -y meson ninja-build build-essential git pkg-config libglib2.0-dev gir1.2-glib-2.0 gobject-introspection libgirepository1.0-dev libmutter-4-dev git-core autoconf automake gjs >> Dockerfile; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "RUN git clone git://github.com/ptomato/jasmine-gjs && cd jasmine-gjs && ./autogen.sh --prefix=/usr && make && make install && rm -rf jasmine-gjs" >> Dockerfile; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker build -t withgit .; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run withgit /bin/sh -c "cd /root && TRAVIS=true CC=$CC CXX=$CXX meson -Dwerror=true builddir && meson test -C builddir -v --num-processes=1"; fi From d4ad04ebf06c7c456610fee1650dc1d1f2b0056b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 06:53:45 -0500 Subject: [PATCH 47/60] build: Use Clutter-7, now --- animation-clutter/meson.build | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index 2df0bfd..3466ec5 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -58,8 +58,8 @@ animation_clutter_sources = animation_clutter_introspectable_sources + animation glib = dependency('glib-2.0') gobject = dependency('gobject-2.0') -clutter = dependency('mutter-clutter-4') -mutter = dependency('libmutter-4') +clutter = dependency('mutter-clutter-7') +mutter = dependency('libmutter-7') mutter_typelibdir = mutter.get_pkgconfig_variable('typelibdir') install_rpath = mutter_typelibdir @@ -88,7 +88,7 @@ animation_clutter_gir = gnome.generate_gir( extra_args: ['--warn-all', '--warn-error'], identifier_prefix: 'AnimationClutter', include_directories: animation_inc, - includes: [animation_glib_gir, 'Clutter-4', 'GLib-2.0', 'GObject-2.0'], + includes: [animation_glib_gir, 'Clutter-7', 'GLib-2.0', 'GObject-2.0'], install: true, namespace: 'AnimationClutter', nsversion: api_version, From c2572cff67265c640985fc64affa8d27ebcae01a Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 06:54:31 -0500 Subject: [PATCH 48/60] animation-clutter: get_paint_volume -> modify_paint_volume --- animation-clutter/animation-clutter-affine-effect.c | 8 ++++---- animation-clutter/animation-clutter-grid-effect.c | 10 +++++----- animation-clutter/animation-clutter-wobbly-effect.c | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/animation-clutter/animation-clutter-affine-effect.c b/animation-clutter/animation-clutter-affine-effect.c index 5519531..8971809 100644 --- a/animation-clutter/animation-clutter-affine-effect.c +++ b/animation-clutter/animation-clutter-affine-effect.c @@ -56,8 +56,8 @@ G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterAffineEffect, CLUTTER_TYPE_EFFECT) static gboolean -animation_clutter_affine_effect_get_paint_volume (ClutterEffect *effect, - ClutterPaintVolume *volume) +animation_clutter_affine_effect_modify_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) { ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); ClutterActor *actor = clutter_actor_meta_get_actor (meta); @@ -67,7 +67,7 @@ animation_clutter_affine_effect_get_paint_volume (ClutterEffect *effect, /* We assume that the parent's get_paint_volume method always returns * TRUE here. */ - CLUTTER_EFFECT_CLASS (animation_clutter_affine_effect_parent_class)->get_paint_volume (effect, volume); + CLUTTER_EFFECT_CLASS (animation_clutter_affine_effect_parent_class)->modify_paint_volume (effect, volume); if (priv->transform_animation && clutter_actor_meta_get_enabled (meta)) { @@ -289,7 +289,7 @@ animation_clutter_affine_effect_class_init (AnimationClutterAffineEffectClass *k object_class->set_property = animation_clutter_affine_effect_set_property; object_class->dispose = animation_clutter_affine_effect_dispose; object_class->finalize = animation_clutter_affine_effect_finalize; - effect_class->get_paint_volume = animation_clutter_affine_effect_get_paint_volume; + effect_class->modify_paint_volume = animation_clutter_affine_effect_modify_paint_volume; effect_class->paint = animation_clutter_affine_effect_paint; object_properties[PROP_TRANSFORM_ANIMATION] = diff --git a/animation-clutter/animation-clutter-grid-effect.c b/animation-clutter/animation-clutter-grid-effect.c index 0bde879..ea2ae60 100644 --- a/animation-clutter/animation-clutter-grid-effect.c +++ b/animation-clutter/animation-clutter-grid-effect.c @@ -57,8 +57,8 @@ G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterGridEffect, CLUTTER_TYPE_DEFORM_EFFECT) static gboolean -animation_clutter_grid_effect_get_paint_volume (ClutterEffect *effect, - ClutterPaintVolume *volume) +animation_clutter_grid_effect_modify_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) { ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); ClutterActor *actor = clutter_actor_meta_get_actor (meta); @@ -66,9 +66,9 @@ animation_clutter_grid_effect_get_paint_volume (ClutterEffect *effect, AnimationClutterGridEffectPrivate *priv = animation_clutter_grid_effect_get_instance_private (grid_effect); - /* We assume that the parent's get_paint_volume method always returns + /* We assume that the parent's modify_paint_volume method always returns * TRUE here. */ - CLUTTER_EFFECT_CLASS (animation_clutter_grid_effect_parent_class)->get_paint_volume (effect, volume); + CLUTTER_EFFECT_CLASS (animation_clutter_grid_effect_parent_class)->modify_paint_volume (effect, volume); if (priv->grid_animation && clutter_actor_meta_get_enabled (meta)) { @@ -311,7 +311,7 @@ animation_clutter_grid_effect_class_init (AnimationClutterGridEffectClass *klass object_class->dispose = animation_clutter_grid_effect_dispose; object_class->finalize = animation_clutter_grid_effect_finalize; meta_class->set_actor = animation_clutter_grid_effect_set_actor; - effect_class->get_paint_volume = animation_clutter_grid_effect_get_paint_volume; + effect_class->modify_paint_volume = animation_clutter_grid_effect_modify_paint_volume; deform_class->deform_vertex = animation_clutter_grid_effect_deform_vertex; object_properties[PROP_GRID_ANIMATION] = diff --git a/animation-clutter/animation-clutter-wobbly-effect.c b/animation-clutter/animation-clutter-wobbly-effect.c index a9957cf..730479d 100644 --- a/animation-clutter/animation-clutter-wobbly-effect.c +++ b/animation-clutter/animation-clutter-wobbly-effect.c @@ -71,8 +71,8 @@ G_DEFINE_TYPE_WITH_PRIVATE (AnimationClutterWobblyEffect, CLUTTER_TYPE_DEFORM_EFFECT) static gboolean -animation_clutter_wobbly_effect_get_paint_volume (ClutterEffect *effect, - ClutterPaintVolume *volume) +animation_clutter_wobbly_effect_modify_paint_volume (ClutterEffect *effect, + ClutterPaintVolume *volume) { ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); ClutterActor *actor = clutter_actor_meta_get_actor (meta); @@ -80,9 +80,9 @@ animation_clutter_wobbly_effect_get_paint_volume (ClutterEffect *effect, AnimationClutterWobblyEffectPrivate *priv = animation_clutter_wobbly_effect_get_instance_private (wobbly_effect); - /* We assume that the parent's get_paint_volume method always returns + /* We assume that the parent's modify_paint_volume method always returns * TRUE here. */ - CLUTTER_EFFECT_CLASS (animation_clutter_wobbly_effect_parent_class)->get_paint_volume (effect, volume); + CLUTTER_EFFECT_CLASS (animation_clutter_wobbly_effect_parent_class)->modify_paint_volume (effect, volume); if (priv->model && clutter_actor_meta_get_enabled (meta)) { @@ -489,7 +489,7 @@ animation_clutter_wobbly_effect_class_init (AnimationClutterWobblyEffectClass *k object_class->set_property = animation_clutter_wobbly_effect_set_property; object_class->finalize = animation_clutter_wobbly_effect_finalize; meta_class->set_actor = animation_clutter_wobbly_effect_set_actor; - effect_class->get_paint_volume = animation_clutter_wobbly_effect_get_paint_volume; + effect_class->modify_paint_volume = animation_clutter_wobbly_effect_modify_paint_volume; deform_class->deform_vertex = animation_clutter_wobbly_effect_deform_vertex; object_properties[PROP_SPRING_K] = From 1c44576c3bad6b5d0666a7258f86b0cc8c2fd9f3 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 06:54:47 -0500 Subject: [PATCH 49/60] affine-effect: clutter_actor_continue_paint needs a ClutterPaintContext --- animation-clutter/animation-clutter-affine-effect.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/animation-clutter/animation-clutter-affine-effect.c b/animation-clutter/animation-clutter-affine-effect.c index 8971809..5515cf7 100644 --- a/animation-clutter/animation-clutter-affine-effect.c +++ b/animation-clutter/animation-clutter-affine-effect.c @@ -157,6 +157,7 @@ animation_clutter_affine_effect_new_frame (gpointer user_data) static void animation_clutter_affine_effect_paint (ClutterEffect *effect, + ClutterPaintContext *context, ClutterEffectPaintFlags flags) { ClutterActorMeta *meta = CLUTTER_ACTOR_META (effect); @@ -172,7 +173,7 @@ animation_clutter_affine_effect_paint (ClutterEffect *effect, clutter_actor_set_pivot_point (actor, 0, 0); clutter_actor_set_transform (actor, &matrix); - clutter_actor_continue_paint (actor); + clutter_actor_continue_paint (actor, context); } static void From e8014b43c5c57fc909d4068911bbd986f1bc30d6 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 06:55:03 -0500 Subject: [PATCH 50/60] animation-clutter: ClutterVertex -> graphene_point3d_t --- animation-clutter/animation-clutter-common.c | 4 ++-- animation-clutter/animation-clutter-grid-effect.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/animation-clutter/animation-clutter-common.c b/animation-clutter/animation-clutter-common.c index 8557e13..a687b6c 100644 --- a/animation-clutter/animation-clutter-common.c +++ b/animation-clutter/animation-clutter-common.c @@ -125,7 +125,7 @@ animation_clutter_get_untransformed_paint_box_from_existing_volume (ClutterActor const ClutterPaintVolume *volume, ClutterActorBox *box) { - ClutterVertex origin; + graphene_point3d_t origin; /* We don't have access to the stage projection matrix * so the best we can do is hope here that the volume is @@ -243,7 +243,7 @@ animation_clutter_expand_paint_volume_with_extremes (ClutterPaintVolume *volume, g_autoptr(ClutterPaintVolume) extremes_volume = clutter_paint_volume_copy (volume); - ClutterVertex const origin = { x1, y1, z1 }; + graphene_point3d_t const origin = { x1, y1, z1 }; clutter_paint_volume_set_origin (extremes_volume, &origin); clutter_paint_volume_set_width (extremes_volume, x2 - x1); clutter_paint_volume_set_height (extremes_volume, y2 - y1); diff --git a/animation-clutter/animation-clutter-grid-effect.c b/animation-clutter/animation-clutter-grid-effect.c index ea2ae60..7914fb8 100644 --- a/animation-clutter/animation-clutter-grid-effect.c +++ b/animation-clutter/animation-clutter-grid-effect.c @@ -102,7 +102,7 @@ animation_clutter_grid_effect_modify_paint_volume (ClutterEffect *effect, g_autoptr(ClutterPaintVolume) extremes_volume = clutter_paint_volume_copy (volume); - ClutterVertex const origin = { x1 - actor_x, y1 - actor_y, z1 }; + graphene_point3d_t const origin = { x1 - actor_x, y1 - actor_y, z1 }; clutter_paint_volume_set_origin (extremes_volume, &origin); clutter_paint_volume_set_width (extremes_volume, MAX (x2 - x1, 1.0)); clutter_paint_volume_set_height (extremes_volume, MAX (y2 - y1, 1.0)); From c83817a3f68579dd032dab80c103c50972d1faeb Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 06:55:53 -0500 Subject: [PATCH 51/60] tests: Add missing stepper test --- tests/js/testStepper.js | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/js/testStepper.js diff --git a/tests/js/testStepper.js b/tests/js/testStepper.js new file mode 100644 index 0000000..ccd496b --- /dev/null +++ b/tests/js/testStepper.js @@ -0,0 +1,48 @@ +/* + * /tests/js/testStepper.js + * + * Copyright (C) 2019 Sam Spilsbury . + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Tests for the JavaScript Binding to the Stepper. + */ + +const { Animation } = imports.gi; +const { BoxWrapper, applyCallerTranslation, multiplyMatrixVector } = imports.common; + +describe('Animation', function() { + describe('LinearStepper', function() { + let stepper; + + beforeEach(function() { + stepper = new Animation.LinearStepper({ + length: 200 + }); + }); + + it('has properties that were set on construction', function() { + expect(stepper.length).toEqual(200); + }); + + it('stops at 1.0 on the last step', function() { + expect(stepper.step(200)).toEqual(1.0); + }); + + it('behaves linearly', function() { + expect(stepper.step(100)).toEqual(0.5); + }); + }); +}); From 2c393d08ce313cbbaf7f51b9fa867ded181f93e5 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 18:51:57 -0500 Subject: [PATCH 52/60] shell: Drop use of Tweener It isn't used in newer shell versions. Hurrah! --- compositor-plugins/gnome-shell/extension.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/compositor-plugins/gnome-shell/extension.js b/compositor-plugins/gnome-shell/extension.js index 742df79..3b907a5 100644 --- a/compositor-plugins/gnome-shell/extension.js +++ b/compositor-plugins/gnome-shell/extension.js @@ -28,7 +28,6 @@ const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const Meta = imports.gi.Meta; const Main = imports.ui.main; -const Tweener = imports.ui.tweener; const settings = new Gio.Settings({ schema: 'org.gnome.shell.extensions.animation' @@ -615,7 +614,7 @@ function enable() { // Remove all existing tweens and minimize animations first first if (Main.wm._removeEffect(Main.wm._minimizing, actor)) { - Tweener.removeTweens(actor); + actor.remove_all_transitions(); actor.set_pivot_point(0, 0); actor.scale_y = 1; actor.scale_x = 1; @@ -644,7 +643,7 @@ function enable() { // Remove all existing tweens and minimize animations first first if (Main.wm._removeEffect(Main.wm._unminimizing, actor)) { - Tweener.removeTweens(actor); + actor.remove_all_transitions(); actor.set_pivot_point(0, 0); actor.scale_y = 1; actor.scale_x = 1; @@ -680,7 +679,7 @@ function enable() { // Remove all existing tweens and open animations first if (Main.wm._removeEffect(Main.wm._mapping, actor)) { - Tweener.removeTweens(actor); + actor.remove_all_transitions(); actor.set_pivot_point(0, 0); actor.scale_y = 1; actor.scale_x = 1; @@ -709,7 +708,7 @@ function enable() { // Remove all existing tweens and destroy animations first if (Main.wm._removeEffect(Main.wm._destroying, actor)) { - Tweener.removeTweens(actor); + actor.remove_all_transitions(); actor.set_pivot_point(0, 0); actor.scale_y = 1; actor.scale_x = 1; From a6b70875e006082fc0a02dded3b13340dad5698d Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Sun, 1 Nov 2020 18:52:29 -0500 Subject: [PATCH 53/60] shell: Don't disconnect signal on string --- compositor-plugins/gnome-shell/extension.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compositor-plugins/gnome-shell/extension.js b/compositor-plugins/gnome-shell/extension.js index 3b907a5..fc2e887 100644 --- a/compositor-plugins/gnome-shell/extension.js +++ b/compositor-plugins/gnome-shell/extension.js @@ -695,7 +695,7 @@ function enable() { shellwm.completed_map(actor); actor.remove_effect(animation); }); - }), 'wm']; + }), wm]; connections['destroy'] = [wm.connect_after('destroy', (shellwm, actor) => { let types = [Meta.WindowType.NORMAL, Meta.WindowType.DIALOG, From c63285d5e0e54e29a4da5c755eeff552691377e1 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:08:43 -0500 Subject: [PATCH 54/60] animtion-clutter: Upgrade to Clutter-8 --- animation-clutter/meson.build | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/animation-clutter/meson.build b/animation-clutter/meson.build index 3466ec5..074cb52 100644 --- a/animation-clutter/meson.build +++ b/animation-clutter/meson.build @@ -58,8 +58,8 @@ animation_clutter_sources = animation_clutter_introspectable_sources + animation glib = dependency('glib-2.0') gobject = dependency('gobject-2.0') -clutter = dependency('mutter-clutter-7') -mutter = dependency('libmutter-7') +clutter = dependency('mutter-clutter-8') +mutter = dependency('libmutter-8') mutter_typelibdir = mutter.get_pkgconfig_variable('typelibdir') install_rpath = mutter_typelibdir @@ -88,7 +88,7 @@ animation_clutter_gir = gnome.generate_gir( extra_args: ['--warn-all', '--warn-error'], identifier_prefix: 'AnimationClutter', include_directories: animation_inc, - includes: [animation_glib_gir, 'Clutter-7', 'GLib-2.0', 'GObject-2.0'], + includes: [animation_glib_gir, 'Clutter-8', 'GLib-2.0', 'GObject-2.0'], install: true, namespace: 'AnimationClutter', nsversion: api_version, From 85018b1f32ce09b091b8223380789879b4c79555 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:08:56 -0500 Subject: [PATCH 55/60] animation-clutter: Use graphene_matrix_t --- .../animation-clutter-affine-effect.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/animation-clutter/animation-clutter-affine-effect.c b/animation-clutter/animation-clutter-affine-effect.c index 5515cf7..764f00d 100644 --- a/animation-clutter/animation-clutter-affine-effect.c +++ b/animation-clutter/animation-clutter-affine-effect.c @@ -125,11 +125,11 @@ animation_clutter_affine_effect_new_frame (gpointer user_data) * need to do this before the actor effect is disabled, since * disabling it may cause the actor to be destroyed * and the actor to be detached from the effect. */ - CoglMatrix matrix; - cogl_matrix_init_identity (&matrix); + graphene_matrix_t matrix; + graphene_matrix_init_identity (&matrix); clutter_actor_set_opacity (actor, 255); clutter_actor_set_transform (actor, - (const ClutterMatrix *) &matrix); + (const graphene_matrix_t *) &matrix); /* Disable the effect */ clutter_actor_meta_set_enabled (meta, FALSE); @@ -167,9 +167,9 @@ animation_clutter_affine_effect_paint (ClutterEffect *effect, animation_clutter_affine_effect_get_instance_private (affine_effect); /* Apply the transform to the actor */ - ClutterMatrix matrix; - clutter_matrix_init_from_array (&matrix, - animation_transform_animation_matrix (priv->transform_animation)); + graphene_matrix_t matrix; + graphene_matrix_init_from_float (&matrix, + animation_transform_animation_matrix (priv->transform_animation)); clutter_actor_set_pivot_point (actor, 0, 0); clutter_actor_set_transform (actor, &matrix); @@ -190,9 +190,9 @@ animation_clutter_affine_effect_ensure_timeline (AnimationClutterAffineEffect *a priv->timeout_id = g_timeout_add (frame_length_ms, animation_clutter_affine_effect_new_frame, affine_effect); /* We need to show the actor and set the initial transform now to prevent flicker */ - ClutterMatrix matrix; - clutter_matrix_init_from_array (&matrix, - animation_transform_animation_matrix (priv->transform_animation)); + graphene_matrix_t matrix; + graphene_matrix_init_from_float (&matrix, + animation_transform_animation_matrix (priv->transform_animation)); float scaled_opacity = animation_transform_animation_progress (priv->transform_animation) * 255.0; guint8 opacity = (guint8) (scaled_opacity); From 8a4092da2bb33fef99040a851454f685bae8be44 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:09:24 -0500 Subject: [PATCH 56/60] wobbly_internal: No need for long double --- animation/wobbly/wobbly_internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/animation/wobbly/wobbly_internal.h b/animation/wobbly/wobbly_internal.h index 77d847a..a4ad585 100644 --- a/animation/wobbly/wobbly_internal.h +++ b/animation/wobbly/wobbly_internal.h @@ -1296,7 +1296,7 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const double const one_u_pow2 = one_u * one_u; double const one_v_pow2 = one_v * one_v; - long double const uCoefficients[] = + double const uCoefficients[] = { one_u * one_u * one_u, three_u * one_u_pow2, @@ -1304,7 +1304,7 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const u_pow2 * u }; - long double const vCoefficients[] = + double const vCoefficients[] = { one_v * one_v * one_v, three_v * one_v_pow2, From 9d79638c7a73efdd3656e3fc1e35fe9d4177dfb8 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:09:35 -0500 Subject: [PATCH 57/60] wobbly_internal: Save some computation --- animation/wobbly/wobbly_internal.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/animation/wobbly/wobbly_internal.h b/animation/wobbly/wobbly_internal.h index a4ad585..14f1402 100644 --- a/animation/wobbly/wobbly_internal.h +++ b/animation/wobbly/wobbly_internal.h @@ -1298,7 +1298,7 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const double const uCoefficients[] = { - one_u * one_u * one_u, + one_u_pow2 * one_u, three_u * one_u_pow2, 3 * u_pow2 * one_u, u_pow2 * u @@ -1306,7 +1306,7 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const double const vCoefficients[] = { - one_v * one_v * one_v, + one_v_pow2 * one_v, three_v * one_v_pow2, 3 * v_pow2 * one_v, v_pow2 * v From b9c433ec0ecacabc27b42d1c350f9aa8b9a9b438 Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:10:46 -0500 Subject: [PATCH 58/60] compositor-plugins/gnome-shell: Connect to position-changed --- compositor-plugins/gnome-shell/extension.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compositor-plugins/gnome-shell/extension.js b/compositor-plugins/gnome-shell/extension.js index fc2e887..6f8ef90 100644 --- a/compositor-plugins/gnome-shell/extension.js +++ b/compositor-plugins/gnome-shell/extension.js @@ -540,7 +540,8 @@ const WobblyEffect = GObject.registerClass({ this._lastPosition = actor.get_position(); this._positionChangedId = - actor.connect('allocation-changed', (actor) => { + actor.metaWindow.connect('position-changed', (window) => { + let actor = window.get_compositor_private() let position = actor.get_position(); let dx = position[0] - this._lastPosition[0]; let dy = position[1] - this._lastPosition[1]; From b67559c549a02539f7fc622cd97f5ce6260078ef Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 16:48:33 -0500 Subject: [PATCH 59/60] animation-clutter: No need to apply box offsets on wobbly effect anymore We can just query the extremes and use those --- .../animation-clutter-wobbly-effect.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/animation-clutter/animation-clutter-wobbly-effect.c b/animation-clutter/animation-clutter-wobbly-effect.c index 730479d..46d465a 100644 --- a/animation-clutter/animation-clutter-wobbly-effect.c +++ b/animation-clutter/animation-clutter-wobbly-effect.c @@ -86,13 +86,6 @@ animation_clutter_wobbly_effect_modify_paint_volume (ClutterEffect *effect, if (priv->model && clutter_actor_meta_get_enabled (meta)) { - ClutterActorBox box; - float actor_x, actor_y; - - animation_clutter_get_untransformed_paint_box_from_existing_volume (actor, volume, &box); - clutter_actor_get_position (actor, &actor_x, &actor_y); - - AnimationVector offset = { box.x1 - actor_x, box.y1 - actor_y }; AnimationVector extremes[4]; animation_wobbly_model_query_extremes (priv->model, @@ -108,10 +101,10 @@ animation_clutter_wobbly_effect_modify_paint_volume (ClutterEffect *effect, ClutterActorBox const extremesBox = { - floor (x1 + offset.x), - floor (y1 + offset.y), - ceil (x2 + offset.x), - ceil (y2 + offset.x) + floor (x1), + floor (y1), + ceil (x2), + ceil (y2) }; clutter_paint_volume_union_box (volume, &extremesBox); From e7bd21f63a971d991eed000c6d68b28dcef70b8b Mon Sep 17 00:00:00 2001 From: Sam Spilsbury Date: Mon, 2 Nov 2020 17:48:09 -0500 Subject: [PATCH 60/60] wobbly: some attempts to optimize things --- animation/wobbly/wobbly_internal.h | 57 +++++++++++++++++++++--- tests/wobbly/mesh_interpolation_test.cpp | 11 +++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/animation/wobbly/wobbly_internal.h b/animation/wobbly/wobbly_internal.h index 14f1402..850f2cf 100644 --- a/animation/wobbly/wobbly_internal.h +++ b/animation/wobbly/wobbly_internal.h @@ -1268,6 +1268,7 @@ wobbly::SpringMesh::CalculateForces (double springConstant) const }; } +// To make this faster inline animation::Point wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const { @@ -1312,11 +1313,52 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const v_pow2 * v }; - double x = 0.0; - double y = 0.0; + double const coefficients[] = + { + uCoefficients[0] * vCoefficients[0], + uCoefficients[0] * vCoefficients[0], + uCoefficients[0] * vCoefficients[1], + uCoefficients[0] * vCoefficients[1], + uCoefficients[0] * vCoefficients[2], + uCoefficients[0] * vCoefficients[2], + uCoefficients[0] * vCoefficients[3], + uCoefficients[0] * vCoefficients[3], + uCoefficients[1] * vCoefficients[0], + uCoefficients[1] * vCoefficients[0], + uCoefficients[1] * vCoefficients[1], + uCoefficients[1] * vCoefficients[1], + uCoefficients[1] * vCoefficients[2], + uCoefficients[1] * vCoefficients[2], + uCoefficients[1] * vCoefficients[3], + uCoefficients[1] * vCoefficients[3], + uCoefficients[2] * vCoefficients[0], + uCoefficients[2] * vCoefficients[0], + uCoefficients[2] * vCoefficients[1], + uCoefficients[2] * vCoefficients[1], + uCoefficients[2] * vCoefficients[2], + uCoefficients[2] * vCoefficients[2], + uCoefficients[2] * vCoefficients[3], + uCoefficients[2] * vCoefficients[3], + uCoefficients[3] * vCoefficients[0], + uCoefficients[3] * vCoefficients[0], + uCoefficients[3] * vCoefficients[1], + uCoefficients[3] * vCoefficients[1], + uCoefficients[3] * vCoefficients[2], + uCoefficients[3] * vCoefficients[2], + uCoefficients[3] * vCoefficients[3], + uCoefficients[3] * vCoefficients[3] + }; + + double pos[config::Width + config::Height] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + + for (size_t i = 0; i < config::Width * config::Height; ++i) + { + pos[(i * 2) % 8] += coefficients[i * 2] * mPoints[i * 2]; + pos[(i * 2 + 1) % 8] += coefficients[i * 2 + 1] * mPoints[i * 2 + 1]; + } /* This will access the point matrix in a linear fashion for - * cache-efficiency */ + * cache-efficiency */ /* for (size_t j = 0; j < config::Height; ++j) { for (size_t i = 0; i < config::Width; ++i) @@ -1324,10 +1366,13 @@ wobbly::BezierMesh::DeformUnitCoordsToMeshSpace (Point const &normalized) const size_t const xIdx = j * 2 * config::Width + i * 2; size_t const yIdx = j * 2 * config::Width + i * 2 + 1; - x += uCoefficients[j] * vCoefficients[i] * mPoints[xIdx]; - y += uCoefficients[j] * vCoefficients[i] * mPoints[yIdx]; + pos[i * 2] += uCoefficients[j] * vCoefficients[i] * mPoints[xIdx]; + pos[i * 2 + 1] += uCoefficients[j] * vCoefficients[i] * mPoints[yIdx]; } - } + }*/ + + double x = pos[0] + pos[2] + pos[4] + pos[6]; + double y = pos[1] + pos[3] + pos[5] + pos[7]; Point absolutePosition (x, y); return absolutePosition; diff --git a/tests/wobbly/mesh_interpolation_test.cpp b/tests/wobbly/mesh_interpolation_test.cpp index f839e20..8f7803b 100644 --- a/tests/wobbly/mesh_interpolation_test.cpp +++ b/tests/wobbly/mesh_interpolation_test.cpp @@ -206,6 +206,17 @@ namespace WithTolerance (10e-5))); } + TEST_F (BezierMesh, Performance) + { + double x = 0; + for (int i = 0; i < 100000000; ++i) { + auto const p = mesh.DeformUnitCoordsToMeshSpace (animation::Point(i / 100000000.0, i / 100000.0)); + x += agd::get <0> (p); + } + + EXPECT_EQ (x, 1.0); + } + /* Its somewhat difficult to test the non-linear case, considering * that the bezier patch evaluation is actually an interpolation * between four different parabolic functions. For now just check