/*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 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.
#include <dali/devel-api/scripting/scripting.h>
#include <dali/integration-api/adaptor-framework/adaptor.h>
#include <dali/integration-api/debug.h>
+#include <dali/public-api/math/math-utils.h>
#include <dali/public-api/object/object-registry.h>
#include <dali/public-api/object/type-registry-helper.h>
#include <cstring>
#include <dali-toolkit/internal/styling/style-manager-impl.h>
#include <dali-toolkit/internal/visuals/visual-base-impl.h>
#include <dali-toolkit/internal/visuals/visual-string-constants.h>
+#include <dali-toolkit/public-api/align-enumerations.h>
#include <dali-toolkit/public-api/controls/image-view/image-view.h>
#include <dali-toolkit/public-api/focus-manager/keyboard-focus-manager.h>
#include <dali-toolkit/public-api/visuals/image-visual-properties.h>
namespace
{
-const std::string READING_INFO_TYPE_NAME = "name";
-const std::string READING_INFO_TYPE_ROLE = "role";
-const std::string READING_INFO_TYPE_DESCRIPTION = "description";
-const std::string READING_INFO_TYPE_STATE = "state";
-const std::string READING_INFO_TYPE_ATTRIBUTE_NAME = "reading_info_type";
-const std::string READING_INFO_TYPE_SEPARATOR = "|";
+const char* READING_INFO_TYPE_NAME = "name";
+const char* READING_INFO_TYPE_ROLE = "role";
+const char* READING_INFO_TYPE_DESCRIPTION = "description";
+const char* READING_INFO_TYPE_STATE = "state";
+const char* READING_INFO_TYPE_ATTRIBUTE_NAME = "reading_info_type";
+const char* READING_INFO_TYPE_SEPARATOR = "|";
} // namespace
namespace Dali
{"DISABLED", Toolkit::DevelControl::DISABLED},
};
const unsigned int ControlStateTableCount = sizeof(ControlStateTable) / sizeof(ControlStateTable[0]);
+const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f);
namespace
{
return false;
}
+/**
+ * Finds visual in given array, returning true if found along with the iterator for that visual as a out parameter
+ */
+bool FindVisual(const Toolkit::Visual::Base findVisual, const RegisteredVisualContainer& visuals, RegisteredVisualContainer::Iterator& iter)
+{
+ for(iter = visuals.Begin(); iter != visuals.End(); iter++)
+ {
+ Toolkit::Visual::Base visual = (*iter)->visual;
+ if(visual && visual == findVisual)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Finds internal visual in given array, returning true if found along with the iterator for that visual as a out parameter
+ */
+bool FindVisual(const Visual::Base& findInternalVisual, const RegisteredVisualContainer& visuals, RegisteredVisualContainer::Iterator& iter)
+{
+ for(iter = visuals.Begin(); iter != visuals.End(); iter++)
+ {
+ Visual::Base& visual = Toolkit::GetImplementation((*iter)->visual);
+ if((&visual == &findInternalVisual))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
void FindChangableVisuals(Dictionary<Property::Map>& stateVisualsToAdd,
Dictionary<Property::Map>& stateVisualsToChange,
DictionaryKeys& stateVisualsToRemove)
}
/**
+ * Discard visual from source to visual factory.
+ */
+void DiscardVisual(RegisteredVisualContainer::Iterator sourceIter, RegisteredVisualContainer& source)
+{
+ Toolkit::Visual::Base visual = (*sourceIter)->visual;
+ if(visual)
+ {
+ if(Stage::IsInstalled())
+ {
+ Toolkit::VisualFactory::Get().DiscardVisual(visual);
+ }
+ }
+
+ source.Erase(sourceIter);
+}
+
+/**
* Performs actions as requested using the action name.
* @param[in] object The object on which to perform the action.
* @param[in] actionName The action to perform.
mStartingPinchScale(nullptr),
mMargin(0, 0, 0, 0),
mPadding(0, 0, 0, 0),
+ mSize(0, 0),
mKeyEventSignal(),
mKeyInputFocusGainedSignal(),
mKeyInputFocusLostSignal(),
mIsKeyboardNavigationSupported(false),
mIsKeyboardFocusGroup(false),
mIsEmittingResourceReadySignal(false),
- mNeedToEmitResourceReady(false),
- mDispatchKeyEvents(true)
+ mIdleCallbackRegistered(false),
+ mDispatchKeyEvents(true),
+ mProcessorRegistered(false)
{
Dali::Accessibility::Accessible::RegisterExternalAccessibleGetter(&ExternalAccessibleGetter);
}
// All gesture detectors will be destroyed so no need to disconnect.
delete mStartingPinchScale;
+ if(mProcessorRegistered && Adaptor::IsAvailable())
+ {
+ // Unregister the processor from the adaptor
+ Adaptor::Get().UnregisterProcessorOnce(*this, true);
+ }
+
if(mIdleCallback && Adaptor::IsAvailable())
{
// Removes the callback from the callback manager in case the control is destroyed before the callback is executed.
Control::Impl& Control::Impl::Get(Internal::Control& internalControl)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
return *internalControl.mImpl;
}
const Control::Impl& Control::Impl::Get(const Internal::Control& internalControl)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
return *internalControl.mImpl;
}
void Control::Impl::CheckHighlightedObjectGeometry()
{
- auto accessible = GetAccessibleObject();
- auto lastPosition = accessible->GetLastPosition();
- auto accessibleRect = accessible->GetExtents(Dali::Accessibility::CoordinateType::WINDOW);
- auto rect = GetShowingGeometry(accessibleRect, accessible);
-
- switch(mAccessibilityLastScreenRelativeMoveType)
+ auto accessible = GetAccessibleObject();
+ if(DALI_LIKELY(accessible))
{
- case Dali::Accessibility::ScreenRelativeMoveType::OUTSIDE:
- {
- if(IsShowingGeometryOnScreen(rect))
- {
- mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::INSIDE;
- }
- break;
- }
- case Dali::Accessibility::ScreenRelativeMoveType::INSIDE:
+ auto lastPosition = accessible->GetLastPosition();
+ auto accessibleRect = accessible->GetExtents(Dali::Accessibility::CoordinateType::WINDOW);
+ auto rect = GetShowingGeometry(accessibleRect, accessible);
+
+ switch(mAccessibilityLastScreenRelativeMoveType)
{
- if(rect.width < 0 && accessibleRect.x != lastPosition.x)
+ case Dali::Accessibility::ScreenRelativeMoveType::OUTSIDE:
{
- mAccessibilityLastScreenRelativeMoveType = (accessibleRect.x < lastPosition.x) ? Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT : Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT;
+ if(IsShowingGeometryOnScreen(rect))
+ {
+ mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::INSIDE;
+ }
+ break;
}
- if(rect.height < 0 && accessibleRect.y != lastPosition.y)
+ case Dali::Accessibility::ScreenRelativeMoveType::INSIDE:
{
- mAccessibilityLastScreenRelativeMoveType = (accessibleRect.y < lastPosition.y) ? Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT : Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT;
+ if(rect.width < 0 && !Dali::Equals(accessibleRect.x, lastPosition.x))
+ {
+ mAccessibilityLastScreenRelativeMoveType = (accessibleRect.x < lastPosition.x) ? Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT : Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT;
+ }
+ if(rect.height < 0 && !Dali::Equals(accessibleRect.y, lastPosition.y))
+ {
+ mAccessibilityLastScreenRelativeMoveType = (accessibleRect.y < lastPosition.y) ? Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT : Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT;
+ }
+ // notify AT-clients on outgoing moves only
+ if(mAccessibilityLastScreenRelativeMoveType != Dali::Accessibility::ScreenRelativeMoveType::INSIDE)
+ {
+ accessible->EmitMovedOutOfScreen(mAccessibilityLastScreenRelativeMoveType);
+ }
+ break;
}
- // notify AT-clients on outgoing moves only
- if(mAccessibilityLastScreenRelativeMoveType != Dali::Accessibility::ScreenRelativeMoveType::INSIDE)
+ case Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT:
+ case Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT:
{
- accessible->EmitMovedOutOfScreen(mAccessibilityLastScreenRelativeMoveType);
+ if(IsShowingGeometryOnScreen(rect))
+ {
+ mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::INSIDE;
+ }
+ else
+ {
+ mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::OUTSIDE;
+ }
+ break;
}
- break;
- }
- case Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_TOP_LEFT:
- case Dali::Accessibility::ScreenRelativeMoveType::OUTGOING_BOTTOM_RIGHT:
- {
- if(IsShowingGeometryOnScreen(rect))
+ default:
{
- mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::INSIDE;
- }
- else
- {
- mAccessibilityLastScreenRelativeMoveType = Dali::Accessibility::ScreenRelativeMoveType::OUTSIDE;
+ break;
}
- break;
}
- default:
- {
- break;
- }
- }
- accessible->SetLastPosition(Vector2(accessibleRect.x, accessibleRect.y));
+ accessible->SetLastPosition(Vector2(accessibleRect.x, accessibleRect.y));
+ }
}
void Control::Impl::RegisterAccessibilityPositionPropertyNotification()
mIsAccessibilityPositionPropertyNotificationSet = false;
}
+void Control::Impl::RegisterAccessibilityPropertySetSignal()
+{
+ if(mIsAccessibilityPropertySetSignalRegistered)
+ {
+ return;
+ }
+ mControlImpl.Self().PropertySetSignal().Connect(this, &Control::Impl::OnAccessibilityPropertySet);
+ mIsAccessibilityPropertySetSignalRegistered = true;
+}
+
+void Control::Impl::UnregisterAccessibilityPropertySetSignal()
+{
+ if(!mIsAccessibilityPropertySetSignalRegistered)
+ {
+ return;
+ }
+ mControlImpl.Self().PropertySetSignal().Disconnect(this, &Control::Impl::OnAccessibilityPropertySet);
+ mIsAccessibilityPropertySetSignalRegistered = false;
+}
+
+void Control::Impl::OnAccessibilityPropertySet(Dali::Handle& handle, Dali::Property::Index index, const Dali::Property::Value& value)
+{
+ auto* accessible = GetAccessibleObject();
+ if(DALI_LIKELY(accessible))
+ {
+ if(mAccessibilityGetNameSignal.Empty())
+ {
+ if(index == DevelControl::Property::ACCESSIBILITY_NAME || (mAccessibilityName.empty() && index == accessible->GetNamePropertyIndex()))
+ {
+ accessible->Emit(Dali::Accessibility::ObjectPropertyChangeEvent::NAME);
+ }
+ }
+
+ if(mAccessibilityGetDescriptionSignal.Empty())
+ {
+ if(index == DevelControl::Property::ACCESSIBILITY_DESCRIPTION || (mAccessibilityDescription.empty() && index == accessible->GetDescriptionPropertyIndex()))
+ {
+ accessible->Emit(Dali::Accessibility::ObjectPropertyChangeEvent::DESCRIPTION);
+ }
+ }
+ }
+}
+
// Gesture Detection Methods
void Control::Impl::PinchDetected(Actor actor, const PinchGesture& pinch)
{
void Control::Impl::RegisterVisual(Property::Index index, Toolkit::Visual::Base& visual, VisualState::Type enabled, DepthIndexValue::Type depthIndexValueSet, int depthIndex)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
DALI_LOG_INFO(gLogFilter, Debug::Concise, "RegisterVisual:%d \n", index);
bool visualReplaced(false);
requiredDepthIndex = depthIndex;
}
+ // Change the depth index value automatically if the visual has DepthIndex to AUTO_INDEX
+ // or if RegisterVisual set DepthIndex to AUTO_INDEX.
+ const bool requiredDepthIndexChanged = (requiredDepthIndex == DepthIndex::AUTO_INDEX);
+
// Visual replacement, existing visual should only be removed from stage when replacement ready.
if(!mVisuals.Empty())
{
mVisuals.Erase(registeredVisualsiter);
}
- // If we've not set the depth-index value and the new visual does not have a depth index applied to it, then use the previously set depth-index for this index
- if((depthIndexValueSet == DepthIndexValue::NOT_SET) &&
- (visual.GetDepthIndex() == 0))
+ // If the visual have a depth index as AUTO_INDEX and the new visual does not have a depth index applied to it, then use the previously set depth-index for this index
+ if(requiredDepthIndexChanged)
{
requiredDepthIndex = currentDepthIndex;
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Use replaced visual index. VisualDepthIndex AUTO_INDEX set as: %d\n", requiredDepthIndex);
}
}
if(!visualReplaced) // New registration entry
{
- // If we've not set the depth-index value, we have more than one visual and the visual does not have a depth index, then set it to be the highest
- if((depthIndexValueSet == DepthIndexValue::NOT_SET) &&
- (mVisuals.Size() > 0) &&
- (visual.GetDepthIndex() == 0))
+ // If we have more than one visual and the visual have a depth index as AUTO_INDEX, then set it to be the highest
+ if((mVisuals.Size() > 0) && requiredDepthIndexChanged)
{
- int maxDepthIndex = std::numeric_limits<int>::min();
+ int maxDepthIndex = static_cast<int>(DepthIndex::CONTENT) - 1; // Start at DepthIndex::CONTENT if maxDepth index belongs to a background or no visuals have been added yet.
RegisteredVisualContainer::ConstIterator iter;
const RegisteredVisualContainer::ConstIterator endIter = mVisuals.End();
maxDepthIndex = visualDepthIndex;
}
}
- ++maxDepthIndex; // Add one to the current maximum depth index so that our added visual appears on top
- requiredDepthIndex = std::max(0, maxDepthIndex); // Start at zero if maxDepth index belongs to a background
+ requiredDepthIndex = ++maxDepthIndex; // Add one to the current maximum depth index so that our added visual appears on top.
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Use top of all visuals. VisualDepthIndex AUTO_INDEX set as: %d\n", requiredDepthIndex);
}
}
if(visual)
{
+ // If required depth index still DepthIndex::AUTO_INDEX, Make it as DepthIndex::CONTENT now
+ if(requiredDepthIndex == static_cast<int>(DepthIndex::AUTO_INDEX))
+ {
+ requiredDepthIndex = static_cast<int>(DepthIndex::CONTENT);
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Some strange cases. VisualDepthIndex AUTO_INDEX set as: %d\n", requiredDepthIndex);
+ }
+
// Set determined depth index
visual.SetDepthIndex(requiredDepthIndex);
{
visualImpl.SetOnScene(self);
}
- else if(visualImpl.IsResourceReady()) // When not being staged, check if visual already 'ResourceReady' before it was Registered. ( Resource may have been loaded already )
+ else if(enabled && visualImpl.IsResourceReady()) // When not being staged, check if visual already 'ResourceReady' before it was Registered. ( Resource may have been loaded already )
{
ResourceReady(visualImpl);
}
void Control::Impl::UnregisterVisual(Property::Index index)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
RegisteredVisualContainer::Iterator iter;
if(FindVisual(index, mVisuals, iter))
{
Actor self(mControlImpl.Self());
Toolkit::GetImplementation((*iter)->visual).SetOffScene(self);
(*iter)->pending = false;
- (*iter)->visual.Reset();
- mRemoveVisuals.Erase(iter);
+
+ // Discard removed visual. It will be destroyed at next Idle time.
+ DiscardVisual(iter, mRemoveVisuals);
}
}
return false;
}
+void Control::Impl::EnableReadyTransitionOverriden(Toolkit::Visual::Base& visual, bool enable)
+{
+ DALI_LOG_INFO(gLogFilter, Debug::General, "Control::EnableReadyTransitionOverriden(%p, %s)\n", visual, enable ? "T" : "F");
+
+ RegisteredVisualContainer::Iterator iter;
+ if(FindVisual(visual, mVisuals, iter))
+ {
+ if((*iter)->overideReadyTransition == enable)
+ {
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Control::EnableReadyTransitionOverriden Visual %s(%p) already %s\n", (*iter)->visual.GetName().c_str(), visual, enable ? "enabled" : "disabled");
+ return;
+ }
+
+ (*iter)->overideReadyTransition = enable;
+ }
+}
+
void Control::Impl::StopObservingVisual(Toolkit::Visual::Base& visual)
{
Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(visual);
visualImpl.AddEventObserver(*this);
}
+void Control::Impl::ResourceReady()
+{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
+ // Emit signal if all enabled visuals registered by the control are ready or there are no visuals.
+ if(IsResourceReady())
+ {
+ EmitResourceReadySignal();
+ }
+}
+
// Called by a Visual when it's resource is ready
void Control::Impl::ResourceReady(Visual::Base& object)
{
Actor self = mControlImpl.Self();
+ RegisteredVisualContainer::Iterator registeredIter;
+
// A resource is ready, find resource in the registered visuals container and get its index
- for(auto registeredIter = mVisuals.Begin(), end = mVisuals.End(); registeredIter != end; ++registeredIter)
+ if(!FindVisual(object, mVisuals, registeredIter))
{
- Internal::Visual::Base& registeredVisualImpl = Toolkit::GetImplementation((*registeredIter)->visual);
+ return;
+ }
- if(&object == ®isteredVisualImpl)
+ RegisteredVisualContainer::Iterator visualToRemoveIter;
+ // Find visual with the same index in the removal container
+ // Set if off stage as it's replacement is now ready.
+ // Remove if from removal list as now removed from stage.
+ // Set Pending flag on the ready visual to false as now ready.
+ if(FindVisual((*registeredIter)->index, mRemoveVisuals, visualToRemoveIter))
+ {
+ (*registeredIter)->pending = false;
+ if(!((*visualToRemoveIter)->overideReadyTransition))
{
- RegisteredVisualContainer::Iterator visualToRemoveIter;
- // Find visual with the same index in the removal container
- // Set if off stage as it's replacement is now ready.
- // Remove if from removal list as now removed from stage.
- // Set Pending flag on the ready visual to false as now ready.
- if(FindVisual((*registeredIter)->index, mRemoveVisuals, visualToRemoveIter))
- {
- (*registeredIter)->pending = false;
- Toolkit::GetImplementation((*visualToRemoveIter)->visual).SetOffScene(self);
- mRemoveVisuals.Erase(visualToRemoveIter);
- }
- break;
+ Toolkit::GetImplementation((*visualToRemoveIter)->visual).SetOffScene(self);
}
+
+ // Discard removed visual. It will be destroyed at next Idle time.
+ DiscardVisual(visualToRemoveIter, mRemoveVisuals);
}
// A visual is ready so control may need relayouting if staged
- if(self.GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
- {
- mControlImpl.RelayoutRequest();
- }
+ RelayoutRequest(object);
- // Emit signal if all enabled visuals registered by the control are ready.
- if(IsResourceReady())
+ // Called by a Visual when it's resource is ready
+ if(((*registeredIter)->enabled))
{
- // Reset the flag
- mNeedToEmitResourceReady = false;
-
- EmitResourceReadySignal();
+ ResourceReady();
}
}
void Control::Impl::RelayoutRequest(Visual::Base& object)
{
- mControlImpl.RelayoutRequest();
+ if(mControlImpl.Self().GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
+ {
+ mControlImpl.RelayoutRequest();
+ }
}
bool Control::Impl::IsResourceReady() const
TransitionData::Animator* animator = (*iter);
Toolkit::Visual::Base visual = GetVisualByName(mVisuals, animator->objectName);
-
if(visual)
{
#if defined(DEBUG_ENABLED)
}
}
+void Control::Impl::DoActionExtension(Dali::Property::Index visualIndex, Dali::Property::Index actionId, Dali::Any attributes)
+{
+ RegisteredVisualContainer::Iterator iter;
+ if(FindVisual(visualIndex, mVisuals, iter))
+ {
+ Toolkit::GetImplementation((*iter)->visual).DoActionExtension(actionId, attributes);
+ }
+}
+
void Control::Impl::AppendAccessibilityAttribute(const std::string& key, const std::string value)
{
Property::Value* checkedValue = mAccessibilityAttributes.Find(key);
void Control::Impl::SetProperty(BaseObject* object, Property::Index index, const Property::Value& value)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
Toolkit::Control control = Toolkit::Control::DownCast(BaseHandle(object));
if(control)
controlImpl.mImpl->mAccessibilityHidden = hidden;
auto* accessible = controlImpl.GetAccessibleObject();
- auto* parent = dynamic_cast<Dali::Accessibility::ActorAccessible*>(accessible->GetParent());
- if(parent)
+ if(DALI_LIKELY(accessible))
{
- parent->OnChildrenChanged();
+ auto* parent = dynamic_cast<Dali::Accessibility::ActorAccessible*>(accessible->GetParent());
+ if(parent)
+ {
+ parent->OnChildrenChanged();
+ }
}
}
break;
Property::Value Control::Impl::GetProperty(BaseObject* object, Property::Index index)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
Property::Value value;
Toolkit::Control control = Toolkit::Control::DownCast(BaseHandle(object));
void Control::Impl::ReplaceStateVisualsAndProperties(const StylePtr oldState, const StylePtr newState, const std::string& subState)
{
+ DALI_ASSERT_ALWAYS(Stage::IsCoreThread() && "Core is not installed. Might call this API from worker thread?");
+
// Collect all old visual names
DictionaryKeys stateVisualsToRemove;
if(oldState)
// Visuals pending replacement can now be taken out of the removal list and set off scene
// Iterate through all replacement visuals and add to a move queue then set off scene
- for(auto removalIter = mRemoveVisuals.Begin(), end = mRemoveVisuals.End(); removalIter != end; removalIter++)
+
+ if(!mRemoveVisuals.Empty())
{
- Toolkit::GetImplementation((*removalIter)->visual).SetOffScene(self);
+ std::reverse(mRemoveVisuals.Begin(), mRemoveVisuals.End());
+
+ while(!mRemoveVisuals.Empty())
+ {
+ auto removalIter = mRemoveVisuals.End() - 1u;
+ Toolkit::GetImplementation((*removalIter)->visual).SetOffScene(self);
+
+ // Discard removed visual. It will be destroyed at next Idle time.
+ DiscardVisual(removalIter, mRemoveVisuals);
+ }
}
for(auto replacedIter = mVisuals.Begin(), end = mVisuals.End(); replacedIter != end; replacedIter++)
{
(*replacedIter)->pending = false;
}
-
- mRemoveVisuals.Clear();
}
void Control::Impl::SetMargin(Extents margin)
if(visual)
{
Internal::Visual::Base& visualImpl = Toolkit::GetImplementation(visual);
- return visualImpl.GetPropertyObject(visualPropertyKey);
+ return visualImpl.GetPropertyObject(std::move(visualPropertyKey));
}
Handle handle;
mIsEmittingResourceReadySignal = true;
// If the signal handler changes visual, it may become ready during this call & therefore this method will
- // get called again recursively. If so, mNeedToEmitResourceReady is set below, and we act on it after that secondary
+ // get called again recursively. If so, mIdleCallbackRegistered is set below, and we act on it after that secondary
// invocation has completed by notifying in an Idle callback to prevent further recursion.
Dali::Toolkit::Control handle(mControlImpl.GetOwner());
mResourceReadySignal.Emit(handle);
- if(mNeedToEmitResourceReady)
+ mIsEmittingResourceReadySignal = false;
+ }
+ else
+ {
+ if(!mIdleCallbackRegistered)
{
+ mIdleCallbackRegistered = true;
+
// Add idler to emit the signal again
if(!mIdleCallback)
{
// The callback manager takes the ownership of the callback object.
mIdleCallback = MakeCallback(this, &Control::Impl::OnIdleCallback);
- Adaptor::Get().AddIdle(mIdleCallback, false);
+ if(DALI_UNLIKELY(!Adaptor::Get().AddIdle(mIdleCallback, true)))
+ {
+ DALI_LOG_ERROR("Fail to add idle callback for control resource ready. Skip this callback.\n");
+ mIdleCallback = nullptr;
+ mIdleCallbackRegistered = false;
+ }
}
}
-
- mIsEmittingResourceReadySignal = false;
- }
- else
- {
- mNeedToEmitResourceReady = true;
}
}
-void Control::Impl::OnIdleCallback()
+bool Control::Impl::OnIdleCallback()
{
- if(mNeedToEmitResourceReady)
+ // Reset the flag
+ mIdleCallbackRegistered = false;
+
+ // A visual is ready so control may need relayouting if staged
+ if(mControlImpl.Self().GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
{
- // Reset the flag
- mNeedToEmitResourceReady = false;
+ mControlImpl.RelayoutRequest();
+ }
- // A visual is ready so control may need relayouting if staged
- if(mControlImpl.Self().GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
- {
- mControlImpl.RelayoutRequest();
- }
+ EmitResourceReadySignal();
- EmitResourceReadySignal();
+ if(!mIdleCallbackRegistered)
+ {
+ // Set the pointer to null as the callback manager deletes the callback after execute it.
+ mIdleCallback = nullptr;
}
- // Set the pointer to null as the callback manager deletes the callback after execute it.
- mIdleCallback = nullptr;
+ // Repeat idle if mIdleCallbackRegistered become true one more time.
+ return mIdleCallbackRegistered;
}
Toolkit::DevelControl::ControlAccessible* Control::Impl::GetAccessibleObject()
{
- if(!mAccessibleObject)
+ if(mAccessibleCreatable && !mAccessibleObject)
{
mAccessibleObject.reset(mControlImpl.CreateAccessibleObject());
}
return mAccessibleObject.get();
}
+bool Control::Impl::IsAccessibleCreated() const
+{
+ return !!mAccessibleObject;
+}
+
+void Control::Impl::EnableCreateAccessible(bool enable)
+{
+ mAccessibleCreatable = enable;
+}
+
+bool Control::Impl::IsCreateAccessibleEnabled() const
+{
+ return mAccessibleCreatable;
+}
+
+void Control::Impl::ApplyFittingMode(const Vector2& size)
+{
+ Actor self = mControlImpl.Self();
+ for(RegisteredVisualContainer::Iterator iter = mVisuals.Begin(); iter != mVisuals.End(); iter++)
+ {
+ // Check whether the visual is empty and enabled
+ if((*iter)->visual && (*iter)->enabled)
+ {
+ Internal::Visual::Base& visualImpl = Toolkit::GetImplementation((*iter)->visual);
+
+ // If the current visual is using the transform property map, fittingMode will not be applied.
+ if(visualImpl.IsIgnoreFittingMode())
+ {
+ continue;
+ }
+
+ Visual::FittingMode fittingMode = visualImpl.GetFittingMode();
+ Property::Map transformMap = Property::Map();
+
+ // If the fittingMode is DONT_CARE, we don't need to apply fittingMode, just Set empty transformMap
+ if(fittingMode == Visual::FittingMode::DONT_CARE)
+ {
+ if(visualImpl.GetType() != Toolkit::Visual::Type::TEXT)
+ {
+ ((*iter)->visual).SetTransformAndSize(transformMap, size);
+ }
+ continue;
+ }
+
+ Extents padding = self.GetProperty<Extents>(Toolkit::Control::Property::PADDING);
+
+ bool zeroPadding = (padding == Extents());
+
+ Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>(
+ self.GetProperty(Dali::Actor::Property::LAYOUT_DIRECTION).Get<int>());
+ if(Dali::LayoutDirection::RIGHT_TO_LEFT == layoutDirection)
+ {
+ std::swap(padding.start, padding.end);
+ }
+
+ // remove padding from the size to know how much is left for the visual
+ Vector2 finalSize = size - Vector2(padding.start + padding.end, padding.top + padding.bottom);
+ Vector2 finalOffset = Vector2(padding.start, padding.top);
+
+ // Reset PIXEL_AREA after using OVER_FIT_KEEP_ASPECT_RATIO
+ if(visualImpl.IsPixelAreaSetForFittingMode())
+ {
+ visualImpl.SetPixelAreaForFittingMode(FULL_TEXTURE_RECT);
+ }
+
+ if((!zeroPadding) || // If padding is not zero
+ (fittingMode != Visual::FittingMode::FILL))
+ {
+ visualImpl.SetTransformMapUsageForFittingMode(true);
+
+ Vector2 naturalSize;
+ // NaturalSize will not be used for FILL fitting mode, which is default.
+ // Skip GetNaturalSize
+ if(fittingMode != Visual::FittingMode::FILL)
+ {
+ ((*iter)->visual).GetNaturalSize(naturalSize);
+ }
+
+ // If FittingMode use FIT_WIDTH or FIT_HEIGTH, it need to change proper fittingMode
+ if(fittingMode == Visual::FittingMode::FIT_WIDTH || fittingMode == Visual::FittingMode::FIT_HEIGHT)
+ {
+ const float widthRatio = !Dali::EqualsZero(naturalSize.width) ? (finalSize.width / naturalSize.width) : 0.0f;
+ const float heightRatio = !Dali::EqualsZero(naturalSize.height) ? (finalSize.height / naturalSize.height) : 0.0f;
+ if(widthRatio < heightRatio)
+ {
+ // Final size has taller form than natural size.
+ fittingMode = (fittingMode == Visual::FittingMode::FIT_WIDTH) ? Visual::FittingMode::FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO;
+ }
+ else
+ {
+ // Final size has wider form than natural size.
+ fittingMode = (fittingMode == Visual::FittingMode::FIT_WIDTH) ? Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO : Visual::FittingMode::FIT_KEEP_ASPECT_RATIO;
+ }
+ }
+
+ // Calculate size for fittingMode
+ switch(fittingMode)
+ {
+ case Visual::FittingMode::FIT_KEEP_ASPECT_RATIO:
+ {
+ auto availableVisualSize = finalSize;
+
+ // scale to fit the padded area
+ finalSize = naturalSize * std::min((!Dali::EqualsZero(naturalSize.width) ? (availableVisualSize.width / naturalSize.width) : 0),
+ (!Dali::EqualsZero(naturalSize.height) ? (availableVisualSize.height / naturalSize.height) : 0));
+
+ // calculate final offset within the padded area
+ finalOffset += (availableVisualSize - finalSize) * .5f;
+
+ // populate the transform map
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
+ .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
+ break;
+ }
+ case Visual::FittingMode::OVER_FIT_KEEP_ASPECT_RATIO:
+ {
+ auto availableVisualSize = finalSize;
+ finalSize = naturalSize * std::max((!Dali::EqualsZero(naturalSize.width) ? (availableVisualSize.width / naturalSize.width) : 0.0f),
+ (!Dali::EqualsZero(naturalSize.height) ? (availableVisualSize.height / naturalSize.height) : 0.0f));
+
+ auto originalOffset = finalOffset;
+
+ if(!visualImpl.IsPixelAreaSetForFittingMode() && !Dali::EqualsZero(finalSize.width) && !Dali::EqualsZero(finalSize.height))
+ {
+ float x = abs((availableVisualSize.width - finalSize.width) / finalSize.width) * .5f;
+ float y = abs((availableVisualSize.height - finalSize.height) / finalSize.height) * .5f;
+ float widthRatio = 1.f - abs((availableVisualSize.width - finalSize.width) / finalSize.width);
+ float heightRatio = 1.f - abs((availableVisualSize.height - finalSize.height) / finalSize.height);
+ Vector4 pixelArea = Vector4(x, y, widthRatio, heightRatio);
+ visualImpl.SetPixelAreaForFittingMode(pixelArea);
+ }
+
+ // populate the transform map
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, originalOffset)
+ .Add(Toolkit::Visual::Transform::Property::SIZE, availableVisualSize);
+ break;
+ }
+ case Visual::FittingMode::CENTER:
+ {
+ auto availableVisualSize = finalSize;
+ if(availableVisualSize.width > naturalSize.width && availableVisualSize.height > naturalSize.height)
+ {
+ finalSize = naturalSize;
+ }
+ else
+ {
+ finalSize = naturalSize * std::min((!Dali::EqualsZero(naturalSize.width) ? (availableVisualSize.width / naturalSize.width) : 0.0f),
+ (!Dali::EqualsZero(naturalSize.height) ? (availableVisualSize.height / naturalSize.height) : 0.0f));
+ }
+
+ finalOffset += (availableVisualSize - finalSize) * .5f;
+
+ // populate the transform map
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
+ .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
+ break;
+ }
+ case Visual::FittingMode::FILL:
+ {
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, finalOffset)
+ .Add(Toolkit::Visual::Transform::Property::SIZE, finalSize);
+ break;
+ }
+ case Visual::FittingMode::FIT_WIDTH:
+ case Visual::FittingMode::FIT_HEIGHT:
+ case Visual::FittingMode::DONT_CARE:
+ {
+ // This FittingMode already converted
+ break;
+ }
+ }
+
+ // Set extra value for applying transformMap
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
+ Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE))
+ .Add(Toolkit::Visual::Transform::Property::ORIGIN, Toolkit::Align::TOP_BEGIN)
+ .Add(Toolkit::Visual::Transform::Property::ANCHOR_POINT, Toolkit::Align::TOP_BEGIN)
+ .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
+ Vector2(Toolkit::Visual::Transform::Policy::ABSOLUTE, Toolkit::Visual::Transform::Policy::ABSOLUTE));
+ }
+ else if(visualImpl.IsTransformMapSetForFittingMode() && zeroPadding) // Reset offset to zero only if padding applied previously
+ {
+ visualImpl.SetTransformMapUsageForFittingMode(false);
+
+ // Reset the transform map
+ transformMap.Add(Toolkit::Visual::Transform::Property::OFFSET, Vector2::ZERO)
+ .Add(Toolkit::Visual::Transform::Property::OFFSET_POLICY,
+ Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE))
+ .Add(Toolkit::Visual::Transform::Property::SIZE, Vector2::ONE)
+ .Add(Toolkit::Visual::Transform::Property::SIZE_POLICY,
+ Vector2(Toolkit::Visual::Transform::Policy::RELATIVE, Toolkit::Visual::Transform::Policy::RELATIVE));
+ }
+
+ ((*iter)->visual).SetTransformAndSize(transformMap, size);
+ }
+ }
+}
+
+void Control::Impl::RegisterProcessorOnce()
+{
+ if(!mProcessorRegistered)
+ {
+ Adaptor::Get().RegisterProcessorOnce(*this, true);
+ mProcessorRegistered = true;
+ }
+}
+
+void Control::Impl::Process(bool postProcessor)
+{
+ // Call ApplyFittingMode
+ ApplyFittingMode(mSize);
+ mProcessorRegistered = false;
+}
+
} // namespace Internal
} // namespace Toolkit