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 float positionX = root.PositionX;
192 float positionY = root.PositionY;
194 // Determine measure specification for root.
195 // The root layout policy could be an exact size, be match parent or wrap children.
196 // If wrap children then at most it can be the root parent size.
197 // If match parent then should be root parent size.
198 // If exact then should be that size limited by the root parent size.
199 MeasureSpecification parentWidthSpecification = CreateMeasureSpecification(parentWidth, root.WidthSpecification);
200 MeasureSpecification parentHeightSpecification = CreateMeasureSpecification(parentHeight, root.HeightSpecification);
202 // Start at root with it's parent's widthSpecification and heightSpecification
203 MeasureHierarchy(root, parentWidthSpecification, parentHeightSpecification);
205 // Start at root which was just measured.
206 PerformLayout(root, new LayoutLength(positionX),
207 new LayoutLength(positionY),
208 new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
209 new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
211 if (SetupCoreAnimation() && OverrideCoreAnimation == false)
217 private MeasureSpecification CreateMeasureSpecification(float size, int specification)
219 LayoutLength length = new LayoutLength(size);
220 MeasureSpecification.ModeType mode = MeasureSpecification.ModeType.Unspecified;
222 if (specification >= 0)
224 // exact size provided so match width exactly
225 length = new LayoutLength(specification);
226 mode = MeasureSpecification.ModeType.Exactly;
228 else if (specification == LayoutParamPolicies.MatchParent)
230 mode = MeasureSpecification.ModeType.Exactly;
232 return new MeasureSpecification(length, mode);
236 /// Entry point into the C# Layouting that starts the Processing
238 private void Process(int id)
240 Vector2 windowSize = window.GetSize();
241 float width = windowSize.Width;
242 float height = windowSize.Height;
243 // First layer in the Window should be the default layer (index 0 )
244 foreach (Layer layer in window.LayersChildren)
246 foreach (View view in layer.Children)
248 FindRootLayouts(view, width, height);
251 windowSize.Dispose();
256 /// Starts measuring the tree, starting from the root layout.
258 private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
260 // Does this View have a layout?
261 // Yes - measure the layout. It will call this method again for each of it's children.
262 // No - reached leaf or no layouts set
264 // If in a leaf View with no layout, it's natural size is bubbled back up.
265 root.Layout?.Measure(widthSpec, heightSpec);
269 /// Performs the layouting positioning
271 private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
273 root.Layout?.Layout(left, top, right, bottom);
277 /// Play the animation.
279 private void PlayAnimation()
281 NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
282 coreAnimation.Play();
285 private void AnimationFinished(object sender, EventArgs e)
287 // Iterate list of LayoutItem that were set for removal.
288 // Now the core animation has finished their Views can be removed.
289 if (itemRemovalQueue != null)
291 foreach (LayoutItem item in itemRemovalQueue)
293 // Check incase the parent was already removed and the Owner was
297 // Check again incase the parent has already been removed.
298 ILayoutParent layoutParent = item.GetParent();
299 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
300 if (layoutGroup != null)
302 layoutGroup.Owner?.RemoveChild(item.Owner);
307 itemRemovalQueue.Clear();
308 // If LayoutItems added to stack whilst the core animation is playing
309 // they would have been cleared here.
310 // Could have another stack to be added to whilst the animation is running.
311 // After the running stack is cleared it can be populated with the content
312 // of the other stack. Then the main removal stack iterated when AnimationFinished
315 NUILog.Debug("LayoutController AnimationFinished");
316 coreAnimation?.Clear();
320 /// Set up the animation from each LayoutItems position data.
321 /// Iterates the transition stack, adding an Animator to the core animation.
323 private bool SetupCoreAnimation()
325 // Initialize animation for this layout run.
326 bool animationPending = false;
328 NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
330 if (layoutTransitionDataQueue.Count > 0) // Something to animate
334 coreAnimation = new Animation();
336 coreAnimation.EndAction = Animation.EndActions.StopFinal;
337 coreAnimation.Finished += AnimationFinished;
339 // Iterate all items that have been queued for repositioning.
340 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
342 AddAnimatorsToAnimation(layoutPositionData);
345 animationPending = true;
347 // transitions have now been applied, clear stack, ready for new transitions on
348 // next layout traversal.
349 layoutTransitionDataQueue.Clear();
351 return animationPending;
354 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
356 // A removed item does not have a valid target position within the layout so don't try to position.
357 if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
359 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
360 new Vector3(layoutPositionData.Left,
361 layoutPositionData.Top,
362 layoutPositionData.Item.Owner.Position.Z),
363 positionTransitionComponents.Delay,
364 positionTransitionComponents.Duration,
365 positionTransitionComponents.AlphaFunction);
367 NUILog.Debug("LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
368 " left:" + layoutPositionData.Left +
369 " top:" + layoutPositionData.Top +
370 " delay:" + positionTransitionComponents.Delay +
371 " duration:" + positionTransitionComponents.Duration);
375 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
377 // Text size cant be animated so is set to it's final size.
378 // It is due to the internals of the Text not being able to recalculate fast enough.
379 if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
381 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
382 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
383 // Set size directly.
384 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
388 coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
389 new Vector3(layoutPositionData.Right - layoutPositionData.Left,
390 layoutPositionData.Bottom - layoutPositionData.Top,
391 layoutPositionData.Item.Owner.Position.Z),
392 sizeTransitionComponents.Delay,
393 sizeTransitionComponents.Duration,
394 sizeTransitionComponents.AlphaFunction);
396 NUILog.Debug("LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
397 " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
398 " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
399 " delay:" + sizeTransitionComponents.Delay +
400 " duration:" + sizeTransitionComponents.Duration);
404 void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
406 if (transitionsToAnimate?.Count > 0)
408 foreach (LayoutTransition transition in transitionsToAnimate)
410 if (transition.AnimatableProperty != AnimatableProperties.Position &&
411 transition.AnimatableProperty != AnimatableProperties.Size)
413 coreAnimation.AnimateTo(view,
414 transition.AnimatableProperty.ToString(),
415 transition.TargetValue,
416 transition.Animator.Delay,
417 transition.Animator.Duration,
418 transition.Animator.AlphaFunction);
420 NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
421 " Property:" + transition.AnimatableProperty.ToString() +
422 " delay:" + transition.Animator.Delay +
423 " duration:" + transition.Animator.Duration);
430 /// Iterate transitions and replace Position Components if replacements found in list.
432 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
433 AnimatableProperties propertyToMatch,
434 ref TransitionComponents transitionComponentToUpdate)
436 foreach (LayoutTransition transition in sourceTransitionList)
438 if (transition.AnimatableProperty == propertyToMatch)
440 // Matched property to animate is for the propertyToMatch so use provided Animator.
441 transitionComponentToUpdate = transition.Animator;
446 private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
448 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
449 return new TransitionComponents(delay, duration, alphaFunction);
453 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
455 private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
457 LayoutTransition positionTransition = new LayoutTransition();
458 LayoutTransition sizeTransition = new LayoutTransition();
459 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
461 // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
462 // reposition to the new layout not to the insertion/removal of a sibling.
463 if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
465 conditionForAnimators = TransitionCondition.LayoutChanged;
468 // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
469 TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
470 TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
472 bool matchedCustomTransitions = false;
475 TransitionList transitionsForCurrentCondition = new TransitionList();
476 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
478 // Check if item to animate has it's own Transitions for this condition.
479 // If a key exists then a List of at least 1 transition exists.
480 if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
482 // Child has transitions for the condition
483 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
486 if (!matchedCustomTransitions)
488 // Inherit parent transitions as none already set on View for the condition.
489 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
490 if (layoutParent != null)
492 // Item doesn't have it's own transitions for this condition so copy parents if
493 // has a parent with transitions.
494 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
495 TransitionList parentTransitionList;
496 // Note TryGetValue returns null if key not matched.
497 if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
499 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
500 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
501 transitionsForCurrentCondition);
507 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
509 // There can only be one position transition and one size position, they will be replaced if set multiple times.
510 // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
512 // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
513 // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
514 // Typically Delay, Duration and Alphafunction can be custom.
515 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
516 AnimatableProperties.Position,
517 ref positionTransitionComponents);
520 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
521 AnimatableProperties.Size,
522 ref sizeTransitionComponents);
524 // Add animators to the core Animation,
526 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
528 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
530 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
533 } // class LayoutController
534 } // namespace Tizen.NUI