2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/event/actors/actor-relayouter.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/actors/actor.h>
24 #include <dali/public-api/math/vector2.h>
25 #include <dali/public-api/math/vector3.h>
27 #include <dali/internal/event/size-negotiation/relayout-controller-impl.h>
31 #if defined(DEBUG_ENABLED)
32 Debug::Filter* gLogRelayoutFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RELAYOUT_TIMER");
36 * @brief Extract a given dimension from a Vector2
38 * @param[in] values The values to extract from
39 * @param[in] dimension The dimension to extract
40 * @return Return the value for the dimension
42 constexpr float GetDimensionValue(const Dali::Vector2& values, const Dali::Dimension::Type dimension)
46 case Dali::Dimension::WIDTH:
50 case Dali::Dimension::HEIGHT:
62 } // unnamed namespace
68 Actor::Relayouter::Relayouter()
69 : sizeModeFactor(DEFAULT_SIZE_MODE_FACTOR),
70 preferredSize(DEFAULT_PREFERRED_SIZE),
71 sizeSetPolicy(DEFAULT_SIZE_SCALE_POLICY),
72 relayoutEnabled(false),
73 insideRelayout(false),
74 relayoutRequested(false)
76 // Set size negotiation defaults
77 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
79 resizePolicies[i] = ResizePolicy::DEFAULT;
80 useAssignedSize[i] = false;
81 negotiatedDimensions[i] = 0.0f;
82 dimensionNegotiated[i] = false;
83 dimensionDirty[i] = false;
84 dimensionDependencies[i] = Dimension::ALL_DIMENSIONS;
85 dimensionPadding[i] = DEFAULT_DIMENSION_PADDING;
86 minimumSize[i] = 0.0f;
87 maximumSize[i] = FLT_MAX;
91 ResizePolicy::Type Actor::Relayouter::GetResizePolicy(Dimension::Type dimension) const
93 // If more than one dimension is requested, just return the first one found
94 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
96 if((dimension & (1 << i)))
98 if(useAssignedSize[i])
100 return ResizePolicy::USE_ASSIGNED_SIZE;
104 return resizePolicies[i];
109 return ResizePolicy::DEFAULT;
112 void Actor::Relayouter::SetPadding(const Vector2& padding, Dimension::Type dimension)
114 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
116 if(dimension & (1 << i))
118 dimensionPadding[i] = padding;
123 Vector2 Actor::Relayouter::GetPadding(Dimension::Type dimension)
125 // If more than one dimension is requested, just return the first one found
126 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
128 if((dimension & (1 << i)))
130 return dimensionPadding[i];
134 return DEFAULT_DIMENSION_PADDING;
137 void Actor::Relayouter::SetLayoutNegotiated(bool negotiated, Dimension::Type dimension)
139 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
141 if(dimension & (1 << i))
143 dimensionNegotiated[i] = negotiated;
148 bool Actor::Relayouter::IsLayoutNegotiated(Dimension::Type dimension) const
150 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
152 if((dimension & (1 << i)) && dimensionNegotiated[i])
160 Vector2 Actor::Relayouter::ApplySizeSetPolicy(Internal::Actor& actor, const Vector2& size)
162 switch(sizeSetPolicy)
164 case SizeScalePolicy::USE_SIZE_SET:
169 case SizeScalePolicy::FIT_WITH_ASPECT_RATIO:
171 // Scale size to fit within the original size bounds, keeping the natural size aspect ratio
172 const Vector3 naturalSize = actor.GetNaturalSize();
173 if(naturalSize.width > 0.0f && naturalSize.height > 0.0f && size.width > 0.0f && size.height > 0.0f)
175 const float sizeRatio = size.width / size.height;
176 const float naturalSizeRatio = naturalSize.width / naturalSize.height;
178 if(naturalSizeRatio < sizeRatio)
180 return Vector2(naturalSizeRatio * size.height, size.height);
182 else if(naturalSizeRatio > sizeRatio)
184 return Vector2(size.width, size.width / naturalSizeRatio);
195 case SizeScalePolicy::FILL_WITH_ASPECT_RATIO:
197 // Scale size to fill the original size bounds, keeping the natural size aspect ratio. Potentially exceeding the original bounds.
198 const Vector3 naturalSize = actor.GetNaturalSize();
199 if(naturalSize.width > 0.0f && naturalSize.height > 0.0f && size.width > 0.0f && size.height > 0.0f)
201 const float sizeRatio = size.width / size.height;
202 const float naturalSizeRatio = naturalSize.width / naturalSize.height;
204 if(naturalSizeRatio < sizeRatio)
206 return Vector2(size.width, size.width / naturalSizeRatio);
208 else if(naturalSizeRatio > sizeRatio)
210 return Vector2(naturalSizeRatio * size.height, size.height);
229 void Actor::Relayouter::SetUseAssignedSize(bool use, Dimension::Type dimension)
231 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
233 if(dimension & (1 << i))
235 useAssignedSize[i] = use;
240 bool Actor::Relayouter::GetUseAssignedSize(Dimension::Type dimension) const
242 // If more than one dimension is requested, just return the first one found
243 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
245 if(dimension & (1 << i))
247 return useAssignedSize[i];
254 void Actor::Relayouter::SetMinimumSize(float size, Dimension::Type dimension)
256 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
258 if(dimension & (1 << i))
260 minimumSize[i] = size;
265 float Actor::Relayouter::GetMinimumSize(Dimension::Type dimension) const
267 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
269 if(dimension & (1 << i))
271 return minimumSize[i];
275 return 0.0f; // Default
278 void Actor::Relayouter::SetMaximumSize(float size, Dimension::Type dimension)
280 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
282 if(dimension & (1 << i))
284 maximumSize[i] = size;
289 float Actor::Relayouter::GetMaximumSize(Dimension::Type dimension) const
291 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
293 if(dimension & (1 << i))
295 return maximumSize[i];
299 return FLT_MAX; // Default
302 void Actor::Relayouter::SetResizePolicy(ResizePolicy::Type policy, Dimension::Type dimension, Vector3& targetSize)
304 ResizePolicy::Type originalWidthPolicy = GetResizePolicy(Dimension::WIDTH);
305 ResizePolicy::Type originalHeightPolicy = GetResizePolicy(Dimension::HEIGHT);
307 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
309 if(dimension & (1 << i))
311 if(policy == ResizePolicy::USE_ASSIGNED_SIZE)
313 useAssignedSize[i] = true;
317 resizePolicies[i] = policy;
318 useAssignedSize[i] = false;
323 if(policy == ResizePolicy::DIMENSION_DEPENDENCY)
325 if(dimension & Dimension::WIDTH)
327 SetDimensionDependency(Dimension::WIDTH, Dimension::HEIGHT);
330 if(dimension & Dimension::HEIGHT)
332 SetDimensionDependency(Dimension::HEIGHT, Dimension::WIDTH);
336 // If calling SetResizePolicy, assume we want relayout enabled
337 relayoutEnabled = true;
339 // If the resize policy is set to be FIXED, the preferred size
340 // should be overrided by the target size. Otherwise the target
341 // size should be overrided by the preferred size.
343 if(dimension & Dimension::WIDTH)
345 if(originalWidthPolicy != ResizePolicy::FIXED && policy == ResizePolicy::FIXED)
347 preferredSize.width = targetSize.width;
349 else if(originalWidthPolicy == ResizePolicy::FIXED && policy != ResizePolicy::FIXED)
351 targetSize.width = preferredSize.width;
355 if(dimension & Dimension::HEIGHT)
357 if(originalHeightPolicy != ResizePolicy::FIXED && policy == ResizePolicy::FIXED)
359 preferredSize.height = targetSize.height;
361 else if(originalHeightPolicy == ResizePolicy::FIXED && policy != ResizePolicy::FIXED)
363 targetSize.height = preferredSize.height;
368 bool Actor::Relayouter::GetRelayoutDependentOnParent(Dimension::Type dimension)
370 // Check if actor is dependent on parent
371 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
373 if((dimension & (1 << i)))
375 const ResizePolicy::Type resizePolicy = GetResizePolicy(static_cast<Dimension::Type>(1 << i));
376 if(resizePolicy == ResizePolicy::FILL_TO_PARENT || resizePolicy == ResizePolicy::SIZE_RELATIVE_TO_PARENT || resizePolicy == ResizePolicy::SIZE_FIXED_OFFSET_FROM_PARENT)
385 bool Actor::Relayouter::GetRelayoutDependentOnChildren(Dimension::Type dimension)
387 // Check if actor is dependent on children
388 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
390 if((dimension & (1 << i)))
392 const ResizePolicy::Type resizePolicy = GetResizePolicy(static_cast<Dimension::Type>(1 << i));
393 if(resizePolicy == ResizePolicy::FIT_TO_CHILDREN || resizePolicy == ResizePolicy::USE_NATURAL_SIZE)
404 bool Actor::Relayouter::GetRelayoutDependentOnDimension(Dimension::Type dimension, Dimension::Type dependency)
406 // Check each possible dimension and see if it is dependent on the input one
407 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
409 if(dimension & (1 << i))
411 return resizePolicies[i] == ResizePolicy::DIMENSION_DEPENDENCY && dimensionDependencies[i] == dependency;
418 void Actor::Relayouter::SetDimensionDependency(Dimension::Type dimension, Dimension::Type dependency)
420 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
422 if(dimension & (1 << i))
424 dimensionDependencies[i] = dependency;
429 Dimension::Type Actor::Relayouter::GetDimensionDependency(Dimension::Type dimension) const
431 // If more than one dimension is requested, just return the first one found
432 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
434 if((dimension & (1 << i)))
436 return dimensionDependencies[i];
440 return Dimension::ALL_DIMENSIONS; // Default
443 void Actor::Relayouter::SetLayoutDirty(bool dirty, Dimension::Type dimension)
445 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
447 if(dimension & (1 << i))
449 dimensionDirty[i] = dirty;
454 bool Actor::Relayouter::IsLayoutDirty(Dimension::Type dimension) const
456 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
458 if((dimension & (1 << i)) && dimensionDirty[i])
467 void Actor::Relayouter::SetPreferredSize(Actor& actor, const Vector2& size)
469 // If valid width or height, then set the resize policy to FIXED
470 // A 0 width or height may also be required so if the resize policy has not been changed, i.e. is still set to DEFAULT,
471 // then change to FIXED as well
473 if(size.width > 0.0f || GetResizePolicy(Dimension::WIDTH) == ResizePolicy::DEFAULT)
475 actor.SetResizePolicy(ResizePolicy::FIXED, Dimension::WIDTH);
478 if(size.height > 0.0f || GetResizePolicy(Dimension::HEIGHT) == ResizePolicy::DEFAULT)
480 actor.SetResizePolicy(ResizePolicy::FIXED, Dimension::HEIGHT);
483 actor.mRelayoutData->preferredSize = size;
485 actor.mUseAnimatedSize = AnimatedSizeFlag::CLEAR;
487 actor.RelayoutRequest();
490 float Actor::Relayouter::ClampDimension(const Internal::Actor& actor, float size, Dimension::Type dimension)
492 const float minSize = actor.GetMinimumSize(dimension);
493 const float maxSize = actor.GetMaximumSize(dimension);
495 return std::max(minSize, std::min(size, maxSize));
498 void Actor::Relayouter::SetNegotiatedDimension(float negotiatedDimension, Dimension::Type dimension)
500 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
502 if(dimension & (1 << i))
504 negotiatedDimensions[i] = negotiatedDimension;
509 float Actor::Relayouter::GetNegotiatedDimension(Dimension::Type dimension)
511 // If more than one dimension is requested, just return the first one found
512 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
514 if((dimension & (1 << i)))
516 return negotiatedDimensions[i];
520 return 0.0f; // Default
523 float Actor::Relayouter::NegotiateDimensionFromParent(Actor& actor, Dimension::Type dimension)
525 Actor* parent = actor.GetParent();
528 Vector2 padding(actor.GetPadding(dimension));
529 Vector2 parentPadding(parent->GetPadding(dimension));
531 // Need to use actor API here to allow deriving actors to layout their children
532 return parent->CalculateChildSize(Dali::Actor(&actor), dimension) - parentPadding.x - parentPadding.y - padding.x - padding.y;
538 float Actor::Relayouter::NegotiateDimensionFromChildren(Actor& actor, Dimension::Type dimension)
540 float maxDimensionPoint = 0.0f;
542 for(uint32_t i = 0, count = actor.GetChildCount(); i < count; ++i)
544 ActorPtr child = actor.GetChildAt(i);
546 if(!child->RelayoutDependentOnParent(dimension))
548 // Calculate the min and max points that the children range across
549 float childPosition = GetDimensionValue(child->GetTargetPosition(), dimension);
550 float dimensionSize = child->GetRelayoutSize(dimension);
551 maxDimensionPoint = std::max(maxDimensionPoint, childPosition + dimensionSize);
555 return maxDimensionPoint;
558 void Actor::Relayouter::NegotiateDimension(Actor& actor, Dimension::Type dimension, const Vector2& allocatedSize, Actor::ActorDimensionStack& recursionStack)
560 // Check if it needs to be negotiated
561 if(actor.IsLayoutDirty(dimension) && !actor.IsLayoutNegotiated(dimension))
563 // Check that we havn't gotten into an infinite loop
564 Actor::ActorDimensionPair searchActor = Actor::ActorDimensionPair(&actor, dimension);
565 bool recursionFound = false;
566 for(auto& element : recursionStack)
568 if(element == searchActor)
570 recursionFound = true;
577 // Record the path that we have taken
578 recursionStack.push_back(Actor::ActorDimensionPair(&actor, dimension));
580 // Dimension dependency check
581 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
583 Dimension::Type dimensionToCheck = static_cast<Dimension::Type>(1 << i);
585 if(actor.RelayoutDependentOnDimension(dimension, dimensionToCheck))
587 NegotiateDimension(actor, dimensionToCheck, allocatedSize, recursionStack);
591 // Parent dependency check
592 Actor* parent = actor.GetParent();
593 if(parent && actor.RelayoutDependentOnParent(dimension))
595 NegotiateDimension(*parent, dimension, allocatedSize, recursionStack);
598 // Children dependency check
599 if(actor.RelayoutDependentOnChildren(dimension))
601 for(uint32_t i = 0, count = actor.GetChildCount(); i < count; ++i)
603 ActorPtr child = actor.GetChildAt(i);
605 // Only relayout child first if it is not dependent on this actor
606 if(!child->RelayoutDependentOnParent(dimension))
608 NegotiateDimension(*child, dimension, allocatedSize, recursionStack);
613 // For deriving classes
614 actor.OnCalculateRelayoutSize(dimension);
616 // All dependencies checked, calculate the size and set negotiated flag
617 const float newSize = ClampDimension(actor, actor.CalculateSize(dimension, allocatedSize), dimension);
619 actor.SetNegotiatedDimension(newSize, dimension);
620 actor.SetLayoutNegotiated(true, dimension);
622 // For deriving classes
623 actor.OnLayoutNegotiated(newSize, dimension);
625 // This actor has been successfully processed, pop it off the recursion stack
626 recursionStack.pop_back();
630 // TODO: Break infinite loop
631 actor.SetLayoutNegotiated(true, dimension);
636 void Actor::Relayouter::NegotiateDimensions(Actor& actor, const Vector2& allocatedSize)
638 // Negotiate all dimensions that require it
639 ActorDimensionStack recursionStack;
641 for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
643 const Dimension::Type dimension = static_cast<Dimension::Type>(1 << i);
646 NegotiateDimension(actor, dimension, allocatedSize, recursionStack);
650 void Actor::Relayouter::NegotiateSize(Actor& actor, const Vector2& allocatedSize, RelayoutContainer& container)
652 // Force a size negotiation for actors that has assigned size during relayout
653 // This is required as otherwise the flags that force a relayout will not
654 // necessarilly be set. This will occur if the actor has already been laid out.
655 // The dirty flags are then cleared. Then if the actor is added back into the
656 // relayout container afterwards, the dirty flags would still be clear...
657 // causing a relayout to be skipped. Here we force any actors added to the
658 // container to be relayed out.
659 DALI_LOG_TIMER_START(NegSizeTimer1);
661 if(actor.GetUseAssignedSize(Dimension::WIDTH))
663 actor.SetLayoutNegotiated(false, Dimension::WIDTH);
665 if(actor.GetUseAssignedSize(Dimension::HEIGHT))
667 actor.SetLayoutNegotiated(false, Dimension::HEIGHT);
670 // Do the negotiation
671 NegotiateDimensions(actor, allocatedSize);
673 // Set the actor size
674 actor.SetNegotiatedSize(container);
676 // Negotiate down to children
677 for(uint32_t i = 0, count = actor.GetChildCount(); i < count; ++i)
679 ActorPtr child = actor.GetChildAt(i);
681 // Forces children that have already been laid out to be relayed out
682 // if they have assigned size during relayout.
683 if(child->GetUseAssignedSize(Dimension::WIDTH))
685 child->SetLayoutNegotiated(false, Dimension::WIDTH);
686 child->SetLayoutDirty(true, Dimension::WIDTH);
689 if(child->GetUseAssignedSize(Dimension::HEIGHT))
691 child->SetLayoutNegotiated(false, Dimension::HEIGHT);
692 child->SetLayoutDirty(true, Dimension::HEIGHT);
695 // Only relayout if required
696 if(child->RelayoutRequired())
698 container.Add(Dali::Actor(child.Get()), actor.mTargetSize.GetVectorXY());
701 DALI_LOG_TIMER_END(NegSizeTimer1, gLogRelayoutFilter, Debug::Concise, "NegotiateSize() took: ");
705 * @brief Extract a given dimension from a Vector3
707 * @param[in] values The values to extract from
708 * @param[in] dimension The dimension to extract
709 * @return Return the value for the dimension
711 float Actor::Relayouter::GetDimensionValue(const Vector3& values, const Dimension::Type dimension)
713 return ::GetDimensionValue(values.GetVectorXY(), dimension);
716 float Actor::Relayouter::CalculateSize(Actor& actor, Dimension::Type dimension, const Vector2& maximumSize)
718 switch(actor.GetResizePolicy(dimension))
720 case ResizePolicy::USE_NATURAL_SIZE:
722 return actor.GetNaturalSize(dimension);
725 case ResizePolicy::FIXED:
727 return ::GetDimensionValue(actor.GetPreferredSize(), dimension);
730 case ResizePolicy::USE_ASSIGNED_SIZE:
732 return ::GetDimensionValue(maximumSize, dimension);
735 case ResizePolicy::FILL_TO_PARENT:
736 case ResizePolicy::SIZE_RELATIVE_TO_PARENT:
737 case ResizePolicy::SIZE_FIXED_OFFSET_FROM_PARENT:
739 return NegotiateDimensionFromParent(actor, dimension);
742 case ResizePolicy::FIT_TO_CHILDREN:
744 return NegotiateDimensionFromChildren(actor, dimension);
747 case ResizePolicy::DIMENSION_DEPENDENCY:
749 const Dimension::Type dimensionDependency = actor.GetDimensionDependency(dimension);
752 if(dimension == Dimension::WIDTH && dimensionDependency == Dimension::HEIGHT)
754 return actor.GetWidthForHeight(actor.GetNegotiatedDimension(Dimension::HEIGHT));
757 if(dimension == Dimension::HEIGHT && dimensionDependency == Dimension::WIDTH)
759 return actor.GetHeightForWidth(actor.GetNegotiatedDimension(Dimension::WIDTH));
771 return 0.0f; // Default
774 float Actor::Relayouter::CalculateChildSize(Actor& actor, const Actor& child, Dimension::Type dimension)
776 // Fill to parent, taking size mode factor into account
777 switch(child.GetResizePolicy(dimension))
779 case ResizePolicy::FILL_TO_PARENT:
781 return actor.GetLatestSize(dimension);
784 case ResizePolicy::SIZE_RELATIVE_TO_PARENT:
786 Property::Value value = child.GetProperty(Dali::Actor::Property::SIZE_MODE_FACTOR);
787 Vector3 childSizeModeFactor = value.Get<Vector3>();
788 return actor.GetLatestSize(dimension) * GetDimensionValue(childSizeModeFactor, dimension);
791 case ResizePolicy::SIZE_FIXED_OFFSET_FROM_PARENT:
793 Property::Value value = child.GetProperty(Dali::Actor::Property::SIZE_MODE_FACTOR);
794 Vector3 childSizeModeFactor = value.Get<Vector3>();
795 return actor.GetLatestSize(dimension) + GetDimensionValue(childSizeModeFactor, dimension);
800 return actor.GetLatestSize(dimension);
805 } // namespace Internal