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