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