Add RiveAnimationView 37/259837/4
authorHeeyong Song <heeyong.song@samsung.com>
Tue, 15 Jun 2021 16:13:12 +0000 (01:13 +0900)
committerTaehyub Kim <taehyub.kim@samsung.com>
Mon, 21 Jun 2021 09:07:53 +0000 (18:07 +0900)
Change-Id: Iee811b4e85a1b5e66c70a29d3247302f8ae27c55

28 files changed:
build/tizen/camera-player/configure.ac
build/tizen/color-controller/configure.ac
build/tizen/configure.ac
build/tizen/dali-extension/Makefile.am
build/tizen/dali-extension/configure.ac
build/tizen/image-loader/configure.ac
build/tizen/key/configure.ac
build/tizen/vector-animation-renderer/configure.ac
build/tizen/vector-image-renderer/configure.ac
build/tizen/video-player/configure.ac
build/tizen/web-engine-chromium/configure.ac
build/tizen/web-engine-lwe/configure.ac
dali-extension/devel-api/rive-animation-view/file.list [new file with mode: 0644]
dali-extension/devel-api/rive-animation-view/rive-animation-view.cpp [new file with mode: 0644]
dali-extension/devel-api/rive-animation-view/rive-animation-view.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/file.list [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-manager.cpp [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-manager.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-task.cpp [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-task.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-thread.cpp [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-thread.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-view-impl.cpp [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-animation-view-impl.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-rasterize-thread.cpp [new file with mode: 0644]
dali-extension/internal/rive-animation-view/rive-rasterize-thread.h [new file with mode: 0644]
dali-extension/internal/rive-animation-view/round-robin-container-view.h [new file with mode: 0644]
packaging/dali-extension.spec

index b579846..fb384cb 100755 (executable)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(CAPI_MEDIA_CAMERA, capi-media-camera)
 
@@ -23,6 +27,7 @@ PKG_CHECK_MODULES(ECORE_WL2, ecore-wl2)
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 0d30319..0054e06 100755 (executable)
@@ -16,10 +16,15 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 22a4747..5ba5d3a 100755 (executable)
@@ -28,6 +28,12 @@ AC_ARG_WITH([tizen_65_or_greater],
             [with_tizen_65_or_greater=yes],
             [with_tizen_65_or_greater=no])
 
+AC_ARG_ENABLE([debug],
+              [AC_HELP_STRING([--enable-debug],
+                              [Turns on debugging])],
+              [enable_debug=$enableval],
+              [enable_debug=no])
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 
 AC_CONFIG_SUBDIRS(key)
index 990e570..a29af6b 100755 (executable)
@@ -22,6 +22,10 @@ extension_src_dir = ../../../dali-extension
 include ../../../dali-extension/devel-api/evas-plugin/file.list
 include ../../../dali-extension/internal/evas-plugin/file.list
 
+# rive-animation-view
+include ../../../dali-extension/devel-api/rive-animation-view/file.list
+include ../../../dali-extension/internal/rive-animation-view/file.list
+
 lib_LTLIBRARIES =
 
 lib_LTLIBRARIES += libdali2-extension.la
@@ -29,7 +33,9 @@ lib_LTLIBRARIES += libdali2-extension.la
 # Todo Evas plugin separation
 libdali2_extension_la_SOURCES = \
             $(evas_plugin_devel_src_files) \
-            $(evas_plugin_internal_src_files)
+            $(evas_plugin_internal_src_files) \
+            $(rive_animation_view_devel_src_files) \
+            $(rive_animation_view_internal_src_files)
 
 libdali2_extension_la_DEPENDENCIES =
 
@@ -62,3 +68,6 @@ dali2_extension_HEADERS = ../../../dali-extension/dali-extension.h
 
 dali2_extension_evasplugindir = $(devincludepath)/dali-extension/devel-api/evas-plugin
 dali2_extension_evasplugin_HEADERS = $(evas_plugin_devel_header_files)
+
+dali2_extension_rive_animation_viewdir = $(devincludepath)/dali-extension/devel-api/rive-animation-view
+dali2_extension_rive_animation_view_HEADERS = $(rive_animation_view_devel_header_files)
index 47952bc..423d19a 100755 (executable)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 # For evas-plugin
 PKG_CHECK_MODULES(DALI_ADAPTOR_INTEGRATION, dali2-adaptor-integration)
 PKG_CHECK_MODULES(ELEMENTARY, elementary)
@@ -24,6 +28,7 @@ PKG_CHECK_MODULES(WAYLAND, [ecore-wl2])
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 609e762..76e0803 100755 (executable)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 AC_ARG_ENABLE([imageloader-extension],
               [AC_HELP_STRING([--enable-imageloader-extension],
               [enables the image loader extension of all the symbols in the library])],
@@ -28,6 +32,7 @@ PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 79d7318..f55ae51 100644 (file)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 AC_ARG_ENABLE([keyextension],
               [AC_HELP_STRING([--enable-keyextension],
               [enables the key extension of all the symbols in the library])],
@@ -26,6 +30,7 @@ AM_CONDITIONAL([USE_KEY_EXTENSION], [test x$enable_keyextension = xyes])
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index cd3641e..594759d 100644 (file)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(RLOTTIE, rlottie)
 
@@ -29,6 +33,7 @@ fi
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 65cb3bc..c99ac7f 100644 (file)
@@ -16,11 +16,16 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(THORVG, thorvg)
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 1d00150..96ef50c 100644 (file)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(CAPI_MEDIA_PLAYER, capi-media-player)
 PKG_CHECK_MODULES(CAPI_SYSTEM_INFO, capi-system-info)
@@ -35,6 +39,7 @@ fi
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 062a485..ce71c15 100644 (file)
@@ -16,6 +16,10 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(WAYLAND, libtbm)
 PKG_CHECK_MODULES(WEB_ENGINE_CHROMIUM, chromium-efl)
@@ -23,6 +27,7 @@ PKG_CHECK_MODULES(ELEMENTARY, elementary)
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
index 7f8bffa..6f2e29b 100644 (file)
@@ -16,12 +16,17 @@ LT_INIT
 DALI_EXTENSION_VERSION=dali_version
 AC_SUBST(DALI_EXTENSION_VERSION)
 
+if test "x$enable_debug" = "xyes"; then
+  DALI_CFLAGS="$DALI_CFLAGS -DDEBUG_ENABLED"
+fi
+
 PKG_CHECK_MODULES([DALI], [dali2-core dali2-adaptor dali2-toolkit])
 PKG_CHECK_MODULES(WAYLAND, libtbm)
 PKG_CHECK_MODULES([WEB_ENGINE_LWE], [lightweight-web-engine-dali-plugin])
 
 devincludepath=${includedir}
 AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
 
 AC_CONFIG_FILES([
 Makefile
diff --git a/dali-extension/devel-api/rive-animation-view/file.list b/dali-extension/devel-api/rive-animation-view/file.list
new file mode 100644 (file)
index 0000000..a6daf33
--- /dev/null
@@ -0,0 +1,6 @@
+rive_animation_view_devel_header_files = \
+   $(extension_src_dir)/devel-api/rive-animation-view/rive-animation-view.h
+
+rive_animation_view_devel_src_files = \
+   $(extension_src_dir)/devel-api/rive-animation-view/rive-animation-view.cpp
+
diff --git a/dali-extension/devel-api/rive-animation-view/rive-animation-view.cpp b/dali-extension/devel-api/rive-animation-view/rive-animation-view.cpp
new file mode 100644 (file)
index 0000000..508b8c5
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-extension/devel-api/rive-animation-view/rive-animation-view.h>
+
+// EXTERNAL INCLUDES
+//#include <dali/integration-api/debug.h>
+//#include <dali/public-api/object/property-map.h>
+
+// INTERNAL INCLUDES
+#include <dali-extension/internal/rive-animation-view/rive-animation-view-impl.h>
+
+namespace Dali
+{
+namespace Extension
+{
+RiveAnimationView::RiveAnimationView()
+{
+}
+
+RiveAnimationView::RiveAnimationView(const RiveAnimationView& riveAnimationView) = default;
+
+RiveAnimationView::RiveAnimationView(RiveAnimationView&& rhs) = default;
+
+RiveAnimationView& RiveAnimationView::operator=(const RiveAnimationView& riveAnimationView) = default;
+
+RiveAnimationView& RiveAnimationView::operator=(RiveAnimationView&& rhs) = default;
+
+RiveAnimationView::~RiveAnimationView()
+{
+}
+
+RiveAnimationView RiveAnimationView::New()
+{
+  return Internal::RiveAnimationView::New();
+}
+
+RiveAnimationView RiveAnimationView::New(const std::string& url)
+{
+  RiveAnimationView riveAnimationView = Internal::RiveAnimationView::New();
+  riveAnimationView[Extension::RiveAnimationView::Property::URL] = url;
+  return riveAnimationView;
+}
+
+RiveAnimationView RiveAnimationView::DownCast(BaseHandle handle)
+{
+  return Control::DownCast<RiveAnimationView, Internal::RiveAnimationView>(handle);
+}
+
+RiveAnimationView::RiveAnimationView(Internal::RiveAnimationView& implementation)
+: Control(implementation)
+{
+}
+
+RiveAnimationView::RiveAnimationView(Dali::Internal::CustomActor* internal)
+: Control(internal)
+{
+  VerifyCustomActorPointer<Internal::RiveAnimationView>(internal);
+}
+
+void RiveAnimationView::PlayAnimation()
+{
+  Extension::GetImplementation(*this).PlayAnimation();
+}
+
+void RiveAnimationView::StopAnimation()
+{
+  Extension::GetImplementation(*this).StopAnimation();
+}
+
+void RiveAnimationView::PauseAnimation()
+{
+  Extension::GetImplementation(*this).PauseAnimation();
+}
+
+RiveAnimationView::AnimationSignalType& RiveAnimationView::AnimationFinishedSignal()
+{
+  return Extension::GetImplementation(*this).AnimationFinishedSignal();
+}
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-extension/devel-api/rive-animation-view/rive-animation-view.h b/dali-extension/devel-api/rive-animation-view/rive-animation-view.h
new file mode 100644 (file)
index 0000000..d52077f
--- /dev/null
@@ -0,0 +1,206 @@
+#ifndef DALI_EXTENSION_RIVE_ANIMATION_VIEW_H
+#define DALI_EXTENSION_RIVE_ANIMATION_VIEW_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali-toolkit/public-api/controls/control.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal DALI_INTERNAL
+{
+class RiveAnimationView;
+}
+
+/**
+ * @brief RiveAnimationView is a class for displaying a rive content.
+ */
+class DALI_IMPORT_API RiveAnimationView : public Toolkit::Control
+{
+public:
+  /**
+   * @brief Enumeration for the start and end property ranges for this control.
+   */
+  enum PropertyRange
+  {
+    PROPERTY_START_INDEX = Control::CONTROL_PROPERTY_END_INDEX + 1,
+    PROPERTY_END_INDEX   = PROPERTY_START_INDEX + 1000,
+  };
+
+  /**
+   * @brief Enumeration for the instance of properties belonging to the RiveAnimationView class.
+   */
+  struct Property
+  {
+    /**
+     * @brief Enumeration for the instance of properties belonging to the RiveAnimationView class.
+     */
+    enum
+    {
+      /**
+       * @brief The URL of the image.
+       * @details Name "url", type Property::STRING
+       */
+      URL = PROPERTY_START_INDEX,
+
+      /**
+       * @brief The playing state the control will use.
+       * @details Name "playState", Type PlayState::Type (Property::INTEGER)
+       * @note This property is read-only.
+       */
+      PLAY_STATE,
+    };
+  };
+
+  /**
+   * @brief Animation signal type
+   */
+  using AnimationSignalType = Signal<void(RiveAnimationView)>;
+
+  /**
+   * @brief Enumeration for what state the animation is in.
+   */
+  enum class PlayState : uint8_t
+  {
+    STOPPED, ///< Animation has stopped
+    PLAYING, ///< The animation is playing
+    PAUSED   ///< The animation is paused
+  };
+
+public:
+  /**
+   * @brief Creates an uninitialized RiveAnimationView.
+   */
+  RiveAnimationView();
+
+  /**
+   * @brief Create an initialized RiveAnimationView.
+   *
+   * @return A handle to a newly allocated RiveAnimationView
+   *
+   * @note RiveAnimationView will not display anything.
+   */
+  static RiveAnimationView New();
+
+  /**
+   * @brief Creates an initialized RiveAnimationView from an URL to an image resource.
+   *
+   * @param[in] url The url of the image resource to display
+   * @return A handle to a newly allocated RiveAnimationView
+   */
+  static RiveAnimationView New(const std::string& url);
+
+  /**
+   * @brief Destructor.
+   *
+   * This is non-virtual since derived Handle types must not contain data or virtual methods.
+   */
+  ~RiveAnimationView();
+
+  /**
+   * @brief Copy constructor.
+   *
+   * @param[in] riveAnimationView RiveAnimationView to copy. The copied RiveAnimationView will point at the same implementation
+   */
+  RiveAnimationView(const RiveAnimationView& riveAnimationView);
+
+  /**
+   * @brief Move constructor
+   *
+   * @param[in] rhs A reference to the moved handle
+   */
+  RiveAnimationView(RiveAnimationView&& rhs);
+
+  /**
+   * @brief Assignment operator.
+   *
+   * @param[in] riveAnimationView The RiveAnimationView to assign from
+   * @return The updated RiveAnimationView
+   */
+  RiveAnimationView& operator=(const RiveAnimationView& riveAnimationView);
+
+  /**
+   * @brief Move assignment
+   *
+   * @param[in] rhs A reference to the moved handle
+   * @return A reference to this
+   */
+  RiveAnimationView& operator=(RiveAnimationView&& rhs);
+
+  /**
+   * @brief Downcasts a handle to RiveAnimationView handle.
+   *
+   * If handle points to a RiveAnimationView, the downcast produces valid handle.
+   * If not, the returned handle is left uninitialized.
+   *
+   * @param[in] handle Handle to an object
+   * @return Handle to a RiveAnimationView or an uninitialized handle
+   */
+  static RiveAnimationView DownCast(BaseHandle handle);
+
+  /**
+   * @brief Play the rive animation.
+   */
+  void PlayAnimation();
+
+  /**
+   * @brief Stop the rive animation.
+   */
+  void StopAnimation();
+
+  /**
+   * @brief Pause the rive animation.
+   */
+  void PauseAnimation();
+
+  /**
+   * @brief Connects to this signal to be notified when animations have finished.
+   *
+   * @return A signal object to connect with
+   */
+  AnimationSignalType& AnimationFinishedSignal();
+
+public: // Not intended for application developers
+  /// @cond internal
+  /**
+   * @brief Creates a handle using the Extension::Internal implementation.
+   *
+   * @param[in] implementation The RiveAnimationView implementation
+   */
+  DALI_INTERNAL RiveAnimationView(Internal::RiveAnimationView& implementation);
+
+  /**
+   * @brief Allows the creation of this RiveAnimationView from an Internal::CustomActor pointer.
+   *
+   * @param[in] internal A pointer to the internal CustomActor
+   */
+  DALI_INTERNAL RiveAnimationView(Dali::Internal::CustomActor* internal);
+  /// @endcond
+};
+
+/**
+ * @}
+ */
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_RIVE_ANIMATION_VIEW_H
diff --git a/dali-extension/internal/rive-animation-view/file.list b/dali-extension/internal/rive-animation-view/file.list
new file mode 100644 (file)
index 0000000..9181412
--- /dev/null
@@ -0,0 +1,15 @@
+rive_animation_view_internal_header_files = \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-manager.h \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-task.h \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-thread.h \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-view-impl.h \
+   $(extension_src_dir)/internal/rive-animation-view/rive-rasterize-thread.h \
+   $(extension_src_dir)/internal/rive-animation-view/round-robin-container-view.h
+
+rive_animation_view_internal_src_files = \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-manager.cpp \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-task.cpp \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-thread.cpp \
+   $(extension_src_dir)/internal/rive-animation-view/rive-animation-view-impl.cpp \
+   $(extension_src_dir)/internal/rive-animation-view/rive-rasterize-thread.cpp
+
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-manager.cpp b/dali-extension/internal/rive-animation-view/rive-animation-manager.cpp
new file mode 100644 (file)
index 0000000..545e8aa
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-extension/internal/rive-animation-view/rive-animation-manager.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-extension/internal/rive-animation-view/rive-animation-thread.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gRiveAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RIVE_ANIMATION");
+#endif
+
+} // unnamed namespace
+
+std::unique_ptr<RiveAnimationManager> RiveAnimationManager::mInstance = nullptr;
+std::once_flag RiveAnimationManager::mOnceFlag;
+
+RiveAnimationManager& RiveAnimationManager::GetInstance()
+{
+  std::call_once(mOnceFlag, []() {
+    mInstance.reset(new RiveAnimationManager);
+  });
+  return *(mInstance.get());
+}
+
+RiveAnimationManager::RiveAnimationManager()
+: mEventCallbacks(),
+  mLifecycleObservers(),
+  mRiveAnimationThread(nullptr),
+  mProcessorRegistered(false)
+{
+}
+
+RiveAnimationManager::~RiveAnimationManager()
+{
+  for(auto&& iter : mEventCallbacks)
+  {
+    delete iter;
+  }
+  mEventCallbacks.clear();
+
+  if(mProcessorRegistered)
+  {
+    Adaptor::Get().UnregisterProcessor(*this);
+  }
+
+  for(auto observer : mLifecycleObservers)
+  {
+    observer->RiveAnimationManagerDestroyed();
+  }
+}
+
+void RiveAnimationManager::AddObserver(RiveAnimationManager::LifecycleObserver& observer)
+{
+  DALI_ASSERT_DEBUG(mLifecycleObservers.end() == std::find(mLifecycleObservers.begin(), mLifecycleObservers.end(), &observer));
+  mLifecycleObservers.push_back(&observer);
+}
+
+void RiveAnimationManager::RemoveObserver(RiveAnimationManager::LifecycleObserver& observer)
+{
+  auto iterator = std::find(mLifecycleObservers.begin(), mLifecycleObservers.end(), &observer);
+  if(iterator != mLifecycleObservers.end())
+  {
+    mLifecycleObservers.erase(iterator);
+  }
+}
+
+RiveAnimationThread& RiveAnimationManager::GetRiveAnimationThread()
+{
+  if(!mRiveAnimationThread)
+  {
+    mRiveAnimationThread = std::unique_ptr<RiveAnimationThread>(new RiveAnimationThread());
+    mRiveAnimationThread->Start();
+  }
+  return *mRiveAnimationThread;
+}
+
+void RiveAnimationManager::RegisterEventCallback(CallbackBase* callback)
+{
+  mEventCallbacks.push_back(callback);
+
+  if(!mProcessorRegistered)
+  {
+    Adaptor::Get().RegisterProcessor(*this);
+    mProcessorRegistered = true;
+  }
+}
+
+void RiveAnimationManager::UnregisterEventCallback(CallbackBase* callback)
+{
+  auto iter = std::find(mEventCallbacks.begin(), mEventCallbacks.end(), callback);
+  if(iter != mEventCallbacks.end())
+  {
+    mEventCallbacks.erase(iter);
+
+    if(mEventCallbacks.empty())
+    {
+      if(Adaptor::IsAvailable())
+      {
+        Adaptor::Get().UnregisterProcessor(*this);
+        mProcessorRegistered = false;
+      }
+    }
+  }
+}
+
+void RiveAnimationManager::Process(bool postProcessor)
+{
+  for(auto&& iter : mEventCallbacks)
+  {
+    CallbackBase::Execute(*iter);
+    delete iter;
+  }
+  mEventCallbacks.clear();
+
+  Adaptor::Get().UnregisterProcessor(*this);
+  mProcessorRegistered = false;
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-manager.h b/dali-extension/internal/rive-animation-view/rive-animation-manager.h
new file mode 100644 (file)
index 0000000..b5a4811
--- /dev/null
@@ -0,0 +1,125 @@
+#ifndef DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_MANAGER_H
+#define DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_MANAGER_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/processor-interface.h>
+#include <dali/public-api/common/vector-wrapper.h>
+#include <dali/public-api/signals/callback.h>
+#include <memory>
+#include <mutex>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+class RiveAnimationThread;
+class RiveAnimationManager;
+
+/**
+ * @brief Rive animation manager
+ */
+class RiveAnimationManager : public Integration::Processor
+{
+public:
+  struct LifecycleObserver
+  {
+    virtual void RiveAnimationManagerDestroyed() = 0;
+  };
+
+  /**
+   * @brief GetInstance.
+   */
+  static RiveAnimationManager& GetInstance();
+
+  /**
+   * @brief Constructor.
+   */
+  RiveAnimationManager();
+
+  /**
+   * @brief Destructor.
+   */
+  ~RiveAnimationManager() override;
+
+  /**
+   * Add a lifecycle observer
+   * @param[in] observer The object watching this one
+   */
+  void AddObserver(LifecycleObserver& observer);
+
+  /**
+   * Remove a lifecycle observer
+   * @param[in] observer The object watching this one
+   */
+  void RemoveObserver(LifecycleObserver& observer);
+
+  /**
+   * Get the rive animation thread.
+   * @return A raw pointer pointing to the rive animation thread.
+   */
+  RiveAnimationThread& GetRiveAnimationThread();
+
+  /**
+   * @brief Register a callback.
+   *
+   * @param callback The callback to register
+   * @note Ownership of the callback is passed onto this class.
+   */
+  void RegisterEventCallback(CallbackBase* callback);
+
+  /**
+   * @brief Unregister a previously registered callback
+   *
+   * @param callback The callback to unregister
+   */
+  void UnregisterEventCallback(CallbackBase* callback);
+
+protected: // Implementation of Processor
+  /**
+   * @copydoc Dali::Integration::Processor::Process()
+   */
+  void Process(bool postProcessor) override;
+
+private:
+  // Undefined
+  RiveAnimationManager(const RiveAnimationManager& manager) = delete;
+
+  // Undefined
+  RiveAnimationManager& operator=(const RiveAnimationManager& manager) = delete;
+
+private:
+  static std::unique_ptr<RiveAnimationManager> mInstance;
+  static std::once_flag                        mOnceFlag;
+
+  std::vector<CallbackBase*>           mEventCallbacks;
+  std::vector<LifecycleObserver*>      mLifecycleObservers;
+  std::unique_ptr<RiveAnimationThread> mRiveAnimationThread;
+  bool                                 mProcessorRegistered;
+};
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_MANAGER_H
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-task.cpp b/dali-extension/internal/rive-animation-view/rive-animation-task.cpp
new file mode 100644 (file)
index 0000000..05cff2d
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-extension/internal/rive-animation-view/rive-animation-task.h>
+
+// EXTERNAL INCLUDES
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/math/math-utils.h>
+#include <dali/public-api/object/property-array.h>
+
+// INTERNAL INCLUDES
+#include <dali-extension/internal/rive-animation-view/rive-animation-manager.h>
+#include <dali-extension/internal/rive-animation-view/rive-animation-thread.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+namespace
+{
+constexpr auto LOOP_FOREVER = -1;
+constexpr auto MICROSECONDS_PER_SECOND(1e+6);
+
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gRiveAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RIVE_ANIMATION");
+#endif
+
+} // unnamed namespace
+
+RiveAnimationTask::RiveAnimationTask()
+: mUrl(),
+  mVectorRenderer(VectorAnimationRenderer::New()),
+  mAnimationData(),
+  mRiveAnimationThread(RiveAnimationManager::GetInstance().GetRiveAnimationThread()),
+  mConditionalWait(),
+  mAnimationFinishedTrigger(),
+  mPlayState(PlayState::STOPPED),
+  mNextFrameStartTime(),
+  mFrameDurationMicroSeconds(MICROSECONDS_PER_SECOND / 60.0f),
+  mFrameRate(60.0f),
+  mCurrentFrame(0),
+  mTotalFrame(0),
+  mStartFrame(0),
+  mEndFrame(0),
+  mDroppedFrames(0),
+  mWidth(0),
+  mHeight(0),
+  mAnimationDataIndex(0),
+  mUpdateFrameNumber(false),
+  mNeedAnimationFinishedTrigger(true),
+  mAnimationDataUpdated(false),
+  mDestroyTask(false)
+{
+}
+
+RiveAnimationTask::~RiveAnimationTask()
+{
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::~RiveAnimationTask: destructor [%p]\n", this);
+}
+
+void RiveAnimationTask::Finalize()
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  // Release some objects in the main thread
+  if(mAnimationFinishedTrigger)
+  {
+    mAnimationFinishedTrigger.reset();
+  }
+
+  mVectorRenderer.Finalize();
+
+  mDestroyTask = true;
+}
+
+bool RiveAnimationTask::Load(const std::string& url)
+{
+  mUrl = url;
+
+  if(!mVectorRenderer.Load(mUrl))
+  {
+    DALI_LOG_ERROR("RiveAnimationTask::Load: Load failed [%s]\n", mUrl.c_str());
+    return false;
+  }
+
+  mTotalFrame = mVectorRenderer.GetTotalFrameNumber();
+
+  mEndFrame = mTotalFrame - 1;
+
+  mFrameRate                 = mVectorRenderer.GetFrameRate();
+  mFrameDurationMicroSeconds = MICROSECONDS_PER_SECOND / mFrameRate;
+
+  uint32_t width, height;
+  mVectorRenderer.GetDefaultSize(width, height);
+
+  SetSize(width, height);
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::Load: file = %s [%d frames, %f fps] [%p]\n", mUrl.c_str(), mTotalFrame, mFrameRate, this);
+
+  return true;
+}
+
+void RiveAnimationTask::SetRenderer(Renderer renderer)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  mVectorRenderer.SetRenderer(renderer);
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::SetRenderer [%p]\n", this);
+}
+
+void RiveAnimationTask::SetAnimationData(const AnimationData& data)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::SetAnimationData [%p]\n", this);
+
+  uint32_t index = mAnimationDataIndex == 0 ? 1 : 0; // Use the other buffer
+
+  mAnimationData[index] = data;
+  mAnimationDataUpdated = true;
+
+  if(data.resendFlag & RiveAnimationTask::RESEND_SIZE)
+  {
+    // The size should be changed in the main thread.
+    SetSize(data.width, data.height);
+  }
+
+  mRiveAnimationThread.AddTask(this);
+}
+
+void RiveAnimationTask::SetSize(uint32_t width, uint32_t height)
+{
+  if(mWidth != width || mHeight != height)
+  {
+    mVectorRenderer.SetSize(width, height);
+
+    mWidth  = width;
+    mHeight = height;
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::SetSize: width = %d, height = %d [%p]\n", width, height, this);
+  }
+}
+
+void RiveAnimationTask::PlayAnimation()
+{
+  if(mPlayState != PlayState::PLAYING)
+  {
+    mNeedAnimationFinishedTrigger = true;
+    mUpdateFrameNumber            = false;
+    mPlayState                    = PlayState::PLAYING;
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::PlayAnimation: Play [%p]\n", this);
+  }
+}
+
+void RiveAnimationTask::StopAnimation()
+{
+  if(mPlayState != PlayState::STOPPING)
+  {
+    mNeedAnimationFinishedTrigger = false;
+    mPlayState                    = PlayState::STOPPING;
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::StopAnimation: Stop [%p]\n", this);
+  }
+}
+
+void RiveAnimationTask::PauseAnimation()
+{
+  if(mPlayState == PlayState::PLAYING)
+  {
+    mPlayState = PlayState::PAUSED;
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::PauseAnimation: Pause [%p]\n", this);
+  }
+}
+
+void RiveAnimationTask::SetAnimationFinishedCallback(EventThreadCallback* callback)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+  if(callback)
+  {
+    mAnimationFinishedTrigger = std::unique_ptr<EventThreadCallback>(callback);
+  }
+}
+
+
+void RiveAnimationTask::GetDefaultSize(uint32_t& width, uint32_t& height) const
+{
+  mVectorRenderer.GetDefaultSize(width, height);
+}
+
+RiveAnimationTask::UploadCompletedSignalType& RiveAnimationTask::UploadCompletedSignal()
+{
+  return mVectorRenderer.UploadCompletedSignal();
+}
+
+bool RiveAnimationTask::Rasterize()
+{
+  bool     stopped = false;
+  uint32_t currentFrame;
+
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+    if(mDestroyTask)
+    {
+      // The task will be destroyed. We don't need rasterization.
+      return false;
+    }
+  }
+
+  ApplyAnimationData();
+
+  if(mPlayState == PlayState::PLAYING && mUpdateFrameNumber)
+  {
+    mCurrentFrame = mCurrentFrame + mDroppedFrames + 1;
+    Dali::ClampInPlace(mCurrentFrame, mStartFrame, mEndFrame);
+  }
+
+  currentFrame = mCurrentFrame;
+
+  mUpdateFrameNumber = true;
+
+  if(mPlayState == PlayState::STOPPING)
+  {
+    currentFrame  = mCurrentFrame;
+    stopped       = true;
+  }
+  else if(mPlayState == PlayState::PLAYING)
+  {
+    bool animationFinished = false;
+
+    if(currentFrame >= mEndFrame) // last frame
+    {
+      animationFinished = true; // end of animation
+    }
+
+    if(animationFinished)
+    {
+      mPlayState = PlayState::STOPPING;
+    }
+  }
+
+  // Rasterize
+  bool renderSuccess = false;
+  if(mVectorRenderer)
+  {
+    renderSuccess = mVectorRenderer.Render(currentFrame);
+    if(!renderSuccess)
+    {
+      DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::Rasterize: Rendering failed. Try again later.[%d] [%p]\n", currentFrame, this);
+      mUpdateFrameNumber = false;
+    }
+  }
+
+  if(stopped && renderSuccess)
+  {
+    mPlayState   = PlayState::STOPPED;
+
+    // Animation is finished
+    {
+      ConditionalWait::ScopedLock lock(mConditionalWait);
+      if(mNeedAnimationFinishedTrigger && mAnimationFinishedTrigger)
+      {
+        mAnimationFinishedTrigger->Trigger();
+      }
+    }
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationTask::Rasterize: Animation is finished [current = %d] [%p]\n", currentFrame, this);
+  }
+
+  bool keepAnimation = true;
+  if(mPlayState == PlayState::PAUSED || mPlayState == PlayState::STOPPED)
+  {
+    keepAnimation = false;
+  }
+
+  return keepAnimation;
+}
+
+RiveAnimationTask::TimePoint RiveAnimationTask::CalculateNextFrameTime(bool renderNow)
+{
+  // std::chrono::time_point template has second parameter duration which defaults to the std::chrono::system_clock supported
+  // duration. In some C++11 implementations it is a milliseconds duration, so it fails to compile unless mNextFrameStartTime
+  // is casted to use the default duration.
+  mNextFrameStartTime = std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds));
+  auto current        = std::chrono::system_clock::now();
+  if(renderNow)
+  {
+    mNextFrameStartTime = current;
+    mDroppedFrames      = 0;
+  }
+  else if(mNextFrameStartTime < current)
+  {
+    uint32_t droppedFrames = 0;
+
+    while(current > std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds)))
+    {
+      droppedFrames++;
+      mNextFrameStartTime = std::chrono::time_point_cast<TimePoint::duration>(mNextFrameStartTime + std::chrono::microseconds(mFrameDurationMicroSeconds));
+    }
+
+    mNextFrameStartTime = current;
+    mDroppedFrames      = droppedFrames;
+  }
+
+  return mNextFrameStartTime;
+}
+
+RiveAnimationTask::TimePoint RiveAnimationTask::GetNextFrameTime()
+{
+  return mNextFrameStartTime;
+}
+
+void RiveAnimationTask::ApplyAnimationData()
+{
+  uint32_t index;
+
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+
+    if(!mAnimationDataUpdated || mAnimationData[mAnimationDataIndex].resendFlag != 0)
+    {
+      // Data is not updated or the previous data is not applied yet.
+      return;
+    }
+
+    mAnimationDataIndex   = mAnimationDataIndex == 0 ? 1 : 0; // Swap index
+    mAnimationDataUpdated = false;
+
+    index = mAnimationDataIndex;
+  }
+
+  if(mAnimationData[index].resendFlag & RiveAnimationTask::RESEND_PLAY_STATE)
+  {
+    if(mAnimationData[index].playState == Extension::RiveAnimationView::PlayState::PLAYING)
+    {
+      PlayAnimation();
+    }
+    else if(mAnimationData[index].playState == Extension::RiveAnimationView::PlayState::PAUSED)
+    {
+      PauseAnimation();
+    }
+    else if(mAnimationData[index].playState == Extension::RiveAnimationView::PlayState::STOPPED)
+    {
+      StopAnimation();
+    }
+  }
+
+  mAnimationData[index].resendFlag = 0;
+}
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-task.h b/dali-extension/internal/rive-animation-view/rive-animation-task.h
new file mode 100644 (file)
index 0000000..929570d
--- /dev/null
@@ -0,0 +1,239 @@
+#ifndef DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_TASK_H
+#define DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_TASK_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/event-thread-callback.h>
+#include <dali/devel-api/adaptor-framework/vector-animation-renderer.h>
+#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/public-api/object/property-array.h>
+#include <chrono>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-extension/devel-api/rive-animation-view/rive-animation-view.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+class RiveAnimationThread;
+class RiveAnimationTask;
+using RiveAnimationTaskPtr = IntrusivePtr<RiveAnimationTask>;
+
+/**
+ * The task of the rive animation.
+ */
+class RiveAnimationTask : public RefObject
+{
+public:
+  using UploadCompletedSignalType = Dali::VectorAnimationRenderer::UploadCompletedSignalType;
+
+  using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+
+  /**
+   * Flags for re-sending data to the rive animation thread
+   */
+  enum ResendFlags
+  {
+    RESEND_PLAY_RANGE    = 1 << 0,
+    RESEND_LOOP_COUNT    = 1 << 1,
+    RESEND_STOP_BEHAVIOR = 1 << 2,
+    RESEND_LOOPING_MODE  = 1 << 3,
+    RESEND_CURRENT_FRAME = 1 << 4,
+    RESEND_SIZE          = 1 << 5,
+    RESEND_PLAY_STATE    = 1 << 6
+  };
+
+  /**
+   * @brief Structure used to pass parameters to the rive animation task
+   */
+  struct AnimationData
+  {
+    AnimationData()
+    : resendFlag(0),
+      playState(),
+      width(0),
+      height(0)
+    {
+    }
+
+    AnimationData& operator=(const AnimationData& rhs)
+    {
+      resendFlag |= rhs.resendFlag; // OR resend flag
+      playState    = rhs.playState;
+      width        = rhs.width;
+      height       = rhs.height;
+      return *this;
+    }
+
+    uint32_t                                   resendFlag;
+    Extension::RiveAnimationView::PlayState    playState;
+    uint32_t                                   width;
+    uint32_t                                   height;
+  };
+
+  /**
+   * @brief Constructor.
+   */
+  RiveAnimationTask();
+
+  /**
+   * @brief Destructor.
+   */
+  ~RiveAnimationTask() override;
+
+  /**
+   * @brief Finalizes the task.
+   */
+  void Finalize();
+
+  /**
+   * @brief Loads the animation file.
+   *
+   * @param[in] url The url of the rive animation file
+   * @return True if loading success, false otherwise.
+   */
+  bool Load(const std::string& url);
+
+  /**
+   * @brief Sets the renderer used to display the result image.
+   *
+   * @param[in] renderer The renderer used to display the result image
+   */
+  void SetRenderer(Renderer renderer);
+
+  /**
+   * @brief Sets data to specify animation playback.
+   * @param[in] data The animation data
+   */
+  void SetAnimationData(const AnimationData& data);
+
+  /**
+   * @brief This callback is called after the animation is finished.
+   * @param[in] callback The animation finished callback
+   */
+  void SetAnimationFinishedCallback(EventThreadCallback* callback);
+
+  /**
+   * @brief Gets the default size of the file,.
+   * @return The default size of the file
+   */
+  void GetDefaultSize(uint32_t& width, uint32_t& height) const;
+
+  /**
+   * @brief Connect to this signal to be notified when the texture upload is completed.
+   * @return The signal to connect to.
+   */
+  UploadCompletedSignalType& UploadCompletedSignal();
+
+  /**
+   * @brief Rasterizes the current frame.
+   * @return true if the animation is running, false otherwise.
+   */
+  bool Rasterize();
+
+  /**
+   * @brief Calculates the time for the next frame rasterization.
+   * @return The time for the next frame rasterization.
+   */
+  TimePoint CalculateNextFrameTime(bool renderNow);
+
+  /**
+   * @brief Gets the time for the next frame rasterization.
+   * @return The time for the next frame rasterization.
+   */
+  TimePoint GetNextFrameTime();
+
+private:
+  /**
+   * @brief Play the rive animation.
+   */
+  void PlayAnimation();
+
+  /**
+   * @brief Stop the rive animation.
+   */
+  void StopAnimation();
+
+  /**
+   * @brief Pause the rive animation.
+   */
+  void PauseAnimation();
+
+  /**
+   * @brief Sets the target image size.
+   *
+   * @param[in] width The target image width
+   * @param[in] height The target image height
+   */
+  void SetSize(uint32_t width, uint32_t height);
+
+  /**
+   * @brief Applies the animation data set by the main thread.
+   */
+  void ApplyAnimationData();
+
+  // Undefined
+  RiveAnimationTask(const RiveAnimationTask& task) = delete;
+
+  // Undefined
+  RiveAnimationTask& operator=(const RiveAnimationTask& task) = delete;
+
+private:
+  enum class PlayState
+  {
+    STOPPING, ///< The animation is stopping
+    STOPPED,  ///< The animation has stopped
+    PLAYING,  ///< The animation is playing
+    PAUSED    ///< The animation is paused
+  };
+
+  std::string                          mUrl;
+  VectorAnimationRenderer              mVectorRenderer;
+  AnimationData                        mAnimationData[2];
+  RiveAnimationThread&                 mRiveAnimationThread;
+  ConditionalWait                      mConditionalWait;
+  std::unique_ptr<EventThreadCallback> mAnimationFinishedTrigger;
+  PlayState                            mPlayState;
+  TimePoint                            mNextFrameStartTime;
+  int64_t                              mFrameDurationMicroSeconds;
+  float                                mFrameRate;
+  uint32_t                             mCurrentFrame;
+  uint32_t                             mTotalFrame;
+  uint32_t                             mStartFrame;
+  uint32_t                             mEndFrame;
+  uint32_t                             mDroppedFrames;
+  uint32_t                             mWidth;
+  uint32_t                             mHeight;
+  uint32_t                             mAnimationDataIndex;
+  bool                                 mUpdateFrameNumber;
+  bool                                 mNeedAnimationFinishedTrigger;
+  bool                                 mAnimationDataUpdated;
+  bool                                 mDestroyTask;
+};
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_TASK_H
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-thread.cpp b/dali-extension/internal/rive-animation-view/rive-animation-thread.cpp
new file mode 100644 (file)
index 0000000..debf36c
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-extension/internal/rive-animation-view/rive-animation-thread.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/environment-variable.h>
+#include <dali/devel-api/adaptor-framework/thread-settings.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/integration-api/debug.h>
+#include <thread>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+namespace
+{
+constexpr auto DEFAULT_NUMBER_OF_RASTERIZE_THREADS = size_t{4u};
+constexpr auto NUMBER_OF_RASTERIZE_THREADS_ENV     = "DALI_RIVE_RASTERIZE_THREADS";
+
+size_t GetNumberOfThreads(const char* environmentVariable, size_t defaultValue)
+{
+  using Dali::EnvironmentVariable::GetEnvironmentVariable;
+  auto           numberString          = GetEnvironmentVariable(environmentVariable);
+  auto           numberOfThreads       = numberString ? std::strtoul(numberString, nullptr, 10) : 0;
+  constexpr auto MAX_NUMBER_OF_THREADS = 100u;
+  DALI_ASSERT_DEBUG(numberOfThreads < MAX_NUMBER_OF_THREADS);
+  return (numberOfThreads > 0 && numberOfThreads < MAX_NUMBER_OF_THREADS) ? numberOfThreads : defaultValue;
+}
+
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gRiveAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RIVE_ANIMATION");
+#endif
+
+} // unnamed namespace
+
+RiveAnimationThread::RiveAnimationThread()
+: mAnimationTasks(),
+  mCompletedTasks(),
+  mWorkingTasks(),
+  mRasterizers(GetNumberOfThreads(NUMBER_OF_RASTERIZE_THREADS_ENV, DEFAULT_NUMBER_OF_RASTERIZE_THREADS), [&]() { return RasterizeHelper(*this); }),
+  mSleepThread(MakeCallback(this, &RiveAnimationThread::OnAwakeFromSleep)),
+  mConditionalWait(),
+  mNeedToSleep(false),
+  mDestroyThread(false),
+  mLogFactory(Dali::Adaptor::Get().GetLogFactory())
+{
+  mSleepThread.Start();
+}
+
+RiveAnimationThread::~RiveAnimationThread()
+{
+  // Stop the thread
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+    mDestroyThread = true;
+    mNeedToSleep   = false;
+    mConditionalWait.Notify(lock);
+  }
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationThread::~RiveAnimationThread: Join [%p]\n", this);
+
+  Join();
+}
+
+void RiveAnimationThread::AddTask(RiveAnimationTaskPtr task)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  if(mAnimationTasks.end() == std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
+  {
+    auto currentTime = task->CalculateNextFrameTime(true); // Rasterize as soon as possible
+
+    bool inserted = false;
+    for(auto iter = mAnimationTasks.begin(); iter != mAnimationTasks.end(); ++iter)
+    {
+      auto nextTime = (*iter)->GetNextFrameTime();
+      if(nextTime > currentTime)
+      {
+        mAnimationTasks.insert(iter, task);
+        inserted = true;
+        break;
+      }
+    }
+
+    if(!inserted)
+    {
+      mAnimationTasks.push_back(task);
+    }
+
+    mNeedToSleep = false;
+    // wake up the animation thread
+    mConditionalWait.Notify(lock);
+  }
+}
+
+void RiveAnimationThread::OnTaskCompleted(RiveAnimationTaskPtr task, bool keepAnimation)
+{
+  if(!mDestroyThread)
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+    bool                        needRasterize = false;
+
+    auto workingTask = std::find(mWorkingTasks.begin(), mWorkingTasks.end(), task);
+    if(workingTask != mWorkingTasks.end())
+    {
+      mWorkingTasks.erase(workingTask);
+    }
+
+    // Check pending task
+    if(mAnimationTasks.end() != std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
+    {
+      needRasterize = true;
+    }
+
+    if(keepAnimation)
+    {
+      if(mCompletedTasks.end() == std::find(mCompletedTasks.begin(), mCompletedTasks.end(), task))
+      {
+        mCompletedTasks.push_back(task);
+        needRasterize = true;
+      }
+    }
+
+    if(needRasterize)
+    {
+      mNeedToSleep = false;
+      // wake up the animation thread
+      mConditionalWait.Notify(lock);
+    }
+  }
+}
+
+void RiveAnimationThread::OnAwakeFromSleep()
+{
+  if(!mDestroyThread)
+  {
+    mNeedToSleep = false;
+    // wake up the animation thread
+    mConditionalWait.Notify();
+  }
+}
+
+void RiveAnimationThread::Run()
+{
+  SetThreadName("RiveAnimationThread");
+  mLogFactory.InstallLogFunction();
+
+  while(!mDestroyThread)
+  {
+    Rasterize();
+  }
+}
+
+void RiveAnimationThread::Rasterize()
+{
+  // Lock while popping task out from the queue
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  // conditional wait
+  if(mNeedToSleep)
+  {
+    mConditionalWait.Wait(lock);
+  }
+
+  mNeedToSleep = true;
+
+  // Process completed tasks
+  for(auto&& task : mCompletedTasks)
+  {
+    if(mAnimationTasks.end() == std::find(mAnimationTasks.begin(), mAnimationTasks.end(), task))
+    {
+      // Should use the frame rate of the animation file
+      auto nextFrameTime = task->CalculateNextFrameTime(false);
+
+      bool inserted = false;
+      for(auto iter = mAnimationTasks.begin(); iter != mAnimationTasks.end(); ++iter)
+      {
+        auto time = (*iter)->GetNextFrameTime();
+        if(time > nextFrameTime)
+        {
+          mAnimationTasks.insert(iter, task);
+          inserted = true;
+          break;
+        }
+      }
+
+      if(!inserted)
+      {
+        mAnimationTasks.push_back(task);
+      }
+    }
+  }
+  mCompletedTasks.clear();
+
+  // pop out the next task from the queue
+  for(auto it = mAnimationTasks.begin(); it != mAnimationTasks.end();)
+  {
+    RiveAnimationTaskPtr nextTask = *it;
+
+    auto currentTime   = std::chrono::system_clock::now();
+    auto nextFrameTime = nextTask->GetNextFrameTime();
+
+#if defined(DEBUG_ENABLED)
+    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(nextFrameTime - currentTime);
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationThread::Rasterize: [next time = %lld]\n", duration.count());
+#endif
+
+    if(nextFrameTime <= currentTime)
+    {
+      // If the task is not in the working list
+      if(std::find(mWorkingTasks.begin(), mWorkingTasks.end(), nextTask) == mWorkingTasks.end())
+      {
+        it = mAnimationTasks.erase(it);
+
+        // Add it to the working list
+        mWorkingTasks.push_back(nextTask);
+
+        auto rasterizerHelperIt = mRasterizers.GetNext();
+        DALI_ASSERT_ALWAYS(rasterizerHelperIt != mRasterizers.End());
+
+        rasterizerHelperIt->Rasterize(nextTask);
+      }
+      else
+      {
+        it++;
+      }
+    }
+    else
+    {
+      mSleepThread.SleepUntil(nextFrameTime);
+      break;
+    }
+  }
+}
+
+RiveAnimationThread::RasterizeHelper::RasterizeHelper(RiveAnimationThread& animationThread)
+: RasterizeHelper(std::unique_ptr<RiveRasterizeThread>(new RiveRasterizeThread()), animationThread)
+{
+}
+
+RiveAnimationThread::RasterizeHelper::RasterizeHelper(RasterizeHelper&& rhs)
+: RasterizeHelper(std::move(rhs.mRasterizer), rhs.mAnimationThread)
+{
+}
+
+RiveAnimationThread::RasterizeHelper::RasterizeHelper(std::unique_ptr<RiveRasterizeThread> rasterizer, RiveAnimationThread& animationThread)
+: mRasterizer(std::move(rasterizer)),
+  mAnimationThread(animationThread)
+{
+  mRasterizer->SetCompletedCallback(MakeCallback(&mAnimationThread, &RiveAnimationThread::OnTaskCompleted));
+}
+
+void RiveAnimationThread::RasterizeHelper::Rasterize(RiveAnimationTaskPtr task)
+{
+  if(task)
+  {
+    mRasterizer->AddTask(task);
+  }
+}
+
+RiveAnimationThread::SleepThread::SleepThread(CallbackBase* callback)
+: mConditionalWait(),
+  mAwakeCallback(std::unique_ptr<CallbackBase>(callback)),
+  mSleepTimePoint(),
+  mLogFactory(Dali::Adaptor::Get().GetLogFactory()),
+  mNeedToSleep(false),
+  mDestroyThread(false)
+{
+}
+
+RiveAnimationThread::SleepThread::~SleepThread()
+{
+  // Stop the thread
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+    mDestroyThread = true;
+    mConditionalWait.Notify(lock);
+  }
+
+  Join();
+}
+
+void RiveAnimationThread::SleepThread::SleepUntil(std::chrono::time_point<std::chrono::system_clock> timeToSleepUntil)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+  mSleepTimePoint = timeToSleepUntil;
+  mNeedToSleep    = true;
+  mConditionalWait.Notify(lock);
+}
+
+void RiveAnimationThread::SleepThread::Run()
+{
+  SetThreadName("RiveSleepThread");
+  mLogFactory.InstallLogFunction();
+
+  while(!mDestroyThread)
+  {
+    bool                                               needToSleep;
+    std::chrono::time_point<std::chrono::system_clock> sleepTimePoint;
+
+    {
+      ConditionalWait::ScopedLock lock(mConditionalWait);
+
+      needToSleep    = mNeedToSleep;
+      sleepTimePoint = mSleepTimePoint;
+
+      mNeedToSleep = false;
+    }
+
+    if(needToSleep)
+    {
+#if defined(DEBUG_ENABLED)
+      auto sleepDuration = std::chrono::duration_cast<std::chrono::milliseconds>(mSleepTimePoint - std::chrono::system_clock::now());
+
+      DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationThread::SleepThread::Run: [sleep duration = %lld]\n", sleepDuration.count());
+#endif
+
+      std::this_thread::sleep_until(sleepTimePoint);
+
+      if(mAwakeCallback)
+      {
+        CallbackBase::Execute(*mAwakeCallback);
+      }
+    }
+
+    {
+      ConditionalWait::ScopedLock lock(mConditionalWait);
+      if(!mDestroyThread && !mNeedToSleep)
+      {
+        mConditionalWait.Wait(lock);
+      }
+    }
+  }
+}
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-thread.h b/dali-extension/internal/rive-animation-view/rive-animation-thread.h
new file mode 100644 (file)
index 0000000..3830d4a
--- /dev/null
@@ -0,0 +1,188 @@
+#ifndef DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_THREAD_H
+#define DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_THREAD_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/devel-api/threading/thread.h>
+#include <dali/integration-api/adaptor-framework/log-factory-interface.h>
+#include <dali/public-api/signals/connection-tracker.h>
+#include <memory>
+
+// INTERNAL INCLUDES
+#include <dali-extension/internal/rive-animation-view/round-robin-container-view.h>
+#include <dali-extension/internal/rive-animation-view/rive-animation-task.h>
+#include <dali-extension/internal/rive-animation-view/rive-rasterize-thread.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+/**
+ * The main animation thread for rive animations
+ */
+class RiveAnimationThread : public Thread
+{
+public:
+  /**
+   * @brief Constructor.
+   */
+  RiveAnimationThread();
+
+  /**
+   * @brief Destructor.
+   */
+  ~RiveAnimationThread() override;
+
+  /**
+   * Add a animation task into the rive animation thread, called by main thread.
+   *
+   * @param[in] task The task added to the thread.
+   */
+  void AddTask(RiveAnimationTaskPtr task);
+
+  /**
+   * @brief Called when the rasterization is completed from the rasterize thread.
+   * @param task The completed task
+   */
+  void OnTaskCompleted(RiveAnimationTaskPtr task, bool stopped);
+
+  /**
+   * @brief Called when the sleep thread is awaken.
+   */
+  void OnAwakeFromSleep();
+
+protected:
+  /**
+   * @brief The entry function of the animation thread.
+   */
+  void Run() override;
+
+private:
+  /**
+   * Rasterizes the tasks.
+   */
+  void Rasterize();
+
+private:
+  /**
+   * @brief Helper class to keep the relation between RiveRasterizeThread and corresponding container
+   */
+  class RasterizeHelper : public ConnectionTracker
+  {
+  public:
+    /**
+     * @brief Create an RasterizeHelper.
+     *
+     * @param[in] animationThread Reference to the RiveAnimationThread
+     */
+    RasterizeHelper(RiveAnimationThread& animationThread);
+
+    /**
+     * @brief Rasterizes the task.
+     *
+     * @param[in] task The task to rasterize.
+     */
+    void Rasterize(RiveAnimationTaskPtr task);
+
+  public:
+    RasterizeHelper(const RasterizeHelper&) = delete;
+    RasterizeHelper& operator=(const RasterizeHelper&) = delete;
+
+    RasterizeHelper(RasterizeHelper&& rhs);
+    RasterizeHelper& operator=(RasterizeHelper&& rhs) = delete;
+
+  private:
+    /**
+     * @brief Main constructor that used by all other constructors
+     */
+    RasterizeHelper(std::unique_ptr<RiveRasterizeThread> rasterizer, RiveAnimationThread& animationThread);
+
+  private:
+    std::unique_ptr<RiveRasterizeThread> mRasterizer;
+    RiveAnimationThread&                 mAnimationThread;
+  };
+
+  /**
+   * @brief The thread to sleep until the next frame time.
+   */
+  class SleepThread : public Thread
+  {
+  public:
+    /**
+     * @brief Constructor.
+     */
+    SleepThread(CallbackBase* callback);
+
+    /**
+     * @brief Destructor.
+     */
+    ~SleepThread() override;
+
+    /**
+     * @brief Sleeps untile the specified time point.
+     */
+    void SleepUntil(std::chrono::time_point<std::chrono::system_clock> timeToSleepUntil);
+
+  protected:
+    /**
+     * @brief The entry function of the animation thread.
+     */
+    void Run() override;
+
+  private:
+    SleepThread(const SleepThread& thread) = delete;
+    SleepThread& operator=(const SleepThread& thread) = delete;
+
+  private:
+    ConditionalWait                                    mConditionalWait;
+    std::unique_ptr<CallbackBase>                      mAwakeCallback;
+    std::chrono::time_point<std::chrono::system_clock> mSleepTimePoint;
+    const Dali::LogFactoryInterface&                   mLogFactory;
+    bool                                               mNeedToSleep;
+    bool                                               mDestroyThread;
+  };
+
+private:
+  // Undefined
+  RiveAnimationThread(const RiveAnimationThread& thread) = delete;
+
+  // Undefined
+  RiveAnimationThread& operator=(const RiveAnimationThread& thread) = delete;
+
+private:
+  std::vector<RiveAnimationTaskPtr>      mAnimationTasks;
+  std::vector<RiveAnimationTaskPtr>      mCompletedTasks;
+  std::vector<RiveAnimationTaskPtr>      mWorkingTasks;
+  RoundRobinContainerView<RasterizeHelper> mRasterizers;
+  SleepThread                              mSleepThread;
+  ConditionalWait                          mConditionalWait;
+  bool                                     mNeedToSleep;
+  bool                                     mDestroyThread;
+  const Dali::LogFactoryInterface&         mLogFactory;
+};
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_THREAD_H
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-view-impl.cpp b/dali-extension/internal/rive-animation-view/rive-animation-view-impl.cpp
new file mode 100644 (file)
index 0000000..86db74d
--- /dev/null
@@ -0,0 +1,536 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include "rive-animation-view-impl.h"
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/window-devel.h>
+#include <dali/devel-api/common/stage.h>
+#include <dali/devel-api/rendering/renderer-devel.h>
+#include <dali/integration-api/debug.h>
+#include <dali/public-api/object/type-registry-helper.h>
+#include <dali/public-api/object/type-registry.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
+
+// INTERNAL INCLUDES
+#include <dali-extension/devel-api/rive-animation-view/rive-animation-view.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+namespace
+{
+
+Geometry CreateQuadGeometry()
+{
+  const float halfWidth  = 0.5f;
+  const float halfHeight = 0.5f;
+  struct QuadVertex
+  {
+    Vector2 position;
+  };
+  QuadVertex quadVertexData[4] =
+    {
+      {Vector2(-halfWidth, -halfHeight)},
+      {Vector2(-halfWidth, halfHeight)},
+      {Vector2(halfWidth, -halfHeight)},
+      {Vector2(halfWidth, halfHeight)}};
+
+  Property::Map quadVertexFormat;
+  quadVertexFormat["aPosition"] = Property::VECTOR2;
+  VertexBuffer quadVertices     = VertexBuffer::New(quadVertexFormat);
+  quadVertices.SetData(quadVertexData, 4);
+
+  // Create the geometry object
+  Geometry geometry = Geometry::New();
+  geometry.AddVertexBuffer(quadVertices);
+  geometry.SetType(Geometry::TRIANGLE_STRIP);
+
+  return geometry;
+}
+
+BaseHandle Create()
+{
+  return Extension::RiveAnimationView::New();
+}
+
+// Setup properties, signals and actions using the type-registry.
+DALI_TYPE_REGISTRATION_BEGIN(Extension::RiveAnimationView, Toolkit::Control, Create);
+DALI_PROPERTY_REGISTRATION(Extension, RiveAnimationView, "url", STRING, URL)
+DALI_PROPERTY_REGISTRATION(Extension, RiveAnimationView, "playState", INTEGER, PLAY_STATE)
+DALI_TYPE_REGISTRATION_END()
+
+const char* VERTEX_SHADER = DALI_COMPOSE_SHADER(
+  attribute mediump vec2 aPosition;\n
+  uniform highp   mat4 uMvpMatrix;\n
+  uniform highp   vec3 uSize;\n
+  varying mediump vec2 vTexCoord;\n
+  \n
+  void main()\n
+  {\n
+    gl_Position = uMvpMatrix * vec4(aPosition * uSize.xy, 0.0, 1.0);\n
+    vTexCoord = aPosition + vec2(0.5);\n
+  }\n
+);
+
+const char* FRAGMENT_SHADER = DALI_COMPOSE_SHADER(
+  varying mediump vec2 vTexCoord;\n
+  uniform sampler2D sTexture;\n
+  uniform lowp vec4 uColor;\n
+  \n
+  void main()\n
+  {\n
+      gl_FragColor = texture2D( sTexture, vTexCoord ) * uColor;\n
+  }\n
+);
+
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gRiveAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RIVE_ANIMATION");
+#endif
+
+} // unnamed namespace
+
+RiveAnimationView::RiveAnimationView()
+: Control(ControlBehaviour(CONTROL_BEHAVIOUR_DEFAULT)),
+  mRiveAnimationTask(new RiveAnimationTask())
+{
+}
+
+RiveAnimationView::~RiveAnimationView()
+{
+  if(!mCoreShutdown)
+  {
+    auto& riveAnimationManager = RiveAnimationManager::GetInstance();
+    riveAnimationManager.RemoveObserver(*this);
+
+    if(mEventCallback)
+    {
+      riveAnimationManager.UnregisterEventCallback(mEventCallback);
+    }
+
+    // Finalize animation task and disconnect the signal in the main thread
+    mRiveAnimationTask->UploadCompletedSignal().Disconnect(this, &RiveAnimationView::OnUploadCompleted);
+    mRiveAnimationTask->Finalize();
+  }
+}
+
+Extension::RiveAnimationView RiveAnimationView::New()
+{
+  RiveAnimationView* impl = new RiveAnimationView();
+
+  Dali::Extension::RiveAnimationView handle = Dali::Extension::RiveAnimationView(*impl);
+
+  // Second-phase init of the implementation
+  // This can only be done after the CustomActor connection has been made...
+  impl->Initialize();
+
+  return handle;
+}
+
+void RiveAnimationView::OnSceneConnection(int depth)
+{
+  Control::OnSceneConnection(depth);
+
+  if(mLoadFailed)
+  {
+    //TODO: do something
+  }
+  else
+  {
+    Actor actor = Self();
+
+    // Add property notification for scaling & size
+    mScaleNotification = actor.AddPropertyNotification(Actor::Property::WORLD_SCALE, StepCondition(0.1f, 1.0f));
+    mScaleNotification.NotifySignal().Connect(this, &RiveAnimationView::OnScaleNotification);
+
+    mSizeNotification = actor.AddPropertyNotification(Actor::Property::SIZE, StepCondition(3.0f));
+    mSizeNotification.NotifySignal().Connect(this, &RiveAnimationView::OnSizeNotification);
+
+    DevelActor::VisibilityChangedSignal(actor).Connect(this, &RiveAnimationView::OnControlVisibilityChanged);
+
+    Window window = DevelWindow::Get(actor);
+    if(window)
+    {
+      DevelWindow::VisibilityChangedSignal(window).Connect(this, &RiveAnimationView::OnWindowVisibilityChanged);
+    }
+  }
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnSceneConnection [%p]\n", this);
+
+}
+
+void RiveAnimationView::OnSceneDisconnection()
+{
+  Control::OnSceneDisconnection();
+
+  StopAnimation();
+  SendAnimationData();
+
+  Actor actor = Self();
+
+  if(mRenderer)
+  {
+    actor.RemoveRenderer(mRenderer);
+    mRendererAdded = false;
+  }
+
+  // Remove property notification
+  actor.RemovePropertyNotification(mScaleNotification);
+  actor.RemovePropertyNotification(mSizeNotification);
+
+  DevelActor::VisibilityChangedSignal(actor).Disconnect(this, &RiveAnimationView::OnControlVisibilityChanged);
+
+  Window window = DevelWindow::Get(actor);
+  if(window)
+  {
+    DevelWindow::VisibilityChangedSignal(window).Disconnect(this, &RiveAnimationView::OnWindowVisibilityChanged);
+  }
+
+  // Reset the visual size to zero so that when adding the actor back to stage the rasterization is forced
+  mSize  = Vector2::ZERO;
+  mScale = Vector2::ONE;
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnSceneDisconnection [%p]\n", this);
+}
+
+void RiveAnimationView::OnInitialize()
+{
+  Toolkit::DevelControl::SetAccessibilityConstructor(Self(), [](Dali::Actor actor) {
+    return std::unique_ptr<Dali::Accessibility::Accessible>(
+      new Toolkit::DevelControl::AccessibleImpl(actor, Dali::Accessibility::Role::IMAGE));
+  });
+
+  // Enable highightability
+  Self().SetProperty(Toolkit::DevelControl::Property::ACCESSIBILITY_HIGHLIGHTABLE, true);
+}
+
+Vector3 RiveAnimationView::GetNaturalSize()
+{
+  Vector3 naturalSize;
+
+  if(mSize != Vector2::ZERO)
+  {
+    naturalSize = mSize;
+  }
+  else
+  {
+    uint32_t width, height;
+    mRiveAnimationTask->GetDefaultSize(width, height);
+    naturalSize.x = width;
+    naturalSize.y = height;
+  }
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::GetNaturalSize: w = %f, h = %f [%p]\n", naturalSize.width, naturalSize.height, this);
+
+  return naturalSize;
+}
+
+void RiveAnimationView::OnRelayout(const Vector2& size, RelayoutContainer& container)
+{
+  Control::OnRelayout(size, container);
+
+  if(Self()[Actor::Property::CONNECTED_TO_SCENE] && size != mSize)
+  {
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnRelayout: width = %f, height = %f [%p]\n", size.width, size.height, this);
+
+    mSize = size;
+
+    SetVectorImageSize();
+
+    if(mPlayState == Dali::Extension::RiveAnimationView::PlayState::PLAYING && mAnimationData.playState != Dali::Extension::RiveAnimationView::PlayState::PLAYING)
+    {
+      mAnimationData.playState = Dali::Extension::RiveAnimationView::PlayState::PLAYING;
+      mAnimationData.resendFlag |= RiveAnimationTask::RESEND_PLAY_STATE;
+    }
+
+    SendAnimationData();
+  }
+}
+
+///////////////////////////////////////////////////////////
+//
+// Properties
+//
+
+void RiveAnimationView::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
+{
+  Dali::Extension::RiveAnimationView riveAnimationView = Dali::Extension::RiveAnimationView::DownCast(Dali::BaseHandle(object));
+
+  if(riveAnimationView)
+  {
+    RiveAnimationView& impl = GetImplementation(riveAnimationView);
+    switch(index)
+    {
+      case Dali::Extension::RiveAnimationView::Property::URL:
+      {
+        std::string url;
+        if(value.Get(url))
+        {
+          impl.SetUrl(url);
+        }
+        break;
+      }
+    }
+  }
+}
+
+Property::Value RiveAnimationView::GetProperty(BaseObject* object, Property::Index propertyIndex)
+{
+  Property::Value value;
+
+  Dali::Extension::RiveAnimationView riveAnimationView = Dali::Extension::RiveAnimationView::DownCast(Dali::BaseHandle(object));
+
+  if(riveAnimationView)
+  {
+    RiveAnimationView& impl = GetImplementation(riveAnimationView);
+    switch(propertyIndex)
+    {
+      case Dali::Extension::RiveAnimationView::Property::URL:
+      {
+        value = impl.mUrl;
+        break;
+      }
+    }
+  }
+
+  return value;
+}
+
+void RiveAnimationView::PlayAnimation()
+{
+  Vector3 size = Self().GetProperty<Vector3>(Dali::Actor::Property::SIZE);
+
+  if(Self()[Actor::Property::CONNECTED_TO_SCENE] && size != Vector3::ZERO)
+  {
+    if(mAnimationData.playState != Dali::Extension::RiveAnimationView::PlayState::PLAYING)
+    {
+      mAnimationData.playState = Dali::Extension::RiveAnimationView::PlayState::PLAYING;
+      mAnimationData.resendFlag |= RiveAnimationTask::RESEND_PLAY_STATE;
+    }
+  }
+  mPlayState = Dali::Extension::RiveAnimationView::PlayState::PLAYING;
+
+  TriggerVectorRasterization();
+}
+
+void RiveAnimationView::StopAnimation()
+{
+  if(mAnimationData.playState != Dali::Extension::RiveAnimationView::PlayState::STOPPED)
+  {
+    mAnimationData.playState = Dali::Extension::RiveAnimationView::PlayState::STOPPED;
+    mAnimationData.resendFlag |= RiveAnimationTask::RESEND_PLAY_STATE;
+  }
+  mPlayState = Dali::Extension::RiveAnimationView::PlayState::STOPPED;
+
+  TriggerVectorRasterization();
+}
+
+void RiveAnimationView::PauseAnimation()
+{
+  if(mAnimationData.playState == Dali::Extension::RiveAnimationView::PlayState::PLAYING)
+  {
+    mAnimationData.playState = Dali::Extension::RiveAnimationView::PlayState::PAUSED;
+    mAnimationData.resendFlag |= RiveAnimationTask::RESEND_PLAY_STATE;
+  }
+  mPlayState = Dali::Extension::RiveAnimationView::PlayState::PAUSED;
+
+  TriggerVectorRasterization();
+}
+
+Dali::Extension::RiveAnimationView::AnimationSignalType& RiveAnimationView::AnimationFinishedSignal()
+{
+  return mFinishedSignal;
+}
+
+void RiveAnimationView::RiveAnimationManagerDestroyed()
+{
+  mCoreShutdown = true;
+}
+
+void RiveAnimationView::SetUrl(const std::string& url)
+{
+  mUrl = url;
+
+  if(!mRiveAnimationTask->Load(mUrl))
+  {
+    mLoadFailed = true;
+  }
+
+  mRiveAnimationTask->UploadCompletedSignal().Connect(this, &RiveAnimationView::OnUploadCompleted);
+  mRiveAnimationTask->SetAnimationFinishedCallback(new EventThreadCallback(MakeCallback(this, &RiveAnimationView::OnAnimationFinished)));
+
+  auto& vectorAnimationManager = RiveAnimationManager::GetInstance();
+  vectorAnimationManager.AddObserver(*this);
+
+  Geometry geometry = CreateQuadGeometry();
+  Shader   shader   = Shader::New(VERTEX_SHADER, FRAGMENT_SHADER);
+  mRenderer  = Renderer::New(geometry, shader);
+
+  TextureSet textureSet = TextureSet::New();
+  mRenderer.SetTextures(textureSet);
+
+  mRenderer.SetProperty(Renderer::Property::BLEND_PRE_MULTIPLIED_ALPHA, true);
+
+  mRiveAnimationTask->SetRenderer(mRenderer);
+
+  TriggerVectorRasterization();
+}
+
+void RiveAnimationView::OnUploadCompleted()
+{
+  if(!mRendererAdded)
+  {
+    Self().AddRenderer(mRenderer);
+    mRendererAdded = true;
+
+    //TODO: do something - resource ready
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnUploadCompleted: Renderer is added [%p]\n", this);
+  }
+}
+
+void RiveAnimationView::OnAnimationFinished()
+{
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnAnimationFinished: action state = %d [%p]\n", mPlayState, this);
+
+  if(mPlayState != Dali::Extension::RiveAnimationView::PlayState::STOPPED)
+  {
+    mPlayState = Dali::Extension::RiveAnimationView::PlayState::STOPPED;
+
+    mAnimationData.playState = Dali::Extension::RiveAnimationView::PlayState::STOPPED;
+
+    Dali::Extension::RiveAnimationView handle(GetOwner());
+    mFinishedSignal.Emit(handle);
+  }
+
+  if(mRenderer)
+  {
+    mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::IF_REQUIRED);
+  }
+}
+
+void RiveAnimationView::SendAnimationData()
+{
+  if(mAnimationData.resendFlag)
+  {
+    mRiveAnimationTask->SetAnimationData(mAnimationData);
+
+    if(mRenderer)
+    {
+      if(mAnimationData.playState == Dali::Extension::RiveAnimationView::PlayState::PLAYING)
+      {
+        mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::CONTINUOUSLY);
+      }
+      else
+      {
+        mRenderer.SetProperty(DevelRenderer::Property::RENDERING_BEHAVIOR, DevelRenderer::Rendering::IF_REQUIRED);
+      }
+    }
+
+    mAnimationData.resendFlag = 0;
+  }
+}
+
+void RiveAnimationView::SetVectorImageSize()
+{
+  uint32_t width  = static_cast<uint32_t>(mSize.width * mScale.width);
+  uint32_t height = static_cast<uint32_t>(mSize.height * mScale.height);
+
+  mAnimationData.width  = width;
+  mAnimationData.height = height;
+  mAnimationData.resendFlag |= RiveAnimationTask::RESEND_SIZE;
+}
+
+void RiveAnimationView::TriggerVectorRasterization()
+{
+  if(!mEventCallback && !mCoreShutdown)
+  {
+    mEventCallback             = MakeCallback(this, &RiveAnimationView::OnProcessEvents);
+    auto& riveAnimationManager = RiveAnimationManager::GetInstance();
+    riveAnimationManager.RegisterEventCallback(mEventCallback);
+    Stage::GetCurrent().KeepRendering(0.0f); // Trigger event processing
+  }
+}
+
+void RiveAnimationView::OnScaleNotification(PropertyNotification& source)
+{
+  Vector3 scale = Self().GetProperty<Vector3>(Actor::Property::WORLD_SCALE);
+
+  if(scale.width >= 1.0f || scale.height >= 1.0f)
+  {
+    mScale.width  = scale.width;
+    mScale.height = scale.height;
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnScaleNotification: scale = %f, %f [%p]\n", mScale.width, mScale.height, this);
+
+    SetVectorImageSize();
+    SendAnimationData();
+
+    Stage::GetCurrent().KeepRendering(0.0f); // Trigger event processing
+  }
+}
+
+void RiveAnimationView::OnSizeNotification(PropertyNotification& source)
+{
+  Vector3 size       = Self().GetCurrentProperty<Vector3>(Actor::Property::SIZE);
+  mSize.width  = size.width;
+  mSize.height = size.height;
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnSizeNotification: size = %f, %f [%p]\n", mSize.width, mSize.height, this);
+
+  SetVectorImageSize();
+  SendAnimationData();
+
+  Stage::GetCurrent().KeepRendering(0.0f); // Trigger event processing
+}
+
+void RiveAnimationView::OnControlVisibilityChanged(Actor actor, bool visible, DevelActor::VisibilityChange::Type type)
+{
+  if(!visible)
+  {
+    StopAnimation();
+    TriggerVectorRasterization();
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnControlVisibilityChanged: invisibile. Pause animation [%p]\n", this);
+  }
+}
+
+void RiveAnimationView::OnWindowVisibilityChanged(Window window, bool visible)
+{
+  if(!visible)
+  {
+    StopAnimation();
+    TriggerVectorRasterization();
+
+    DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveAnimationView::OnWindowVisibilityChanged: invisibile. Pause animation [%p]\n", this);
+  }
+}
+
+void RiveAnimationView::OnProcessEvents()
+{
+  SendAnimationData();
+
+  mEventCallback = nullptr; // The callback will be deleted in the RiveAnimationManager
+}
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Dali
diff --git a/dali-extension/internal/rive-animation-view/rive-animation-view-impl.h b/dali-extension/internal/rive-animation-view/rive-animation-view-impl.h
new file mode 100644 (file)
index 0000000..2ee61a7
--- /dev/null
@@ -0,0 +1,232 @@
+#ifndef DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_VIEW_H
+#define DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_VIEW_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/actors/actor-devel.h>
+#include <dali/public-api/adaptor-framework/window.h>
+#include <dali/public-api/object/property-notification.h>
+#include <dali-toolkit/public-api/controls/control-impl.h>
+
+// INTERNAL INCLUDES
+#include <dali-extension/devel-api/rive-animation-view/rive-animation-view.h>
+#include <dali-extension/internal/rive-animation-view/rive-animation-task.h>
+#include <dali-extension/internal/rive-animation-view/rive-animation-manager.h>
+#include <dali-extension/internal/rive-animation-view/rive-animation-task.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+
+class RiveAnimationView : public Toolkit::Internal::Control, public RiveAnimationManager::LifecycleObserver
+{
+protected:
+  /**
+   * Construct a new RiveAnimationView.
+   */
+  RiveAnimationView();
+
+  /**
+   * A reference counted object may only be deleted by calling Unreference()
+   */
+  virtual ~RiveAnimationView();
+
+public:
+  /**
+   * @brief Create a new RiveAnimationView.
+   *
+   * @return A smart-pointer to the newly allocated RiveAnimationView.
+   */
+  static Dali::Extension::RiveAnimationView New();
+
+  /**
+   * @copydoc Dali::Extension::RiveAnimationView::PlayAnimation
+   */
+  void PlayAnimation();
+
+  /**
+   * @copydoc Dali::Extension::RiveAnimationView::StopAnimation
+   */
+  void StopAnimation();
+
+  /**
+   * @copydoc Dali::Extension::RiveAnimationView::PauseAnimation
+   */
+  void PauseAnimation();
+
+  /**
+   * @copydoc Dali::Extension::RiveAnimationView::AnimationFinishedSignal
+   */
+  Dali::Extension::RiveAnimationView::AnimationSignalType& AnimationFinishedSignal();
+
+  // Properties
+  /**
+   * Called when a property of an object of this type is set.
+   * @param[in] object The object whose property is set.
+   * @param[in] index The property index.
+   * @param[in] value The new property value.
+   */
+  static void SetProperty(BaseObject* object, Property::Index index, const Property::Value& value);
+
+  /**
+   * Called to retrieve a property of an object of this type.
+   * @param[in] object The object whose property is to be retrieved.
+   * @param[in] index The property index.
+   * @return The current value of the property.
+   */
+  static Property::Value GetProperty(BaseObject* object, Property::Index propertyIndex);
+
+private: // From CustomActorImpl
+  /**
+   * @copydoc CustomActorImpl::OnSceneConnection()
+   */
+  void OnSceneConnection(int depth) override;
+
+  /**
+   * @copydoc CustomActorImpl::OnSceneDisconnection()
+   */
+  void OnSceneDisconnection() override;
+
+private: // From Control
+  /**
+   * @copydoc Toolkit::Control::OnInitialize
+   */
+  void OnInitialize() override;
+
+  /**
+   * @copydoc Toolkit::Control::GetNaturalSize
+   */
+  Vector3 GetNaturalSize() override;
+
+  /**
+   * @copydoc Toolkit::Control::OnRelayout()
+   */
+  void OnRelayout(const Vector2& size, RelayoutContainer& container) override;
+
+private: // From RiveAnimationManager::LifecycleObserver:
+  /**
+   * @copydoc RiveAnimationManager::LifecycleObserver::RiveAnimationManagerDestroyed()
+   */
+  void RiveAnimationManagerDestroyed() override;
+
+private:
+  /**
+   * @brief Set an image url.
+   *
+   * @param[in] url The url of the image resource to display
+   */
+  void SetUrl(const std::string& url);
+
+  /**
+   * @brief Called when the texture upload is completed.
+   */
+  void OnUploadCompleted();
+
+  /**
+   * @brief Event callback from rasterize thread. This is called after the animation is finished.
+   */
+  void OnAnimationFinished();
+
+  /**
+   * @brief Send animation data to the rasterize thread.
+   */
+  void SendAnimationData();
+
+  /**
+   * @brief Set the vector image size.
+   */
+  void SetVectorImageSize();
+
+  /**
+   * @brief Trigger rasterization of the vector content.
+   */
+  void TriggerVectorRasterization();
+
+  /**
+   * @brief Callback when the world scale factor changes.
+   */
+  void OnScaleNotification(PropertyNotification& source);
+
+  /**
+   * @brief Callback when the size changes.
+   */
+  void OnSizeNotification(PropertyNotification& source);
+
+  /**
+   * @brief Callback when the visibility of the actor is changed.
+   */
+  void OnControlVisibilityChanged(Actor actor, bool visible, DevelActor::VisibilityChange::Type type);
+
+  /**
+   * @brief Callback when the visibility of the window is changed.
+   */
+  void OnWindowVisibilityChanged(Window window, bool visible);
+
+  /**
+   * @brief Callback when the event is processed.
+   */
+  void OnProcessEvents();
+
+private:
+  // Undefined
+  RiveAnimationView(const RiveAnimationView&);
+  RiveAnimationView& operator=(const RiveAnimationView&);
+
+private:
+  std::string                                             mUrl{};
+  RiveAnimationTask::AnimationData                        mAnimationData{};
+  RiveAnimationTaskPtr                                    mRiveAnimationTask{};
+  PropertyNotification                                    mScaleNotification;
+  PropertyNotification                                    mSizeNotification;
+  Dali::Renderer                                          mRenderer;
+  Dali::Extension::RiveAnimationView::AnimationSignalType mFinishedSignal{};
+  Vector2                                                 mSize{};
+  Vector2                                                 mScale{};
+  Dali::Extension::RiveAnimationView::PlayState           mPlayState{Dali::Extension::RiveAnimationView::PlayState::STOPPED};
+  CallbackBase*                                           mEventCallback{nullptr}; // Not owned
+  bool                                                    mLoadFailed{false};
+  bool                                                    mRendererAdded{false};
+  bool                                                    mCoreShutdown{false};
+};
+
+} // namespace Internal
+
+// Helpers for public-api forwarding methods
+inline Extension::Internal::RiveAnimationView& GetImplementation(Extension::RiveAnimationView& obj)
+{
+  DALI_ASSERT_ALWAYS(obj);
+  Dali::RefObject& handle = obj.GetImplementation();
+  return static_cast<Extension::Internal::RiveAnimationView&>(handle);
+}
+
+inline const Extension::Internal::RiveAnimationView& GetImplementation(const Extension::RiveAnimationView& obj)
+{
+  DALI_ASSERT_ALWAYS(obj);
+  const Dali::RefObject& handle = obj.GetImplementation();
+  return static_cast<const Extension::Internal::RiveAnimationView&>(handle);
+}
+
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_RIVE_ANIMATION_VIEW_H
diff --git a/dali-extension/internal/rive-animation-view/rive-rasterize-thread.cpp b/dali-extension/internal/rive-animation-view/rive-rasterize-thread.cpp
new file mode 100644 (file)
index 0000000..ff30a99
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-extension/internal/rive-animation-view/rive-rasterize-thread.h>
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/thread-settings.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
+#include <dali/integration-api/debug.h>
+#include <chrono>
+#include <thread>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gRiveAnimationLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RIVE_ANIMATION");
+#endif
+
+} // unnamed namespace
+
+RiveRasterizeThread::RiveRasterizeThread()
+: mRasterizeTasks(),
+  mConditionalWait(),
+  mCompletedCallback(),
+  mDestroyThread(false),
+  mIsThreadStarted(false),
+  mLogFactory(Dali::Adaptor::Get().GetLogFactory())
+{
+}
+
+RiveRasterizeThread::~RiveRasterizeThread()
+{
+  // Stop the thread
+  {
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+    mDestroyThread = true;
+    mConditionalWait.Notify(lock);
+  }
+
+  DALI_LOG_INFO(gRiveAnimationLogFilter, Debug::Verbose, "RiveRasterizeThread::~RiveRasterizeThread: Join [%p]\n", this);
+
+  Join();
+}
+
+void RiveRasterizeThread::SetCompletedCallback(CallbackBase* callback)
+{
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  mCompletedCallback = std::unique_ptr<CallbackBase>(callback);
+}
+
+void RiveRasterizeThread::AddTask(RiveAnimationTaskPtr task)
+{
+  // Lock while adding task to the queue
+  ConditionalWait::ScopedLock lock(mConditionalWait);
+
+  if(!mIsThreadStarted)
+  {
+    Start();
+    mIsThreadStarted = true;
+  }
+
+  if(mRasterizeTasks.end() == std::find(mRasterizeTasks.begin(), mRasterizeTasks.end(), task))
+  {
+    mRasterizeTasks.push_back(task);
+
+    // wake up the animation thread
+    mConditionalWait.Notify(lock);
+  }
+}
+
+void RiveRasterizeThread::Run()
+{
+  SetThreadName("RiveRasterizeThread");
+  mLogFactory.InstallLogFunction();
+
+  while(!mDestroyThread)
+  {
+    Rasterize();
+  }
+}
+
+void RiveRasterizeThread::Rasterize()
+{
+  RiveAnimationTaskPtr nextTask;
+  {
+    // Lock while popping task out from the queue
+    ConditionalWait::ScopedLock lock(mConditionalWait);
+
+    // conditional wait
+    if(mRasterizeTasks.empty())
+    {
+      mConditionalWait.Wait(lock);
+    }
+
+    // pop out the next task from the queue
+    if(!mRasterizeTasks.empty())
+    {
+      std::vector<RiveAnimationTaskPtr>::iterator next = mRasterizeTasks.begin();
+      nextTask                                         = *next;
+      mRasterizeTasks.erase(next);
+    }
+  }
+
+  if(nextTask)
+  {
+    bool keepAnimation = nextTask->Rasterize();
+
+    if(mCompletedCallback)
+    {
+      CallbackBase::Execute(*mCompletedCallback, nextTask, keepAnimation);
+    }
+  }
+}
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
diff --git a/dali-extension/internal/rive-animation-view/rive-rasterize-thread.h b/dali-extension/internal/rive-animation-view/rive-rasterize-thread.h
new file mode 100644 (file)
index 0000000..c0f1797
--- /dev/null
@@ -0,0 +1,100 @@
+#ifndef DALI_EXTENSION_INTERNAL_RIVE_RASTERIZE_THREAD_H
+#define DALI_EXTENSION_INTERNAL_RIVE_RASTERIZE_THREAD_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/threading/conditional-wait.h>
+#include <dali/devel-api/threading/thread.h>
+#include <dali/integration-api/adaptor-framework/log-factory-interface.h>
+#include <memory>
+#include <vector>
+
+// INTERNAL INCLUDES
+#include <dali-extension/internal/rive-animation-view/rive-animation-task.h>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+/**
+ * The worker thread for rive image rasterization.
+ */
+class RiveRasterizeThread : public Thread
+{
+public:
+  /**
+   * @brief Constructor.
+   */
+  RiveRasterizeThread();
+
+  /**
+   * @brief Destructor.
+   */
+  ~RiveRasterizeThread() override;
+
+  /**
+   * The callback is called from the rasterize thread after the rasterization is completed.
+   * @param[in] callBack  The function to call.
+   */
+  void SetCompletedCallback(CallbackBase* callback);
+
+  /**
+   * Add a task to rasterize.
+   *
+   * @param[in] task The task to rasterize
+   */
+  void AddTask(RiveAnimationTaskPtr task);
+
+protected:
+  /**
+   * @brief The entry function of the worker thread.
+   *        It rasterizes the rive image.
+   */
+  void Run() override;
+
+private:
+  /**
+   * Rasterizes the tasks.
+   */
+  void Rasterize();
+
+private:
+  // Undefined
+  RiveRasterizeThread(const RiveRasterizeThread& thread) = delete;
+
+  // Undefined
+  RiveRasterizeThread& operator=(const RiveRasterizeThread& thread) = delete;
+
+private:
+  std::vector<RiveAnimationTaskPtr> mRasterizeTasks;
+  ConditionalWait                   mConditionalWait;
+  std::unique_ptr<CallbackBase>     mCompletedCallback;
+  bool                              mDestroyThread; ///< Whether the thread be destroyed
+  bool                              mIsThreadStarted;
+  const Dali::LogFactoryInterface&  mLogFactory; ///< The log factory
+};
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_RIVE_RASTERIZE_THREAD_H
diff --git a/dali-extension/internal/rive-animation-view/round-robin-container-view.h b/dali-extension/internal/rive-animation-view/round-robin-container-view.h
new file mode 100644 (file)
index 0000000..2f4bdc8
--- /dev/null
@@ -0,0 +1,119 @@
+#ifndef DALI_EXTENSION_INTERNAL_ROUND_ROBIN_CONTAINER_VIEW_H
+#define DALI_EXTENSION_INTERNAL_ROUND_ROBIN_CONTAINER_VIEW_H
+
+/*
+ * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <cstddef>
+#include <vector>
+
+namespace Dali
+{
+namespace Extension
+{
+namespace Internal
+{
+/**
+ * @brief RoundRobinContainerView is a view to a container that allows iterating through the elements cyclically.
+ */
+template<typename T>
+class RoundRobinContainerView
+{
+public:
+  using ContainerType = std::vector<T>;
+
+  /**
+   * @brief Constructs a new RoundRobinControlView with the given number elements using the provided factory.
+   * @param[in] numberOfElements The number of elements in the container
+   * @param[in] factory          Factory function of functor that will be used to create instances of the elements
+   */
+  template<typename FactoryType>
+  RoundRobinContainerView(size_t numberOfElements, const FactoryType& factory)
+  : mElements(),
+    mNextIndex{}
+  {
+    mElements.reserve(numberOfElements);
+    for(unsigned i = {}; i < numberOfElements; ++i)
+    {
+      mElements.push_back(factory());
+    }
+  }
+
+  /**
+   * @brief Reset the position of the iterator returned by GetNext() to the first element.
+   */
+  void Reset()
+  {
+    mNextIndex = 0u;
+  }
+
+  /**
+   * @brief Returns the next element on the container.
+   * @return Iterator for the next element
+   */
+  typename ContainerType::iterator GetNext()
+  {
+    SetValidNextIndex();
+
+    return mElements.begin() + mNextIndex++;
+  }
+
+  /**
+   * @brief Returns the iterator to the end of the container.
+   *
+   * Can be used to compare against GetNext() to check if the container is empty.
+   *
+   * @return The container end() element
+   */
+  typename ContainerType::const_iterator End() const
+  {
+    return mElements.cend();
+  }
+
+  // default members
+  ~RoundRobinContainerView() = default;
+
+  RoundRobinContainerView(const RoundRobinContainerView&) = delete;
+  RoundRobinContainerView& operator=(const RoundRobinContainerView&) = delete;
+  RoundRobinContainerView(RoundRobinContainerView&&)                 = default;
+  RoundRobinContainerView& operator=(RoundRobinContainerView&&) = default;
+
+private:
+  /**
+   * @brief Check the current index and reset if necessary.
+   */
+  void SetValidNextIndex()
+  {
+    if(mNextIndex >= mElements.size())
+    {
+      Reset();
+    }
+  }
+
+private:
+  ContainerType mElements;  //< container of elements
+  size_t        mNextIndex; //< index to the next element to be viewed
+};
+
+} // namespace Internal
+
+} // namespace Extension
+
+} // namespace Dali
+
+#endif // DALI_EXTENSION_INTERNAL_ROUND_ROBIN_CONTAINER_VIEW_H
index aca9cf0..5795f9e 100755 (executable)
@@ -48,13 +48,7 @@ dali-extension
 %define tizen_65_or_greater 1
 %endif
 
-
-# # Note
-# %if 0%{?tizen_version_major} >= 6
-# %define tizen_60_or_greater 1
-# %endif
-
-##############################
+#############################
 # devel
 ##############################
 %package devel
@@ -239,6 +233,9 @@ cd %{_builddir}/%{name}-%{version}/build/tizen
 autoreconf --install
 
 %configure --prefix=$PREFIX \
+%if 0%{?enable_debug}
+           --enable-debug \
+%endif
 %if 0%{?tizen_50_or_greater}
            --with-tizen-50-or-greater \
 %endif