b6574c7e47a03cc2a9ffcc914bd7be8a2545ed7b
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Layouting / LayoutController.cs
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 using Tizen.NUI.BaseComponents;
19 using System.Collections.Generic;
20 using System;
21 using System.ComponentModel;
22
23 namespace Tizen.NUI
24 {
25     /// <summary>
26     /// [Draft] The class that initiates the Measuring and Layouting of the tree,
27     ///         It sets a callback that becomes the entry point into the C# Layouting.
28     /// </summary>
29     internal class LayoutController : Disposable
30     {
31         private static int layoutControllerID = 1;
32
33         private int id;
34         private Window window;
35         private Animation coreAnimation;
36         private List<LayoutData> layoutTransitionDataQueue;
37         private List<LayoutItem> itemRemovalQueue;
38         private float windowWidth;
39         private float windowHeight;
40
41         private bool subscribed;
42
43         /// <summary>
44         /// Constructs a LayoutController which controls the measuring and layouting.<br />
45         /// <param name="window">Window attach this LayoutController to.</param>
46         /// </summary>
47         public LayoutController(Window window)
48         {
49             this.window = window;
50             this.window.Resized += OnWindowResized;
51             var windowSize = window.GetSize();
52             windowWidth = windowSize.Width;
53             windowHeight = windowSize.Height;
54
55             layoutTransitionDataQueue = new List<LayoutData>();
56             id = layoutControllerID++;
57
58             windowSize.Dispose();
59             windowSize = null;
60         }
61
62         private void OnWindowResized(object sender, Window.ResizedEventArgs e)
63         {
64             windowWidth = e.WindowSize.Width;
65             windowHeight = e.WindowSize.Height;
66         }
67
68         /// <summary>
69         /// Set or Get Layouting core animation override property.
70         /// Gives explicit control over the Layouting animation playback if set to True.
71         /// Reset to False if explicit control no longer required.
72         /// </summary>
73         [EditorBrowsable(EditorBrowsableState.Never)]
74         public bool OverrideCoreAnimation { get; set; } = false;
75
76         /// <summary>
77         /// Get the unique id of the LayoutController
78         /// </summary>
79         public int GetId()
80         {
81             return id;
82         }
83
84         /// <summary>
85         /// Request relayout of a LayoutItem and it's parent tree.
86         /// </summary>
87         /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
88         public void RequestLayout(LayoutItem layoutItem)
89         {
90             // Go up the tree and mark all parents to relayout
91             ILayoutParent layoutParent = layoutItem.GetParent();
92             if (layoutParent != null)
93             {
94                 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
95                 if (layoutGroup != null && !layoutGroup.LayoutRequested)
96                 {
97                     layoutGroup.RequestLayout();
98                 }
99             }
100         }
101
102
103         /// <summary>
104         /// Entry point into the C# Layouting that starts the Processing
105         /// </summary>
106         public void Process(object source, EventArgs e)
107         {
108             window.LayersChildren?.ForEach(layer =>
109             {
110                 layer?.Children?.ForEach(view =>
111                 {
112                     if (view != null)
113                     {
114                         FindRootLayouts(view, windowWidth, windowHeight);
115                     }
116                 });
117             });
118
119             if (SetupCoreAnimation() && OverrideCoreAnimation == false)
120             {
121                 PlayAnimation();
122             }
123         }
124         /// <summary>
125         /// Get the Layouting animation object that transitions layouts and content.
126         /// Use OverrideCoreAnimation to explicitly control Playback.
127         /// </summary>
128         /// <returns> The layouting core Animation. </returns>
129         [EditorBrowsable(EditorBrowsableState.Never)]
130         public Animation GetCoreAnimation()
131         {
132             return coreAnimation;
133         }
134
135         /// <summary>
136         /// Create and set process callback
137         /// </summary>
138         internal void CreateProcessCallback()
139         {
140             if (!subscribed)
141             {
142                 ProcessorController.Instance.LayoutProcessorEvent += Process;
143                 subscribed = true;
144             }
145         }
146
147         /// <summary>
148         /// Add transition data for a LayoutItem to the transition stack.
149         /// </summary>
150         /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
151         internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
152         {
153             layoutTransitionDataQueue.Add(transitionDataEntry);
154         }
155
156         /// <summary>
157         /// Add LayoutItem to a removal stack for removal after transitions finish.
158         /// </summary>
159         /// <param name="itemToRemove">LayoutItem to remove.</param>
160         internal void AddToRemovalStack(LayoutItem itemToRemove)
161         {
162             if (itemRemovalQueue == null)
163             {
164                 itemRemovalQueue = new List<LayoutItem>();
165             }
166             itemRemovalQueue.Add(itemToRemove);
167         }
168
169         /// <summary>
170         /// Dispose Explicit or Implicit
171         /// </summary>
172         protected override void Dispose(DisposeTypes type)
173         {
174             if (disposed)
175             {
176                 return;
177             }
178
179             if (subscribed)
180             {
181                 ProcessorController.Instance.LayoutProcessorEvent -= Process;
182                 subscribed = false;
183             }
184
185             //Release your own unmanaged resources here.
186             //You should not access any managed member here except static instance.
187             //because the execution order of Finalizes is non-deterministic.
188
189             if (SwigCPtr.Handle != global::System.IntPtr.Zero)
190             {
191                 SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
192             }
193
194             base.Dispose(type);
195         }
196
197         // Traverse the tree looking for a root node that is a layout.
198         // Once found, it's children can be assigned Layouts and the Measure process started.
199         private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
200         {
201             if (rootNode.Layout != null)
202             {
203                 NUILog.Debug("LayoutController Root found:" + rootNode.Name);
204                 // rootNode has a layout, start measuring and layouting from here.
205                 MeasureAndLayout(rootNode, parentWidth, parentHeight);
206             }
207             else
208             {
209                 float rootWidth = rootNode.SizeWidth;
210                 float rootHeight = rootNode.SizeHeight;
211                 // Search children of supplied node for a layout.
212                 for (uint i = 0; i < rootNode.ChildCount; i++)
213                 {
214                     FindRootLayouts(rootNode.GetChildAt(i), rootWidth, rootHeight);
215                 }
216             }
217         }
218
219         // Starts of the actual measuring and layouting from the given root node.
220         // Can be called from multiple starting roots but in series.
221         // Get parent View's Size.  If using Legacy size negotiation then should have been set already.
222         // Parent not a View so assume it's a Layer which is the size of the window.
223         private void MeasureAndLayout(View root, float parentWidth, float parentHeight)
224         {
225             if (root.Layout != null)
226             {
227                 // Determine measure specification for root.
228                 // The root layout policy could be an exact size, be match parent or wrap children.
229                 // If wrap children then at most it can be the root parent size.
230                 // If match parent then should be root parent size.
231                 // If exact then should be that size limited by the root parent size.
232                 float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
233                 float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
234                 var widthMode = GetMode(root.WidthSpecification);
235                 var heightMode = GetMode(root.HeightSpecification);
236
237                 if (root.Layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
238                 {
239                     var widthSpec = CreateMeasureSpecification(widthSize, widthMode);
240                     var heightSpec = CreateMeasureSpecification(heightSize, heightMode);
241
242                     // Start at root with it's parent's widthSpecification and heightSpecification
243                     MeasureHierarchy(root, widthSpec, heightSpec);
244                 }
245
246                 float positionX = root.PositionX;
247                 float positionY = root.PositionY;
248                 // Start at root which was just measured.
249                 PerformLayout(root, new LayoutLength(positionX),
250                                      new LayoutLength(positionY),
251                                      new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
252                                      new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
253             }
254         }
255
256         private float GetLengthSize(float size, int specification)
257         {
258             // exact size provided so match width exactly
259             return (specification >= 0) ? specification : size;
260         }
261
262         private MeasureSpecification.ModeType GetMode(int specification)
263         {
264             if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
265             {
266                 return MeasureSpecification.ModeType.Exactly;
267             }
268             return MeasureSpecification.ModeType.Unspecified;
269         }
270
271         private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
272         {
273             return new MeasureSpecification(new LayoutLength(size), mode);
274         }
275
276         /// <summary>
277         /// Starts measuring the tree, starting from the root layout.
278         /// </summary>
279         private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
280         {
281             // Does this View have a layout?
282             // Yes - measure the layout. It will call this method again for each of it's children.
283             // No -  reached leaf or no layouts set
284             //
285             // If in a leaf View with no layout, it's natural size is bubbled back up.
286             root.Layout?.Measure(widthSpec, heightSpec);
287         }
288
289         /// <summary>
290         /// Performs the layouting positioning
291         /// </summary>
292         private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
293         {
294             root.Layout?.Layout(left, top, right, bottom);
295         }
296
297         /// <summary>
298         /// Play the animation.
299         /// </summary>
300         private void PlayAnimation()
301         {
302             NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
303             coreAnimation.Play();
304         }
305
306         private void AnimationFinished(object sender, EventArgs e)
307         {
308             // Iterate list of LayoutItem that were set for removal.
309             // Now the core animation has finished their Views can be removed.
310             if (itemRemovalQueue != null)
311             {
312                 foreach (LayoutItem item in itemRemovalQueue)
313                 {
314                     // Check incase the parent was already removed and the Owner was
315                     // removed already.
316                     if (item.Owner)
317                     {
318                         // Check again incase the parent has already been removed.
319                         ILayoutParent layoutParent = item.GetParent();
320                         LayoutGroup layoutGroup = layoutParent as LayoutGroup;
321                         if (layoutGroup != null)
322                         {
323                             layoutGroup.Owner?.RemoveChild(item.Owner);
324                         }
325
326                     }
327                 }
328                 itemRemovalQueue.Clear();
329                 // If LayoutItems added to stack whilst the core animation is playing
330                 // they would have been cleared here.
331                 // Could have another stack to be added to whilst the animation is running.
332                 // After the running stack is cleared it can be populated with the content
333                 // of the other stack.  Then the main removal stack iterated when AnimationFinished
334                 // occurs again.
335             }
336             NUILog.Debug("LayoutController AnimationFinished");
337             coreAnimation?.Clear();
338         }
339
340         /// <summary>
341         /// Set up the animation from each LayoutItems position data.
342         /// Iterates the transition stack, adding an Animator to the core animation.
343         /// </summary>
344         private bool SetupCoreAnimation()
345         {
346             // Initialize animation for this layout run.
347             bool animationPending = false;
348
349             NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
350
351             if (layoutTransitionDataQueue.Count > 0) // Something to animate
352             {
353                 if (!coreAnimation)
354                 {
355                     coreAnimation = new Animation();
356                 }
357                 coreAnimation.EndAction = Animation.EndActions.StopFinal;
358                 coreAnimation.Finished += AnimationFinished;
359
360                 // Iterate all items that have been queued for repositioning.
361                 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
362                 {
363                     AddAnimatorsToAnimation(layoutPositionData);
364                 }
365
366                 animationPending = true;
367
368                 // transitions have now been applied, clear stack, ready for new transitions on
369                 // next layout traversal.
370                 layoutTransitionDataQueue.Clear();
371             }
372             return animationPending;
373         }
374
375         private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
376         {
377             // A removed item does not have a valid target position within the layout so don't try to position.
378             if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
379             {
380                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
381                             new Vector3(layoutPositionData.Left,
382                                         layoutPositionData.Top,
383                                         layoutPositionData.Item.Owner.Position.Z),
384                             positionTransitionComponents.Delay,
385                             positionTransitionComponents.Duration,
386                             positionTransitionComponents.AlphaFunction);
387
388                 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
389                                    " left:" + layoutPositionData.Left +
390                                    " top:" + layoutPositionData.Top +
391                                    " delay:" + positionTransitionComponents.Delay +
392                                    " duration:" + positionTransitionComponents.Duration);
393             }
394         }
395
396         private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
397         {
398             // Text size cant be animated so is set to it's final size.
399             // It is due to the internals of the Text not being able to recalculate fast enough.
400             if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
401             {
402                 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
403                 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
404                 // Set size directly.
405                 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
406             }
407             else
408             {
409                 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
410                                          new Vector3(layoutPositionData.Right - layoutPositionData.Left,
411                                                      layoutPositionData.Bottom - layoutPositionData.Top,
412                                                      layoutPositionData.Item.Owner.Position.Z),
413                                          sizeTransitionComponents.Delay,
414                                          sizeTransitionComponents.Duration,
415                                          sizeTransitionComponents.AlphaFunction);
416
417                 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
418                                    " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
419                                    " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
420                                    " delay:" + sizeTransitionComponents.Delay +
421                                    " duration:" + sizeTransitionComponents.Duration);
422             }
423         }
424
425         void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
426         {
427             if (transitionsToAnimate?.Count > 0)
428             {
429                 foreach (LayoutTransition transition in transitionsToAnimate)
430                 {
431                     if (transition.AnimatableProperty != AnimatableProperties.Position &&
432                         transition.AnimatableProperty != AnimatableProperties.Size)
433                     {
434                         coreAnimation.AnimateTo(view,
435                                                  transition.AnimatableProperty.ToString(),
436                                                  transition.TargetValue,
437                                                  transition.Animator.Delay,
438                                                  transition.Animator.Duration,
439                                                  transition.Animator.AlphaFunction);
440
441                         NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
442                                            " Property:" + transition.AnimatableProperty.ToString() +
443                                            " delay:" + transition.Animator.Delay +
444                                            " duration:" + transition.Animator.Duration);
445                     }
446                 }
447             }
448         }
449
450         /// <summary>
451         /// Iterate transitions and replace Position Components if replacements found in list.
452         /// </summary>
453         private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
454                                                                  AnimatableProperties propertyToMatch,
455                                                                  ref TransitionComponents transitionComponentToUpdate)
456         {
457             foreach (LayoutTransition transition in sourceTransitionList)
458             {
459                 if (transition.AnimatableProperty == propertyToMatch)
460                 {
461                     // Matched property to animate is for the propertyToMatch so use provided Animator.
462                     transitionComponentToUpdate = transition.Animator;
463                 }
464             }
465         }
466
467         private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
468         {
469             AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
470             return new TransitionComponents(delay, duration, alphaFunction);
471         }
472
473         /// <summary>
474         /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
475         /// </summary>
476         private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
477         {
478             LayoutTransition positionTransition = new LayoutTransition();
479             LayoutTransition sizeTransition = new LayoutTransition();
480             TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
481
482             // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
483             // reposition to the new layout not to the insertion/removal of a sibling.
484             if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
485             {
486                 conditionForAnimators = TransitionCondition.LayoutChanged;
487             }
488
489             // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
490             TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
491             TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
492
493             bool matchedCustomTransitions = false;
494
495
496             TransitionList transitionsForCurrentCondition = new TransitionList();
497             // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
498
499             // Check if item to animate has it's own Transitions for this condition.
500             // If a key exists then a List of at least 1 transition exists.
501             if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
502             {
503                 // Child has transitions for the condition
504                 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
505             }
506
507             if (!matchedCustomTransitions)
508             {
509                 // Inherit parent transitions as none already set on View for the condition.
510                 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
511                 if (layoutParent != null)
512                 {
513                     // Item doesn't have it's own transitions for this condition so copy parents if
514                     // has a parent with transitions.
515                     LayoutGroup layoutGroup = layoutParent as LayoutGroup;
516                     TransitionList parentTransitionList;
517                     // Note TryGetValue returns null if key not matched.
518                     if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
519                     {
520                         // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
521                         LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
522                                                                 transitionsForCurrentCondition);
523                     }
524                 }
525             }
526
527
528             // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
529
530             // There can only be one position transition and one size position, they will be replaced if set multiple times.
531             // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
532
533             // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
534             // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
535             // Typically Delay, Duration and Alphafunction can be custom.
536             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
537                                                         AnimatableProperties.Position,
538                                                         ref positionTransitionComponents);
539
540             // Size
541             FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
542                                                         AnimatableProperties.Size,
543                                                         ref sizeTransitionComponents);
544
545             // Add animators to the core Animation,
546
547             SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
548
549             SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
550
551             SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
552
553             // Dispose components
554             positionTransitionComponents.Dispose();
555             sizeTransitionComponents.Dispose();
556         }
557
558     } // class LayoutController
559 } // namespace Tizen.NUI