2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using Tizen.NUI.BaseComponents;
19 using System.Runtime.InteropServices;
20 using System.Collections.Generic;
21 using System.Diagnostics;
23 using System.ComponentModel;
28 /// [Draft] The class that initiates the Measuring and Layouting of the tree,
29 /// It sets a callback that becomes the entry point into the C# Layouting.
31 internal class LayoutController : Disposable
33 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
34 internal delegate void Callback(int id);
36 event Callback instance;
38 private Window window;
40 Animation coreAnimation;
42 private List<LayoutData> layoutTransitionDataQueue;
44 private List<LayoutItem> itemRemovalQueue;
47 /// Constructs a LayoutController which controls the measuring and layouting.<br />
48 /// <param name="window">Window attach this LayoutController to.</param>
50 public LayoutController(Window window) : this(Interop.LayoutController.New(), true)
53 layoutTransitionDataQueue = new List<LayoutData>();
56 internal LayoutController(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
61 /// Get the unique id of the LayoutController
65 return Interop.LayoutController.GetId(SwigCPtr);
69 /// Request relayout of a LayoutItem and it's parent tree.
71 /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
72 public void RequestLayout(LayoutItem layoutItem)
74 // Go up the tree and mark all parents to relayout
75 ILayoutParent layoutParent = layoutItem.GetParent();
76 if (layoutParent != null)
78 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
79 if (layoutGroup != null && !layoutGroup.LayoutRequested)
81 layoutGroup.RequestLayout();
87 /// Get the Layouting animation object that transitions layouts and content.
88 /// Use OverrideCoreAnimation to explicitly control Playback.
90 /// <returns> The layouting core Animation. </returns>
91 [EditorBrowsable(EditorBrowsableState.Never)]
92 public Animation GetCoreAnimation()
98 /// Set or Get Layouting core animation override property.
99 /// Gives explicit control over the Layouting animation playback if set to True.
100 /// Reset to False if explicit control no longer required.
102 [EditorBrowsable(EditorBrowsableState.Never)]
103 public bool OverrideCoreAnimation { get; set; } = false;
106 /// Create and set process callback
108 internal void CreateProcessCallback()
110 if (instance == null)
112 instance = new Callback(Process);
113 Interop.LayoutController.SetCallback(SwigCPtr, instance);
118 /// Add transition data for a LayoutItem to the transition stack.
120 /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
121 internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
123 layoutTransitionDataQueue.Add(transitionDataEntry);
127 /// Add LayoutItem to a removal stack for removal after transitions finish.
129 /// <param name="itemToRemove">LayoutItem to remove.</param>
130 internal void AddToRemovalStack(LayoutItem itemToRemove)
132 if (itemRemovalQueue == null)
134 itemRemovalQueue = new List<LayoutItem>();
136 itemRemovalQueue.Add(itemToRemove);
140 /// Dispose Explicit or Implicit
142 protected override void Dispose(DisposeTypes type)
149 //Release your own unmanaged resources here.
150 //You should not access any managed member here except static instance.
151 //because the execution order of Finalizes is non-deterministic.
153 if (SwigCPtr.Handle != global::System.IntPtr.Zero)
155 Interop.LayoutController.DeleteLayoutController(SwigCPtr);
156 SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
162 // Traverse the tree looking for a root node that is a layout.
163 // Once found, it's children can be assigned Layouts and the Measure process started.
164 private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
166 if (rootNode.Layout != null)
168 NUILog.Debug("LayoutController Root found:" + rootNode.Name);
169 // rootNode has a layout, start measuring and layouting from here.
170 MeasureAndLayout(rootNode, parentWidth, parentHeight);
174 float rootWidth = rootNode.SizeWidth;
175 float rootHeight = rootNode.SizeHeight;
176 // Search children of supplied node for a layout.
177 for (uint i = 0; i < rootNode.ChildCount; i++)
179 View view = rootNode.GetChildAt(i);
180 FindRootLayouts(view, rootWidth, rootHeight);
185 // Starts of the actual measuring and layouting from the given root node.
186 // Can be called from multiple starting roots but in series.
187 // Get parent View's Size. If using Legacy size negotiation then should have been set already.
188 // Parent not a View so assume it's a Layer which is the size of the window.
189 void MeasureAndLayout(View root, float parentWidth, float parentHeight)
191 if (root.Layout != null)
193 // Determine measure specification for root.
194 // The root layout policy could be an exact size, be match parent or wrap children.
195 // If wrap children then at most it can be the root parent size.
196 // If match parent then should be root parent size.
197 // If exact then should be that size limited by the root parent size.
198 float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
199 float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
200 MeasureSpecification.ModeType widthMode = GetMode(root.WidthSpecification);
201 MeasureSpecification.ModeType heightMode = GetMode(root.HeightSpecification);
203 if (root.Layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
205 MeasureSpecification widthSpec = CreateMeasureSpecification(widthSize, widthMode);
206 MeasureSpecification heightSpec = CreateMeasureSpecification(heightSize, heightMode);
208 // Start at root with it's parent's widthSpecification and heightSpecification
209 MeasureHierarchy(root, widthSpec, heightSpec);
212 float positionX = root.PositionX;
213 float positionY = root.PositionY;
214 // Start at root which was just measured.
215 PerformLayout(root, new LayoutLength(positionX),
216 new LayoutLength(positionY),
217 new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
218 new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
221 if (SetupCoreAnimation() && OverrideCoreAnimation == false)
227 private float GetLengthSize(float size, int specification)
229 // exact size provided so match width exactly
230 return (specification >= 0) ? specification : size;
233 private MeasureSpecification.ModeType GetMode(int specification)
235 if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
237 return MeasureSpecification.ModeType.Exactly;
239 return MeasureSpecification.ModeType.Unspecified;
242 private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
244 return new MeasureSpecification(new LayoutLength(size), mode);
248 /// Entry point into the C# Layouting that starts the Processing
250 private void Process(int id)
252 Vector2 windowSize = window.GetSize();
253 float width = windowSize.Width;
254 float height = windowSize.Height;
256 window.LayersChildren?.ForEach(layer =>
258 layer?.Children?.ForEach(view =>
262 FindRootLayouts(view, width, height);
266 windowSize.Dispose();
271 /// Starts measuring the tree, starting from the root layout.
273 private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
275 // Does this View have a layout?
276 // Yes - measure the layout. It will call this method again for each of it's children.
277 // No - reached leaf or no layouts set
279 // If in a leaf View with no layout, it's natural size is bubbled back up.
280 root.Layout?.Measure(widthSpec, heightSpec);
284 /// Performs the layouting positioning
286 private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
288 root.Layout?.Layout(left, top, right, bottom);
292 /// Play the animation.
294 private void PlayAnimation()
296 NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
297 coreAnimation.Play();
300 private void AnimationFinished(object sender, EventArgs e)
302 // Iterate list of LayoutItem that were set for removal.
303 // Now the core animation has finished their Views can be removed.
304 if (itemRemovalQueue != null)
306 foreach (LayoutItem item in itemRemovalQueue)
308 // Check incase the parent was already removed and the Owner was
312 // Check again incase the parent has already been removed.
313 ILayoutParent layoutParent = item.GetParent();
314 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
315 if (layoutGroup != null)
317 layoutGroup.Owner?.RemoveChild(item.Owner);
322 itemRemovalQueue.Clear();
323 // If LayoutItems added to stack whilst the core animation is playing
324 // they would have been cleared here.
325 // Could have another stack to be added to whilst the animation is running.
326 // After the running stack is cleared it can be populated with the content
327 // of the other stack. Then the main removal stack iterated when AnimationFinished
330 NUILog.Debug("LayoutController AnimationFinished");
331 coreAnimation?.Clear();
335 /// Set up the animation from each LayoutItems position data.
336 /// Iterates the transition stack, adding an Animator to the core animation.
338 private bool SetupCoreAnimation()
340 // Initialize animation for this layout run.
341 bool animationPending = false;
343 NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
345 if (layoutTransitionDataQueue.Count > 0) // Something to animate
349 coreAnimation = new Animation();
351 coreAnimation.EndAction = Animation.EndActions.StopFinal;
352 coreAnimation.Finished += AnimationFinished;
354 // Iterate all items that have been queued for repositioning.
355 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
357 AddAnimatorsToAnimation(layoutPositionData);
360 animationPending = true;
362 // transitions have now been applied, clear stack, ready for new transitions on
363 // next layout traversal.
364 layoutTransitionDataQueue.Clear();
366 return animationPending;
369 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
371 // A removed item does not have a valid target position within the layout so don't try to position.
372 if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
374 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
375 new Vector3(layoutPositionData.Left,
376 layoutPositionData.Top,
377 layoutPositionData.Item.Owner.Position.Z),
378 positionTransitionComponents.Delay,
379 positionTransitionComponents.Duration,
380 positionTransitionComponents.AlphaFunction);
382 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
383 " left:" + layoutPositionData.Left +
384 " top:" + layoutPositionData.Top +
385 " delay:" + positionTransitionComponents.Delay +
386 " duration:" + positionTransitionComponents.Duration);
390 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
392 // Text size cant be animated so is set to it's final size.
393 // It is due to the internals of the Text not being able to recalculate fast enough.
394 if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
396 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
397 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
398 // Set size directly.
399 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
403 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
404 new Vector3(layoutPositionData.Right - layoutPositionData.Left,
405 layoutPositionData.Bottom - layoutPositionData.Top,
406 layoutPositionData.Item.Owner.Position.Z),
407 sizeTransitionComponents.Delay,
408 sizeTransitionComponents.Duration,
409 sizeTransitionComponents.AlphaFunction);
411 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
412 " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
413 " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
414 " delay:" + sizeTransitionComponents.Delay +
415 " duration:" + sizeTransitionComponents.Duration);
419 void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
421 if (transitionsToAnimate?.Count > 0)
423 foreach (LayoutTransition transition in transitionsToAnimate)
425 if (transition.AnimatableProperty != AnimatableProperties.Position &&
426 transition.AnimatableProperty != AnimatableProperties.Size)
428 coreAnimation.AnimateTo(view,
429 transition.AnimatableProperty.ToString(),
430 transition.TargetValue,
431 transition.Animator.Delay,
432 transition.Animator.Duration,
433 transition.Animator.AlphaFunction);
435 NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
436 " Property:" + transition.AnimatableProperty.ToString() +
437 " delay:" + transition.Animator.Delay +
438 " duration:" + transition.Animator.Duration);
445 /// Iterate transitions and replace Position Components if replacements found in list.
447 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
448 AnimatableProperties propertyToMatch,
449 ref TransitionComponents transitionComponentToUpdate)
451 foreach (LayoutTransition transition in sourceTransitionList)
453 if (transition.AnimatableProperty == propertyToMatch)
455 // Matched property to animate is for the propertyToMatch so use provided Animator.
456 transitionComponentToUpdate = transition.Animator;
461 private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
463 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
464 return new TransitionComponents(delay, duration, alphaFunction);
468 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
470 private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
472 LayoutTransition positionTransition = new LayoutTransition();
473 LayoutTransition sizeTransition = new LayoutTransition();
474 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
476 // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
477 // reposition to the new layout not to the insertion/removal of a sibling.
478 if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
480 conditionForAnimators = TransitionCondition.LayoutChanged;
483 // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
484 TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
485 TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
487 bool matchedCustomTransitions = false;
490 TransitionList transitionsForCurrentCondition = new TransitionList();
491 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
493 // Check if item to animate has it's own Transitions for this condition.
494 // If a key exists then a List of at least 1 transition exists.
495 if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
497 // Child has transitions for the condition
498 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
501 if (!matchedCustomTransitions)
503 // Inherit parent transitions as none already set on View for the condition.
504 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
505 if (layoutParent != null)
507 // Item doesn't have it's own transitions for this condition so copy parents if
508 // has a parent with transitions.
509 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
510 TransitionList parentTransitionList;
511 // Note TryGetValue returns null if key not matched.
512 if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
514 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
515 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
516 transitionsForCurrentCondition);
522 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
524 // There can only be one position transition and one size position, they will be replaced if set multiple times.
525 // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
527 // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
528 // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
529 // Typically Delay, Duration and Alphafunction can be custom.
530 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
531 AnimatableProperties.Position,
532 ref positionTransitionComponents);
535 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
536 AnimatableProperties.Size,
537 ref sizeTransitionComponents);
539 // Add animators to the core Animation,
541 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
543 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
545 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
548 } // class LayoutController
549 } // namespace Tizen.NUI