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)
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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
[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)
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
# 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 =
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)
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)
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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])],
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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])],
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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)
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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
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)
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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)
devincludepath=${includedir}
AC_SUBST(devincludepath)
+AC_SUBST(DALI_CFLAGS)
AC_CONFIG_FILES([
Makefile
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
--- /dev/null
+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
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+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
+
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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
--- /dev/null
+#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
--- /dev/null
+#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
%define tizen_65_or_greater 1
%endif
-
-# # Note
-# %if 0%{?tizen_version_major} >= 6
-# %define tizen_60_or_greater 1
-# %endif
-
-##############################
+#############################
# devel
##############################
%package devel
autoreconf --install
%configure --prefix=$PREFIX \
+%if 0%{?enable_debug}
+ --enable-debug \
+%endif
%if 0%{?tizen_50_or_greater}
--with-tizen-50-or-greater \
%endif