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.Collections.Generic;
21 using System.ComponentModel;
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.
29 internal class LayoutController : Disposable
31 private static int layoutControllerID = 1;
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;
41 private bool subscribed;
44 /// Constructs a LayoutController which controls the measuring and layouting.<br />
45 /// <param name="window">Window attach this LayoutController to.</param>
47 public LayoutController(Window window)
50 this.window.Resized += OnWindowResized;
51 var windowSize = window.GetSize();
52 windowWidth = windowSize.Width;
53 windowHeight = windowSize.Height;
55 layoutTransitionDataQueue = new List<LayoutData>();
56 id = layoutControllerID++;
62 private void OnWindowResized(object sender, Window.ResizedEventArgs e)
64 windowWidth = e.WindowSize.Width;
65 windowHeight = e.WindowSize.Height;
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.
73 [EditorBrowsable(EditorBrowsableState.Never)]
74 public bool OverrideCoreAnimation { get; set; } = false;
77 /// Get the unique id of the LayoutController
85 /// Request relayout of a LayoutItem and it's parent tree.
87 /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
88 public void RequestLayout(LayoutItem layoutItem)
90 // Go up the tree and mark all parents to relayout
91 ILayoutParent layoutParent = layoutItem.GetParent();
92 if (layoutParent != null)
94 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
95 if (layoutGroup != null && !layoutGroup.LayoutRequested)
97 layoutGroup.RequestLayout();
104 /// Entry point into the C# Layouting that starts the Processing
106 public void Process(object source, EventArgs e)
108 window.LayersChildren?.ForEach(layer =>
110 layer?.Children?.ForEach(view =>
114 FindRootLayouts(view, windowWidth, windowHeight);
120 /// Get the Layouting animation object that transitions layouts and content.
121 /// Use OverrideCoreAnimation to explicitly control Playback.
123 /// <returns> The layouting core Animation. </returns>
124 [EditorBrowsable(EditorBrowsableState.Never)]
125 public Animation GetCoreAnimation()
127 return coreAnimation;
131 /// Create and set process callback
133 internal void CreateProcessCallback()
137 ProcessorController.Instance.LayoutProcessorEvent += Process;
143 /// Add transition data for a LayoutItem to the transition stack.
145 /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
146 internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
148 layoutTransitionDataQueue.Add(transitionDataEntry);
152 /// Add LayoutItem to a removal stack for removal after transitions finish.
154 /// <param name="itemToRemove">LayoutItem to remove.</param>
155 internal void AddToRemovalStack(LayoutItem itemToRemove)
157 if (itemRemovalQueue == null)
159 itemRemovalQueue = new List<LayoutItem>();
161 itemRemovalQueue.Add(itemToRemove);
165 /// Dispose Explicit or Implicit
167 protected override void Dispose(DisposeTypes type)
176 ProcessorController.Instance.LayoutProcessorEvent -= Process;
180 //Release your own unmanaged resources here.
181 //You should not access any managed member here except static instance.
182 //because the execution order of Finalizes is non-deterministic.
184 if (SwigCPtr.Handle != global::System.IntPtr.Zero)
186 SwigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
192 // Traverse the tree looking for a root node that is a layout.
193 // Once found, it's children can be assigned Layouts and the Measure process started.
194 private void FindRootLayouts(View rootNode, float parentWidth, float parentHeight)
196 if (rootNode.Layout != null)
198 NUILog.Debug("LayoutController Root found:" + rootNode.Name);
199 // rootNode has a layout, start measuring and layouting from here.
200 MeasureAndLayout(rootNode, parentWidth, parentHeight);
204 float rootWidth = rootNode.SizeWidth;
205 float rootHeight = rootNode.SizeHeight;
206 // Search children of supplied node for a layout.
207 for (uint i = 0; i < rootNode.ChildCount; i++)
209 FindRootLayouts(rootNode.GetChildAt(i), rootWidth, rootHeight);
214 // Starts of the actual measuring and layouting from the given root node.
215 // Can be called from multiple starting roots but in series.
216 // Get parent View's Size. If using Legacy size negotiation then should have been set already.
217 // Parent not a View so assume it's a Layer which is the size of the window.
218 private void MeasureAndLayout(View root, float parentWidth, float parentHeight)
220 if (root.Layout != null)
222 // Determine measure specification for root.
223 // The root layout policy could be an exact size, be match parent or wrap children.
224 // If wrap children then at most it can be the root parent size.
225 // If match parent then should be root parent size.
226 // If exact then should be that size limited by the root parent size.
227 float widthSize = GetLengthSize(parentWidth, root.WidthSpecification);
228 float heightSize = GetLengthSize(parentHeight, root.HeightSpecification);
229 var widthMode = GetMode(root.WidthSpecification);
230 var heightMode = GetMode(root.HeightSpecification);
232 if (root.Layout.NeedsLayout(widthSize, heightSize, widthMode, heightMode))
234 var widthSpec = CreateMeasureSpecification(widthSize, widthMode);
235 var heightSpec = CreateMeasureSpecification(heightSize, heightMode);
237 // Start at root with it's parent's widthSpecification and heightSpecification
238 MeasureHierarchy(root, widthSpec, heightSpec);
241 float positionX = root.PositionX;
242 float positionY = root.PositionY;
243 // Start at root which was just measured.
244 PerformLayout(root, new LayoutLength(positionX),
245 new LayoutLength(positionY),
246 new LayoutLength(positionX) + root.Layout.MeasuredWidth.Size,
247 new LayoutLength(positionY) + root.Layout.MeasuredHeight.Size);
250 if (SetupCoreAnimation() && OverrideCoreAnimation == false)
256 private float GetLengthSize(float size, int specification)
258 // exact size provided so match width exactly
259 return (specification >= 0) ? specification : size;
262 private MeasureSpecification.ModeType GetMode(int specification)
264 if (specification >= 0 || specification == LayoutParamPolicies.MatchParent)
266 return MeasureSpecification.ModeType.Exactly;
268 return MeasureSpecification.ModeType.Unspecified;
271 private MeasureSpecification CreateMeasureSpecification(float size, MeasureSpecification.ModeType mode)
273 return new MeasureSpecification(new LayoutLength(size), mode);
277 /// Starts measuring the tree, starting from the root layout.
279 private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
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
285 // If in a leaf View with no layout, it's natural size is bubbled back up.
286 root.Layout?.Measure(widthSpec, heightSpec);
290 /// Performs the layouting positioning
292 private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
294 root.Layout?.Layout(left, top, right, bottom);
298 /// Play the animation.
300 private void PlayAnimation()
302 NUILog.Debug("LayoutController Playing, Core Duration:" + coreAnimation.Duration);
303 coreAnimation.Play();
306 private void AnimationFinished(object sender, EventArgs e)
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)
312 foreach (LayoutItem item in itemRemovalQueue)
314 // Check incase the parent was already removed and the Owner was
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)
323 layoutGroup.Owner?.RemoveChild(item.Owner);
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
336 NUILog.Debug("LayoutController AnimationFinished");
337 coreAnimation?.Clear();
341 /// Set up the animation from each LayoutItems position data.
342 /// Iterates the transition stack, adding an Animator to the core animation.
344 private bool SetupCoreAnimation()
346 // Initialize animation for this layout run.
347 bool animationPending = false;
349 NUILog.Debug("LayoutController SetupCoreAnimation for:" + layoutTransitionDataQueue.Count);
351 if (layoutTransitionDataQueue.Count > 0) // Something to animate
355 coreAnimation = new Animation();
357 coreAnimation.EndAction = Animation.EndActions.StopFinal;
358 coreAnimation.Finished += AnimationFinished;
360 // Iterate all items that have been queued for repositioning.
361 foreach (LayoutData layoutPositionData in layoutTransitionDataQueue)
363 AddAnimatorsToAnimation(layoutPositionData);
366 animationPending = true;
368 // transitions have now been applied, clear stack, ready for new transitions on
369 // next layout traversal.
370 layoutTransitionDataQueue.Clear();
372 return animationPending;
375 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
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)
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);
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);
396 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
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)
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);
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);
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);
425 void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
427 if (transitionsToAnimate?.Count > 0)
429 foreach (LayoutTransition transition in transitionsToAnimate)
431 if (transition.AnimatableProperty != AnimatableProperties.Position &&
432 transition.AnimatableProperty != AnimatableProperties.Size)
434 coreAnimation.AnimateTo(view,
435 transition.AnimatableProperty.ToString(),
436 transition.TargetValue,
437 transition.Animator.Delay,
438 transition.Animator.Duration,
439 transition.Animator.AlphaFunction);
441 NUILog.Debug("LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
442 " Property:" + transition.AnimatableProperty.ToString() +
443 " delay:" + transition.Animator.Delay +
444 " duration:" + transition.Animator.Duration);
451 /// Iterate transitions and replace Position Components if replacements found in list.
453 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
454 AnimatableProperties propertyToMatch,
455 ref TransitionComponents transitionComponentToUpdate)
457 foreach (LayoutTransition transition in sourceTransitionList)
459 if (transition.AnimatableProperty == propertyToMatch)
461 // Matched property to animate is for the propertyToMatch so use provided Animator.
462 transitionComponentToUpdate = transition.Animator;
467 private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
469 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
470 return new TransitionComponents(delay, duration, alphaFunction);
474 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
476 private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
478 LayoutTransition positionTransition = new LayoutTransition();
479 LayoutTransition sizeTransition = new LayoutTransition();
480 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
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))
486 conditionForAnimators = TransitionCondition.LayoutChanged;
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);
493 bool matchedCustomTransitions = false;
496 TransitionList transitionsForCurrentCondition = new TransitionList();
497 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
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))
503 // Child has transitions for the condition
504 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
507 if (!matchedCustomTransitions)
509 // Inherit parent transitions as none already set on View for the condition.
510 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
511 if (layoutParent != null)
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))
520 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
521 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
522 transitionsForCurrentCondition);
528 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
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.
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);
541 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
542 AnimatableProperties.Size,
543 ref sizeTransitionComponents);
545 // Add animators to the core Animation,
547 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
549 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
551 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
553 // Dispose components
554 positionTransitionComponents.Dispose();
555 sizeTransitionComponents.Dispose();
558 } // class LayoutController
559 } // namespace Tizen.NUI