2 * Copyright (c) 2019 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 static bool LayoutDebugController = false; // Debug flag
35 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
36 internal delegate void Callback(int id);
38 event Callback _instance;
40 private Window _window;
42 Animation _coreAnimation;
44 private List<LayoutData> _layoutTransitionDataQueue;
46 private List<LayoutItem> _itemRemovalQueue;
49 /// Constructs a LayoutController which controls the measuring and layouting.<br />
50 /// <param name="window">Window attach this LayoutController to.</param>
52 public LayoutController(Window window) : this(Interop.LayoutController.LayoutController_New(), true)
55 _instance = new Callback(Process);
56 Interop.LayoutController.LayoutController_SetCallback(swigCPtr, _instance);
58 _layoutTransitionDataQueue = new List<LayoutData>();
61 internal LayoutController(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
66 /// Get the unique id of the LayoutController
70 return Interop.LayoutController.LayoutController_GetId(swigCPtr);
74 /// Request relayout of a LayoutItem and it's parent tree.
76 /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
77 public void RequestLayout(LayoutItem layoutItem)
79 // Go up the tree and mark all parents to relayout
80 ILayoutParent layoutParent = layoutItem.GetParent();
81 if (layoutParent != null)
83 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
84 if (layoutGroup != null && !layoutGroup.LayoutRequested)
86 layoutGroup.RequestLayout();
92 /// Get the Layouting animation object that transitions layouts and content.
93 /// Use OverrideCoreAnimation to explicitly control Playback.
95 /// <returns> The layouting core Animation. </returns>
96 [EditorBrowsable(EditorBrowsableState.Never)]
97 public Animation GetCoreAnimation()
99 return _coreAnimation;
103 /// Set or Get Layouting core animation override property.
104 /// Gives explicit control over the Layouting animation playback if set to True.
105 /// Reset to False if explicit control no longer required.
107 [EditorBrowsable(EditorBrowsableState.Never)]
108 public bool OverrideCoreAnimation { get; set; } = false;
111 /// Add transition data for a LayoutItem to the transition stack.
113 /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
114 internal void AddTransitionDataEntry(LayoutData transitionDataEntry)
116 _layoutTransitionDataQueue.Add(transitionDataEntry);
120 /// Add LayoutItem to a removal stack for removal after transitions finish.
122 /// <param name="itemToRemove">LayoutItem to remove.</param>
123 internal void AddToRemovalStack(LayoutItem itemToRemove)
125 if (_itemRemovalQueue == null)
127 _itemRemovalQueue = new List<LayoutItem>();
129 _itemRemovalQueue.Add(itemToRemove);
133 /// Dispose Explict or Implicit
135 protected override void Dispose(DisposeTypes type)
142 //Release your own unmanaged resources here.
143 //You should not access any managed member here except static instance.
144 //because the execution order of Finalizes is non-deterministic.
146 if (swigCPtr.Handle != global::System.IntPtr.Zero)
148 Interop.LayoutController.delete_LayoutController(swigCPtr);
149 swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
155 // Traverse the tree looking for a root node that is a layout.
156 // Once found, it's children can be assigned Layouts and the Measure process started.
157 private void FindRootLayouts(View rootNode)
159 if (rootNode.Layout != null)
161 Debug.WriteLineIf(LayoutDebugController, "LayoutController Root found:" + rootNode.Name);
162 // rootNode has a layout, start measuring and layouting from here.
163 MeasureAndLayout(rootNode);
167 // Search children of supplied node for a layout.
168 for (uint i = 0; i < rootNode.ChildCount; i++)
170 View view = rootNode.GetChildAt(i);
171 FindRootLayouts(view);
176 // Starts of the actual measuring and layouting from the given root node.
177 // Can be called from multiple starting roots but in series.
178 void MeasureAndLayout(View root)
182 // Get parent MeasureSpecification, this could be the Window or View with an exact size.
183 Container parentNode = root.GetParent();
185 Position2D rootPosition = root.Position2D;
186 View parentView = parentNode as View;
187 if (parentView != null)
189 // Get parent View's Size. If using Legacy size negotiation then should have been set already.
190 rootSize = new Size2D(parentView.Size2D.Width, parentView.Size2D.Height);
194 // Parent not a View so assume it's a Layer which is the size of the window.
195 rootSize = new Size2D(_window.Size.Width, _window.Size.Height);
198 // Determine measure specification for root.
199 // The root layout policy could be an exact size, be match parent or wrap children.
200 // If wrap children then at most it can be the root parent size.
201 // If match parent then should be root parent size.
202 // If exact then should be that size limited by the root parent size.
204 LayoutLength width = new LayoutLength(rootSize.Width);
205 LayoutLength height = new LayoutLength(rootSize.Height);
206 MeasureSpecification.ModeType widthMode = MeasureSpecification.ModeType.Unspecified;
207 MeasureSpecification.ModeType heightMode = MeasureSpecification.ModeType.Unspecified;
209 if (root.WidthSpecification >= 0)
211 // exact size provided so match width exactly
212 width = new LayoutLength(root.WidthSpecification);
213 widthMode = MeasureSpecification.ModeType.Exactly;
215 else if (root.WidthSpecification == LayoutParamPolicies.MatchParent)
218 widthMode = MeasureSpecification.ModeType.Exactly;
221 if (root.HeightSpecification >= 0)
223 // exact size provided so match height exactly
224 height = new LayoutLength(root.HeightSpecification);
225 heightMode = MeasureSpecification.ModeType.Exactly;
227 else if (root.HeightSpecification == LayoutParamPolicies.MatchParent)
229 heightMode = MeasureSpecification.ModeType.Exactly;
232 MeasureSpecification parentWidthSpecification =
233 new MeasureSpecification(width, widthMode);
235 MeasureSpecification parentHeightSpecification =
236 new MeasureSpecification(height, heightMode);
238 // Start at root with it's parent's widthSpecification and heightSpecification
239 MeasureHierarchy(root, parentWidthSpecification, parentHeightSpecification);
241 // Start at root which was just measured.
242 PerformLayout(root, new LayoutLength(rootPosition.X),
243 new LayoutLength(rootPosition.Y),
244 new LayoutLength(rootPosition.X) + root.Layout.MeasuredWidth.Size,
245 new LayoutLength(rootPosition.Y) + root.Layout.MeasuredHeight.Size);
247 bool readyToPlay = SetupCoreAnimation();
249 if (readyToPlay && OverrideCoreAnimation == false)
257 /// Entry point into the C# Layouting that starts the Processing
259 private void Process(int id)
261 // First layer in the Window should be the default layer (index 0 )
262 uint numberOfLayers = _window.LayerCount;
263 for (uint layerIndex = 0; layerIndex < numberOfLayers; layerIndex++)
265 Layer layer = _window.GetLayer(layerIndex);
268 for (uint i = 0; i < layer.ChildCount; i++)
270 View view = layer.GetChildAt(i);
271 FindRootLayouts(view);
279 /// Starts measuring the tree, starting from the root layout.
281 private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
283 // Does this View have a layout?
284 // Yes - measure the layout. It will call this method again for each of it's children.
285 // No - reached leaf or no layouts set
287 // If in a leaf View with no layout, it's natural size is bubbled back up.
289 if (root.Layout != null)
291 root.Layout.Measure(widthSpec, heightSpec);
296 /// Performs the layouting positioning
298 private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
300 if (root.Layout != null)
302 root.Layout.Layout(left, top, right, bottom);
307 /// Play the animation.
309 private void PlayAnimation()
311 Debug.WriteLineIf(LayoutDebugController, "LayoutController Playing, Core Duration:" + _coreAnimation.Duration);
312 _coreAnimation.Play();
315 private void AnimationFinished(object sender, EventArgs e)
317 // Iterate list of LayoutItem that were set for removal.
318 // Now the core animation has finished their Views can be removed.
319 if (_itemRemovalQueue != null)
321 foreach (LayoutItem item in _itemRemovalQueue)
323 // Check incase the parent was already removed and the Owner was
327 // Check again incase the parent has already been removed.
328 ILayoutParent layoutParent = item.GetParent();
329 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
330 if (layoutGroup != null)
332 layoutGroup.Owner?.RemoveChild(item.Owner);
337 _itemRemovalQueue.Clear();
338 // If LayoutItems added to stack whilst the core animation is playing
339 // they would have been cleared here.
340 // Could have another stack to be added to whilst the animation is running.
341 // After the running stack is cleared it can be populated with the content
342 // of the other stack. Then the main removal stack iterated when AnimationFinished
345 Debug.WriteLineIf(LayoutDebugController, "LayoutController AnimationFinished");
346 _coreAnimation?.Clear();
350 /// Set up the animation from each LayoutItems position data.
351 /// Iterates the transition stack, adding an Animator to the core animation.
353 private bool SetupCoreAnimation()
355 // Initialize animation for this layout run.
356 bool animationPending = false;
358 Debug.WriteLineIf(LayoutDebugController,
359 "LayoutController SetupCoreAnimation for:" + _layoutTransitionDataQueue.Count);
361 if (_layoutTransitionDataQueue.Count > 0) // Something to animate
365 _coreAnimation = new Animation();
367 _coreAnimation.EndAction = Animation.EndActions.StopFinal;
368 _coreAnimation.Finished += AnimationFinished;
370 // Iterate all items that have been queued for repositioning.
371 foreach (LayoutData layoutPositionData in _layoutTransitionDataQueue)
373 AddAnimatorsToAnimation(layoutPositionData);
376 animationPending = true;
378 // transitions have now been applied, clear stack, ready for new transitions on
379 // next layout traversal.
380 _layoutTransitionDataQueue.Clear();
382 return animationPending;
385 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
387 // A removed item does not have a valid target position within the layout so don't try to position.
388 if (layoutPositionData.ConditionForAnimation != TransitionCondition.Remove)
390 _coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
391 new Vector3(layoutPositionData.Left,
392 layoutPositionData.Top,
393 layoutPositionData.Item.Owner.Position.Z),
394 positionTransitionComponents.Delay,
395 positionTransitionComponents.Duration,
396 positionTransitionComponents.AlphaFunction);
398 Debug.WriteLineIf(LayoutDebugController,
399 "LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
400 " left:" + layoutPositionData.Left +
401 " top:" + layoutPositionData.Top +
402 " delay:" + positionTransitionComponents.Delay +
403 " duration:" + positionTransitionComponents.Duration);
407 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
409 // Text size cant be animated so is set to it's final size.
410 // It is due to the internals of the Text not being able to recalculate fast enough.
411 if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
413 float itemWidth = layoutPositionData.Right - layoutPositionData.Left;
414 float itemHeight = layoutPositionData.Bottom - layoutPositionData.Top;
415 // Set size directly.
416 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
420 _coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
421 new Vector3(layoutPositionData.Right - layoutPositionData.Left,
422 layoutPositionData.Bottom - layoutPositionData.Top,
423 layoutPositionData.Item.Owner.Position.Z),
424 sizeTransitionComponents.Delay,
425 sizeTransitionComponents.Duration,
426 sizeTransitionComponents.AlphaFunction);
428 Debug.WriteLineIf(LayoutDebugController,
429 "LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
430 " width:" + (layoutPositionData.Right - layoutPositionData.Left) +
431 " height:" + (layoutPositionData.Bottom - layoutPositionData.Top) +
432 " delay:" + sizeTransitionComponents.Delay +
433 " duration:" + sizeTransitionComponents.Duration);
437 void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view)
439 if (transitionsToAnimate?.Count > 0)
441 foreach (LayoutTransition transition in transitionsToAnimate)
443 if (transition.AnimatableProperty != AnimatableProperties.Position &&
444 transition.AnimatableProperty != AnimatableProperties.Size)
446 _coreAnimation.AnimateTo(view,
447 transition.AnimatableProperty.ToString(),
448 transition.TargetValue,
449 transition.Animator.Delay,
450 transition.Animator.Duration,
451 transition.Animator.AlphaFunction);
453 Debug.WriteLineIf(LayoutDebugController,
454 "LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
455 " Property:" + transition.AnimatableProperty.ToString() +
456 " delay:" + transition.Animator.Delay +
457 " duration:" + transition.Animator.Duration);
464 /// Iterate transitions and replace Position Components if replacements found in list.
466 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
467 AnimatableProperties propertyToMatch,
468 ref TransitionComponents transitionComponentToUpdate)
470 foreach (LayoutTransition transition in sourceTransitionList)
472 if (transition.AnimatableProperty == propertyToMatch)
474 // Matched property to animate is for the propertyToMatch so use provided Animator.
475 transitionComponentToUpdate = transition.Animator;
480 private TransitionComponents CreateDefaultTransitionComponent(int delay, int duration)
482 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
483 return new TransitionComponents(delay, duration, alphaFunction);
487 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
489 private void AddAnimatorsToAnimation(LayoutData layoutPositionData)
491 LayoutTransition positionTransition = new LayoutTransition();
492 LayoutTransition sizeTransition = new LayoutTransition();
493 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
495 // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
496 // reposition to the new layout not to the insertion/removal of a sibling.
497 if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
499 conditionForAnimators = TransitionCondition.LayoutChanged;
502 // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
503 TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 300);
504 TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 300);
506 bool matchedCustomTransitions = false;
509 TransitionList transitionsForCurrentCondition = new TransitionList();
510 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
512 // Check if item to animate has it's own Transitions for this condition.
513 // If a key exists then a List of atleast 1 transition exists.
514 if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
516 // Child has transitions for the condition
517 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
520 if (!matchedCustomTransitions)
522 // Inherit parent transitions as none already set on View for the condition.
523 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
524 if (layoutParent != null)
526 // Item doesn't have it's own transitions for this condition so copy parents if
527 // has a parent with transitions.
528 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
529 TransitionList parentTransitionList;
530 // Note TryGetValue returns null if key not matched.
531 if (layoutGroup != null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
533 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
534 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
535 transitionsForCurrentCondition);
541 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
543 // There can only be one position transition and one size position, they will be replaced if set multiple times.
544 // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
546 // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
547 // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
548 // Typically Delay, Duration and Alphafunction can be custom.
549 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
550 AnimatableProperties.Position,
551 ref positionTransitionComponents);
554 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
555 AnimatableProperties.Size,
556 ref sizeTransitionComponents);
558 // Add animators to the core Animation,
560 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
562 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
564 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
567 } // class LayoutController
568 } // namespace Tizen.NUI