[NUI] Separate the LayoutTransition from LayoutController (#3592)
[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.ComponentModel;
20 using System;
21
22 namespace Tizen.NUI
23 {
24     /// <summary>
25     /// [Draft] The class that initiates the Measuring and Layouting of the tree,
26     ///         It sets a callback that becomes the entry point into the C# Layouting.
27     /// </summary>
28     internal class LayoutController : Disposable
29     {
30         private static int layoutControllerID = 1;
31
32         private int id;
33         private Window window;
34         private float windowWidth;
35         private float windowHeight;
36         private LayoutTransitionManager transitionManager;
37
38         private bool subscribed;
39
40         /// <summary>
41         /// Constructs a LayoutController which controls the measuring and layouting.<br />
42         /// <param name="window">Window attach this LayoutController to.</param>
43         /// </summary>
44         public LayoutController(Window window)
45         {
46             transitionManager = new LayoutTransitionManager();
47             id = layoutControllerID++;
48
49             SetBaseWindowAndSize(window);
50         }
51
52         /// <summary>
53         /// Dispose Explicit or Implicit
54         /// </summary>
55         protected override void Dispose(DisposeTypes type)
56         {
57             if (Disposed)
58             {
59                 return;
60             }
61
62             if (subscribed)
63             {
64                 ProcessorController.Instance.LayoutProcessorEvent -= Process;
65                 subscribed = false;
66             }
67
68             //Release your own unmanaged resources here.
69             //You should not access any managed member here except static instance.
70             //because the execution order of Finalizes is non-deterministic.
71
72             if (SwigCPtr.Handle != global::System.IntPtr.Zero)
73             {
74                 SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
75             }
76
77             transitionManager.Dispose();
78
79             base.Dispose(type);
80         }
81
82         /// <summary>
83         /// Set or Get Layouting core animation override property.
84         /// Gives explicit control over the Layouting animation playback if set to True.
85         /// Reset to False if explicit control no longer required.
86         /// </summary>
87         [EditorBrowsable(EditorBrowsableState.Never)]
88         public bool OverrideCoreAnimation
89         {
90             get
91             {
92                 return transitionManager.OverrideCoreAnimation;
93             }
94             set
95             {
96                 transitionManager.OverrideCoreAnimation = value;
97             }
98         }
99
100         /// <summary>
101         /// Get the unique id of the LayoutController
102         /// </summary>
103         public int GetId()
104         {
105             return id;
106         }
107
108         /// <summary>
109         /// Request relayout of a LayoutItem and it's parent tree.
110         /// </summary>
111         /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
112         public void RequestLayout(LayoutItem layoutItem)
113         {
114             // Go up the tree and mark all parents to relayout
115             ILayoutParent layoutParent = layoutItem.GetParent();
116             if (layoutParent != null)
117             {
118                 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
119                 if (layoutGroup != null && !layoutGroup.LayoutRequested)
120                 {
121                     layoutGroup.RequestLayout();
122                 }
123             }
124         }
125
126         /// <summary>
127         /// Entry point into the C# Layouting that starts the Processing
128         /// </summary>
129         public void Process(object source, EventArgs e)
130         {
131             window.LayersChildren?.ForEach(layer =>
132             {
133                 layer?.Children?.ForEach(view =>
134                 {
135                     if (view != null)
136                     {
137                         FindRootLayouts(view, windowWidth, windowHeight);
138                     }
139                 });
140             });
141
142             transitionManager.SetupCoreAndPlayAnimation();
143         }
144
145         /// <summary>
146         /// Get the Layouting animation object that transitions layouts and content.
147         /// Use OverrideCoreAnimation to explicitly control Playback.
148         /// </summary>
149         /// <returns> The layouting core Animation. </returns>
150         [EditorBrowsable(EditorBrowsableState.Never)]
151         public Animation GetCoreAnimation()
152         {
153             return transitionManager.CoreAnimation;
154         }
155
156         /// <summary>
157         /// Create and set process callback
158         /// </summary>
159         internal void CreateProcessCallback()
160         {
161             if (!subscribed)
162             {
163                 ProcessorController.Instance.LayoutProcessorEvent += Process;
164                 subscribed = true;
165             }
166         }
167
168         /// <summary>
169         /// Add transition data for a LayoutItem to the transition stack.
170         /// </summary>
171         /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
172         internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
173         {
174             transitionManager.AddTransitionDataEntry(transitionDataEntry);
175         }
176
177         /// <summary>
178         /// Add LayoutItem to a removal stack for removal after transitions finish.
179         /// </summary>
180         /// <param name="itemToRemove">LayoutItem to remove.</param>
181         internal void AddToRemovalStack(LayoutItem itemToRemove)
182         {
183             transitionManager.AddToRemovalStack(itemToRemove);
184         }
185
186         // Traverse the tree looking for a root node that is a layout.
187         // Once found, it's children can be assigned Layouts and the Measure process started.
188         private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
189         {
190             if (rootNode.Layout != null)
191             {
192                 NUILog.Debug("LayoutController Root found:" + rootNode.Name);
193                 // rootNode has a layout, start measuring and layouting from here.
194                 MeasureAndLayout(rootNode, parentWidth, parentHeight);
195             }
196             else
197             {
198                 float rootWidth = rootNode.SizeWidth;
199                 float rootHeight = rootNode.SizeHeight;
200                 // Search children of supplied node for a layout.
201                 for (uint i = 0; i < rootNode.ChildCount; i++)
202                 {
203                     FindRootLayouts(rootNode.GetChildAt(i), rootWidth, rootHeight);
204                 }
205             }
206         }
207
208         // Starts of the actual measuring and layouting from the given root node.
209         // Can be called from multiple starting roots but in series.
210         // Get parent View's Size.  If using Legacy size negotiation then should have been set already.
211         // Parent not a View so assume it's a Layer which is the size of the window.
212         private void MeasureAndLayout(View root, float parentWidth, float parentHeight)
213         {
214             if (root.Layout != null)
215             {
216                 // Determine measure specification for root.
217                 // The root layout policy could be an exact size, be match parent or wrap children.
218                 // If wrap children then at most it can be the root parent size.
219                 // If match parent then should be root parent size.
220                 // If exact then should be that size limited by the root parent size.
221                 float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
222                 float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
223                 var widthMode = GetMode(root.WidthSpecification);
224                 var heightMode = GetMode(root.HeightSpecification);
225
226                 if (root.Layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
227                 {
228                     var widthSpec = CreateMeasureSpecification(widthSize, widthMode);
229                     var heightSpec = CreateMeasureSpecification(heightSize, heightMode);
230
231                     // Start at root with it's parent's widthSpecification and heightSpecification
232                     MeasureHierarchy(root, widthSpec, heightSpec);
233                 }
234
235                 float positionX = root.PositionX;
236                 float positionY = root.PositionY;
237                 // Start at root which was just measured.
238                 PerformLayout(root, new LayoutLength(positionX),
239                                      new LayoutLength(positionY),
240                                      new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
241                                      new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
242             }
243         }
244
245         private float GetLengthSize(float size, int specification)
246         {
247             // exact size provided so match width exactly
248             return (specification >= 0) ? specification : size;
249         }
250
251         private MeasureSpecification.ModeType GetMode(int specification)
252         {
253             if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
254             {
255                 return MeasureSpecification.ModeType.Exactly;
256             }
257             return MeasureSpecification.ModeType.Unspecified;
258         }
259
260         private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
261         {
262             return new MeasureSpecification(new LayoutLength(size), mode);
263         }
264
265         /// <summary>
266         /// Starts measuring the tree, starting from the root layout.
267         /// </summary>
268         private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
269         {
270             // Does this View have a layout?
271             // Yes - measure the layout. It will call this method again for each of it's children.
272             // No -  reached leaf or no layouts set
273             //
274             // If in a leaf View with no layout, it's natural size is bubbled back up.
275             root.Layout?.Measure(widthSpec, heightSpec);
276         }
277
278         /// <summary>
279         /// Performs the layouting positioning
280         /// </summary>
281         private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
282         {
283             root.Layout?.Layout(left, top, right, bottom);
284         }
285
286         private void SetBaseWindowAndSize(Window window)
287         {
288             this.window = window;
289             this.window.Resized += OnWindowResized;
290
291             var windowSize = window.GetSize();
292             windowWidth = windowSize.Width;
293             windowHeight = windowSize.Height;
294             windowSize.Dispose();
295             windowSize = null;
296         }
297
298         private void OnWindowResized(object sender, Window.ResizedEventArgs e)
299         {
300             windowWidth = e.WindowSize.Width;
301             windowHeight = e.WindowSize.Height;
302         }
303     } // class LayoutController
304 } // namespace Tizen.NUI