8cde6d50a2342d172efee5f3f2a20dd901b69b0c
[platform/core/uifw/dali-core.git] / dali / internal / event / actors / actor-relayouter.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/event/actors/actor-relayouter.h>
20
21 // INTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <dali/internal/event/size-negotiation/relayout-controller-impl.h>
24 #include <dali/public-api/math/vector2.h>
25 #include <dali/public-api/math/vector3.h>
26
27 namespace
28 {
29 #if defined(DEBUG_ENABLED)
30 Debug::Filter* gLogRelayoutFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_RELAYOUT_TIMER");
31 #endif
32 } // unnamed namespace
33
34 namespace Dali
35 {
36 namespace Internal
37 {
38 Actor::Relayouter::Relayouter()
39 : sizeModeFactor(DEFAULT_SIZE_MODE_FACTOR),
40   preferredSize(DEFAULT_PREFERRED_SIZE),
41   sizeSetPolicy(DEFAULT_SIZE_SCALE_POLICY),
42   relayoutEnabled(false),
43   insideRelayout(false)
44 {
45   // Set size negotiation defaults
46   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
47   {
48     resizePolicies[i]        = ResizePolicy::DEFAULT;
49     useAssignedSize[i]       = false;
50     negotiatedDimensions[i]  = 0.0f;
51     dimensionNegotiated[i]   = false;
52     dimensionDirty[i]        = false;
53     dimensionDependencies[i] = Dimension::ALL_DIMENSIONS;
54     dimensionPadding[i]      = DEFAULT_DIMENSION_PADDING;
55     minimumSize[i]           = 0.0f;
56     maximumSize[i]           = FLT_MAX;
57   }
58 }
59
60 ResizePolicy::Type Actor::Relayouter::GetResizePolicy(Dimension::Type dimension) const
61 {
62   // If more than one dimension is requested, just return the first one found
63   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
64   {
65     if((dimension & (1 << i)))
66     {
67       if(useAssignedSize[i])
68       {
69         return ResizePolicy::USE_ASSIGNED_SIZE;
70       }
71       else
72       {
73         return resizePolicies[i];
74       }
75     }
76   }
77
78   return ResizePolicy::DEFAULT;
79 }
80
81 void Actor::Relayouter::SetPadding(const Vector2& padding, Dimension::Type dimension)
82 {
83   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
84   {
85     if(dimension & (1 << i))
86     {
87       dimensionPadding[i] = padding;
88     }
89   }
90 }
91
92 void Actor::Relayouter::SetLayoutNegotiated(bool negotiated, Dimension::Type dimension)
93 {
94   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
95   {
96     if(dimension & (1 << i))
97     {
98       dimensionNegotiated[i] = negotiated;
99     }
100   }
101 }
102
103 bool Actor::Relayouter::IsLayoutNegotiated(Dimension::Type dimension) const
104 {
105   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
106   {
107     if((dimension & (1 << i)) && dimensionNegotiated[i])
108     {
109       return true;
110     }
111   }
112   return false;
113 }
114
115 Vector2 Actor::Relayouter::ApplySizeSetPolicy(Internal::Actor& actor, const Vector2& size)
116 {
117   switch(sizeSetPolicy)
118   {
119     case SizeScalePolicy::USE_SIZE_SET:
120     {
121       return size;
122     }
123
124     case SizeScalePolicy::FIT_WITH_ASPECT_RATIO:
125     {
126       // Scale size to fit within the original size bounds, keeping the natural size aspect ratio
127       const Vector3 naturalSize = actor.GetNaturalSize();
128       if(naturalSize.width > 0.0f && naturalSize.height > 0.0f && size.width > 0.0f && size.height > 0.0f)
129       {
130         const float sizeRatio        = size.width / size.height;
131         const float naturalSizeRatio = naturalSize.width / naturalSize.height;
132
133         if(naturalSizeRatio < sizeRatio)
134         {
135           return Vector2(naturalSizeRatio * size.height, size.height);
136         }
137         else if(naturalSizeRatio > sizeRatio)
138         {
139           return Vector2(size.width, size.width / naturalSizeRatio);
140         }
141         else
142         {
143           return size;
144         }
145       }
146
147       break;
148     }
149
150     case SizeScalePolicy::FILL_WITH_ASPECT_RATIO:
151     {
152       // Scale size to fill the original size bounds, keeping the natural size aspect ratio. Potentially exceeding the original bounds.
153       const Vector3 naturalSize = actor.GetNaturalSize();
154       if(naturalSize.width > 0.0f && naturalSize.height > 0.0f && size.width > 0.0f && size.height > 0.0f)
155       {
156         const float sizeRatio        = size.width / size.height;
157         const float naturalSizeRatio = naturalSize.width / naturalSize.height;
158
159         if(naturalSizeRatio < sizeRatio)
160         {
161           return Vector2(size.width, size.width / naturalSizeRatio);
162         }
163         else if(naturalSizeRatio > sizeRatio)
164         {
165           return Vector2(naturalSizeRatio * size.height, size.height);
166         }
167         else
168         {
169           return size;
170         }
171       }
172       break;
173     }
174
175     default:
176     {
177       break;
178     }
179   }
180
181   return size;
182 }
183
184 void Actor::Relayouter::SetUseAssignedSize(bool use, Dimension::Type dimension)
185 {
186   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
187   {
188     if(dimension & (1 << i))
189     {
190       useAssignedSize[i] = use;
191     }
192   }
193 }
194
195 bool Actor::Relayouter::GetUseAssignedSize(Dimension::Type dimension) const
196 {
197   // If more than one dimension is requested, just return the first one found
198   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
199   {
200     if(dimension & (1 << i))
201     {
202       return useAssignedSize[i];
203     }
204   }
205
206   return false;
207 }
208
209 void Actor::Relayouter::SetMinimumSize(float size, Dimension::Type dimension)
210 {
211   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
212   {
213     if(dimension & (1 << i))
214     {
215       minimumSize[i] = size;
216     }
217   }
218 }
219
220 float Actor::Relayouter::GetMinimumSize(Dimension::Type dimension) const
221 {
222   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
223   {
224     if(dimension & (1 << i))
225     {
226       return minimumSize[i];
227     }
228   }
229
230   return 0.0f; // Default
231 }
232
233 void Actor::Relayouter::SetMaximumSize(float size, Dimension::Type dimension)
234 {
235   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
236   {
237     if(dimension & (1 << i))
238     {
239       maximumSize[i] = size;
240     }
241   }
242 }
243
244 float Actor::Relayouter::GetMaximumSize(Dimension::Type dimension) const
245 {
246   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
247   {
248     if(dimension & (1 << i))
249     {
250       return maximumSize[i];
251     }
252   }
253
254   return FLT_MAX; // Default
255 }
256
257 void Actor::Relayouter::SetResizePolicy(ResizePolicy::Type policy, Dimension::Type dimension, Vector3& targetSize)
258 {
259   ResizePolicy::Type originalWidthPolicy  = GetResizePolicy(Dimension::WIDTH);
260   ResizePolicy::Type originalHeightPolicy = GetResizePolicy(Dimension::HEIGHT);
261
262   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
263   {
264     if(dimension & (1 << i))
265     {
266       if(policy == ResizePolicy::USE_ASSIGNED_SIZE)
267       {
268         useAssignedSize[i] = true;
269       }
270       else
271       {
272         resizePolicies[i]  = policy;
273         useAssignedSize[i] = false;
274       }
275     }
276   }
277
278   if(policy == ResizePolicy::DIMENSION_DEPENDENCY)
279   {
280     if(dimension & Dimension::WIDTH)
281     {
282       SetDimensionDependency(Dimension::WIDTH, Dimension::HEIGHT);
283     }
284
285     if(dimension & Dimension::HEIGHT)
286     {
287       SetDimensionDependency(Dimension::HEIGHT, Dimension::WIDTH);
288     }
289   }
290
291   // If calling SetResizePolicy, assume we want relayout enabled
292   relayoutEnabled = true;
293
294   // If the resize policy is set to be FIXED, the preferred size
295   // should be overrided by the target size. Otherwise the target
296   // size should be overrided by the preferred size.
297
298   if(dimension & Dimension::WIDTH)
299   {
300     if(originalWidthPolicy != ResizePolicy::FIXED && policy == ResizePolicy::FIXED)
301     {
302       preferredSize.width = targetSize.width;
303     }
304     else if(originalWidthPolicy == ResizePolicy::FIXED && policy != ResizePolicy::FIXED)
305     {
306       targetSize.width = preferredSize.width;
307     }
308   }
309
310   if(dimension & Dimension::HEIGHT)
311   {
312     if(originalHeightPolicy != ResizePolicy::FIXED && policy == ResizePolicy::FIXED)
313     {
314       preferredSize.height = targetSize.height;
315     }
316     else if(originalHeightPolicy == ResizePolicy::FIXED && policy != ResizePolicy::FIXED)
317     {
318       targetSize.height = preferredSize.height;
319     }
320   }
321 }
322
323 void Actor::Relayouter::SetDimensionDependency(Dimension::Type dimension, Dimension::Type dependency)
324 {
325   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
326   {
327     if(dimension & (1 << i))
328     {
329       dimensionDependencies[i] = dependency;
330     }
331   }
332 }
333
334 Dimension::Type Actor::Relayouter::GetDimensionDependency(Dimension::Type dimension) const
335 {
336   // If more than one dimension is requested, just return the first one found
337   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
338   {
339     if((dimension & (1 << i)))
340     {
341       return dimensionDependencies[i];
342     }
343   }
344
345   return Dimension::ALL_DIMENSIONS; // Default
346 }
347
348 void Actor::Relayouter::SetLayoutDirty(bool dirty, Dimension::Type dimension)
349 {
350   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
351   {
352     if(dimension & (1 << i))
353     {
354       dimensionDirty[i] = dirty;
355     }
356   }
357 }
358
359 bool Actor::Relayouter::IsLayoutDirty(Dimension::Type dimension) const
360 {
361   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
362   {
363     if((dimension & (1 << i)) && dimensionDirty[i])
364     {
365       return true;
366     }
367   }
368
369   return false;
370 }
371
372 void Actor::Relayouter::SetPreferredSize(Actor& actor, const Vector2& size)
373 {
374   // If valid width or height, then set the resize policy to FIXED
375   // 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,
376   // then change to FIXED as well
377
378   if(size.width > 0.0f || GetResizePolicy(Dimension::WIDTH) == ResizePolicy::DEFAULT)
379   {
380     actor.SetResizePolicy(ResizePolicy::FIXED, Dimension::WIDTH);
381   }
382
383   if(size.height > 0.0f || GetResizePolicy(Dimension::HEIGHT) == ResizePolicy::DEFAULT)
384   {
385     actor.SetResizePolicy(ResizePolicy::FIXED, Dimension::HEIGHT);
386   }
387
388   actor.mRelayoutData->preferredSize = size;
389
390   actor.mUseAnimatedSize = AnimatedSizeFlag::CLEAR;
391
392   actor.RelayoutRequest();
393 }
394
395 float Actor::Relayouter::ClampDimension(const Internal::Actor& actor, float size, Dimension::Type dimension)
396 {
397   const float minSize = actor.GetMinimumSize(dimension);
398   const float maxSize = actor.GetMaximumSize(dimension);
399
400   return std::max(minSize, std::min(size, maxSize));
401 }
402
403 void Actor::Relayouter::NegotiateDimension(Actor& actor, Dimension::Type dimension, const Vector2& allocatedSize, Actor::ActorDimensionStack& recursionStack)
404 {
405   // Check if it needs to be negotiated
406   if(actor.IsLayoutDirty(dimension) && !actor.IsLayoutNegotiated(dimension))
407   {
408     // Check that we havn't gotten into an infinite loop
409     Actor::ActorDimensionPair searchActor    = Actor::ActorDimensionPair(&actor, dimension);
410     bool                      recursionFound = false;
411     for(auto& element : recursionStack)
412     {
413       if(element == searchActor)
414       {
415         recursionFound = true;
416         break;
417       }
418     }
419
420     if(!recursionFound)
421     {
422       // Record the path that we have taken
423       recursionStack.push_back(Actor::ActorDimensionPair(&actor, dimension));
424
425       // Dimension dependency check
426       for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
427       {
428         Dimension::Type dimensionToCheck = static_cast<Dimension::Type>(1 << i);
429
430         if(actor.RelayoutDependentOnDimension(dimension, dimensionToCheck))
431         {
432           NegotiateDimension(actor, dimensionToCheck, allocatedSize, recursionStack);
433         }
434       }
435
436       // Parent dependency check
437       Actor* parent = actor.GetParent();
438       if(parent && actor.RelayoutDependentOnParent(dimension))
439       {
440         NegotiateDimension(*parent, dimension, allocatedSize, recursionStack);
441       }
442
443       // Children dependency check
444       if(actor.RelayoutDependentOnChildren(dimension))
445       {
446         for(uint32_t i = 0, count = actor.GetChildCount(); i < count; ++i)
447         {
448           ActorPtr child = actor.GetChildAt(i);
449
450           // Only relayout child first if it is not dependent on this actor
451           if(!child->RelayoutDependentOnParent(dimension))
452           {
453             NegotiateDimension(*child, dimension, allocatedSize, recursionStack);
454           }
455         }
456       }
457
458       // For deriving classes
459       actor.OnCalculateRelayoutSize(dimension);
460
461       // All dependencies checked, calculate the size and set negotiated flag
462       const float newSize = ClampDimension(actor, actor.CalculateSize(dimension, allocatedSize), dimension);
463
464       actor.SetNegotiatedDimension(newSize, dimension);
465       actor.SetLayoutNegotiated(true, dimension);
466
467       // For deriving classes
468       actor.OnLayoutNegotiated(newSize, dimension);
469
470       // This actor has been successfully processed, pop it off the recursion stack
471       recursionStack.pop_back();
472     }
473     else
474     {
475       // TODO: Break infinite loop
476       actor.SetLayoutNegotiated(true, dimension);
477     }
478   }
479 }
480
481 void Actor::Relayouter::NegotiateDimensions(Actor& actor, const Vector2& allocatedSize)
482 {
483   // Negotiate all dimensions that require it
484   ActorDimensionStack recursionStack;
485
486   for(uint32_t i = 0; i < Dimension::DIMENSION_COUNT; ++i)
487   {
488     const Dimension::Type dimension = static_cast<Dimension::Type>(1 << i);
489
490     // Negotiate
491     NegotiateDimension(actor, dimension, allocatedSize, recursionStack);
492   }
493 }
494
495 void Actor::Relayouter::NegotiateSize(Actor& actor, const Vector2& allocatedSize, RelayoutContainer& container)
496 {
497   // Force a size negotiation for actors that has assigned size during relayout
498   // This is required as otherwise the flags that force a relayout will not
499   // necessarilly be set. This will occur if the actor has already been laid out.
500   // The dirty flags are then cleared. Then if the actor is added back into the
501   // relayout container afterwards, the dirty flags would still be clear...
502   // causing a relayout to be skipped. Here we force any actors added to the
503   // container to be relayed out.
504   DALI_LOG_TIMER_START(NegSizeTimer1);
505
506   if(actor.GetUseAssignedSize(Dimension::WIDTH))
507   {
508     actor.SetLayoutNegotiated(false, Dimension::WIDTH);
509   }
510   if(actor.GetUseAssignedSize(Dimension::HEIGHT))
511   {
512     actor.SetLayoutNegotiated(false, Dimension::HEIGHT);
513   }
514
515   // Do the negotiation
516   NegotiateDimensions(actor, allocatedSize);
517
518   // Set the actor size
519   actor.SetNegotiatedSize(container);
520
521   // Negotiate down to children
522   for(uint32_t i = 0, count = actor.GetChildCount(); i < count; ++i)
523   {
524     ActorPtr child = actor.GetChildAt(i);
525
526     // Forces children that have already been laid out to be relayed out
527     // if they have assigned size during relayout.
528     if(child->GetUseAssignedSize(Dimension::WIDTH))
529     {
530       child->SetLayoutNegotiated(false, Dimension::WIDTH);
531       child->SetLayoutDirty(true, Dimension::WIDTH);
532     }
533
534     if(child->GetUseAssignedSize(Dimension::HEIGHT))
535     {
536       child->SetLayoutNegotiated(false, Dimension::HEIGHT);
537       child->SetLayoutDirty(true, Dimension::HEIGHT);
538     }
539
540     // Only relayout if required
541     if(child->RelayoutRequired())
542     {
543       container.Add(Dali::Actor(child.Get()), actor.mTargetSize.GetVectorXY());
544     }
545   }
546   DALI_LOG_TIMER_END(NegSizeTimer1, gLogRelayoutFilter, Debug::Concise, "NegotiateSize() took: ");
547 }
548
549 } // namespace Internal
550
551 } // namespace Dali