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 private global::System.Runtime.InteropServices.HandleRef unmanagedLayoutController;
37 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
38 internal delegate void Callback(int id);
40 event Callback _instance;
42 // A Flag to check if it is already disposed.
43 private bool disposed = false;
45 private Window _window;
47 Animation _coreAnimation;
49 private List<LayoutData> _layoutTransitionDataQueue;
51 private List<LayoutItem> _itemRemovalQueue;
54 /// Constructs a LayoutController which controls the measuring and layouting.<br />
55 /// <param name="window">Window attach this LayoutController to.</param>
57 public LayoutController(Window window)
59 global::System.IntPtr cPtr = Interop.LayoutController.LayoutController_New();
61 // Wrap cPtr in a managed handle.
62 unmanagedLayoutController = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
64 _instance = new Callback(Process);
65 Interop.LayoutController.LayoutController_SetCallback(unmanagedLayoutController, _instance);
67 _layoutTransitionDataQueue = new List<LayoutData>();
71 /// Get the unique id of the LayoutController
75 return Interop.LayoutController.LayoutController_GetId(unmanagedLayoutController);
79 /// Request relayout of a LayoutItem and it's parent tree.
81 /// <param name="layoutItem">LayoutItem that is required to be laid out again.</param>
82 public void RequestLayout(LayoutItem layoutItem)
84 // Go up the tree and mark all parents to relayout
85 ILayoutParent layoutParent = layoutItem.GetParent();
86 if (layoutParent != null)
88 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
89 if(! layoutGroup.LayoutRequested)
91 layoutGroup.RequestLayout();
97 /// Get the Layouting animation object that transitions layouts and content.
98 /// Use OverrideCoreAnimation to explicitly control Playback.
100 /// <returns> The layouting core Animation. </returns>
101 [EditorBrowsable(EditorBrowsableState.Never)]
102 public Animation GetCoreAnimation()
104 return _coreAnimation;
108 /// Set or Get Layouting core animation override property.
109 /// Gives explicit control over the Layouting animation playback if set to True.
110 /// Reset to False if explicit control no longer required.
112 [EditorBrowsable(EditorBrowsableState.Never)]
113 public bool OverrideCoreAnimation {get;set;} = false;
116 /// Destructor which adds LayoutController to the Dispose queue.
125 public void Dispose()
127 Dispose(DisposeTypes.Explicit);
131 /// Add transition data for a LayoutItem to the transition stack.
133 /// <param name="transitionDataEntry">Transition data for a LayoutItem.</param>
134 internal void AddTransitionDataEntry( LayoutData transitionDataEntry)
136 _layoutTransitionDataQueue.Add(transitionDataEntry);
140 /// Add LayoutItem to a removal stack for removal after transitions finish.
142 /// <param name="itemToRemove">LayoutItem to remove.</param>
143 internal void AddToRemovalStack( LayoutItem itemToRemove)
145 if (_itemRemovalQueue == null)
147 _itemRemovalQueue = new List<LayoutItem>();
149 _itemRemovalQueue.Add(itemToRemove);
153 /// Dispose Explict or Implicit
155 protected override void Dispose(DisposeTypes type)
162 //Release your own unmanaged resources here.
163 //You should not access any managed member here except static instance.
164 //because the execution order of Finalizes is non-deterministic.
166 if (unmanagedLayoutController.Handle != global::System.IntPtr.Zero)
168 Interop.LayoutController.delete_LayoutController(unmanagedLayoutController);
169 unmanagedLayoutController = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
175 // Traverse through children from the provided root.
176 // If a child has no layout but is a pure View then assign a default layout and continue traversal.
177 // If child has no layout and not a pure View then assign a LayoutItem that terminates the layouting (leaf),
178 private void AutomaticallyAssignLayouts(View rootNode)
180 for (uint i = 0; i < rootNode.ChildCount; i++)
182 View view = rootNode.GetChildAt(i);
183 if (rootNode.Layout == null )
185 if (rootNode.GetType() == typeof(View))
187 rootNode.Layout = new LayoutGroup();
188 AutomaticallyAssignLayouts(rootNode);
192 rootNode.Layout = new LayoutItem();
198 // Traverse the tree looking for a root node that is a layout.
199 // Once found, it's children can be assigned Layouts and the Measure process started.
200 private void FindRootLayouts(View rootNode)
202 if (rootNode.Layout != null)
204 // rootNode has a layout, ensure all children have default layouts or layout items.
205 AutomaticallyAssignLayouts(rootNode);
206 // rootNode has a layout, start measuring and layouting from here.
207 MeasureAndLayout(rootNode);
211 // Search children of supplied node for a layout.
212 for (uint i = 0; i < rootNode.ChildCount; i++)
214 View view = rootNode.GetChildAt(i);
215 FindRootLayouts(view);
220 // Starts of the actual measuring and layouting from the given root node.
221 // Can be called from multiple starting roots but in series.
222 void MeasureAndLayout(View root)
226 // Get parent MeasureSpecification, this could be the Window or View with an exact size.
227 Container parentNode = root.GetParent();
229 Position2D rootPosition = root.Position2D;
230 View parentView = parentNode as View;
233 // Get parent View's Size. If using Legacy size negotiation then should have been set already.
234 rootSize = new Size2D(parentView.Size2D.Width, parentView.Size2D.Height);
238 // Parent not a View so assume it's a Layer which is the size of the window.
239 rootSize = new Size2D(_window.Size.Width, _window.Size.Height);
242 // Determine measure specification for root.
243 // The root layout policy could be an exact size, be match parent or wrap children.
244 // If wrap children then at most it can be the root parent size.
245 // If match parent then should be root parent size.
246 // If exact then should be that size limited by the root parent size.
248 LayoutLength width = new LayoutLength(rootSize.Width);
249 LayoutLength height = new LayoutLength(rootSize.Height);
250 MeasureSpecification.ModeType widthMode = MeasureSpecification.ModeType.AtMost;
251 MeasureSpecification.ModeType heightMode = MeasureSpecification.ModeType.AtMost;
253 if ( root.WidthSpecification >= 0 )
255 // exact size provided so match width exactly
256 width = new LayoutLength(root.WidthSpecification);
257 widthMode = MeasureSpecification.ModeType.Exactly;
259 else if (root.WidthSpecification == LayoutParamPolicies.MatchParent)
262 widthMode = MeasureSpecification.ModeType.Exactly;
265 if (root.HeightSpecification >= 0 )
267 // exact size provided so match height exactly
268 height = new LayoutLength(root.HeightSpecification);
269 heightMode = MeasureSpecification.ModeType.Exactly;
271 else if (root.HeightSpecification == LayoutParamPolicies.MatchParent)
273 heightMode = MeasureSpecification.ModeType.Exactly;
276 MeasureSpecification parentWidthSpecification =
277 new MeasureSpecification( width, widthMode);
279 MeasureSpecification parentHeightSpecification =
280 new MeasureSpecification( height, heightMode);
282 // Start at root with it's parent's widthSpecification and heightSpecification
283 MeasureHierarchy(root, parentWidthSpecification, parentHeightSpecification);
285 // Start at root which was just measured.
286 PerformLayout( root, new LayoutLength(rootPosition.X),
287 new LayoutLength(rootPosition.Y),
288 new LayoutLength(rootPosition.X) + root.Layout.MeasuredWidth.Size,
289 new LayoutLength(rootPosition.Y) + root.Layout.MeasuredHeight.Size );
291 bool readyToPlay = SetupCoreAnimation();
293 if (readyToPlay && OverrideCoreAnimation==false)
301 /// Entry point into the C# Layouting that starts the Processing
303 private void Process(int id)
305 // First layer in the Window should be the default layer (index 0 )
306 uint numberOfLayers = _window.LayerCount;
307 for (uint layerIndex = 0; layerIndex < numberOfLayers; layerIndex++)
309 Layer layer = _window.GetLayer(layerIndex);
310 for (uint i = 0; i < layer.ChildCount; i++)
312 View view = layer.GetChildAt(i);
313 FindRootLayouts(view);
320 /// Starts measuring the tree, starting from the root layout.
322 private void MeasureHierarchy(View root, MeasureSpecification widthSpec, MeasureSpecification heightSpec)
324 // Does this View have a layout?
325 // Yes - measure the layout. It will call this method again for each of it's children.
326 // No - reached leaf or no layouts set
328 // If in a leaf View with no layout, it's natural size is bubbled back up.
330 if (root.Layout != null)
332 root.Layout.Measure(widthSpec, heightSpec);
337 /// Performs the layouting positioning
339 private void PerformLayout(View root, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
341 if(root.Layout != null)
343 root.Layout.Layout(left, top, right, bottom);
348 /// Play the animation.
350 private void PlayAnimation()
352 Debug.WriteLineIf( LayoutDebugController, "LayoutController Playing, Core Duration:" + _coreAnimation.Duration);
353 _coreAnimation.Play();
356 private void AnimationFinished(object sender, EventArgs e)
358 // Iterate list of LayoutItem that were set for removal.
359 // Now the core animation has finished their Views can be removed.
360 if (_itemRemovalQueue != null)
362 foreach (LayoutItem item in _itemRemovalQueue)
364 // Check incase the parent was already removed and the Owner was
368 // Check again incase the parent has already been removed.
369 ILayoutParent layoutParent = item.GetParent();
370 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
371 if (layoutGroup !=null)
373 layoutGroup.Owner?.RemoveChild(item.Owner);
378 _itemRemovalQueue.Clear();
379 // If LayoutItems added to stack whilst the core animation is playing
380 // they would have been cleared here.
381 // Could have another stack to be added to whilst the animation is running.
382 // After the running stack is cleared it can be populated with the content
383 // of the other stack. Then the main removal stack iterated when AnimationFinished
386 Debug.WriteLineIf( LayoutDebugController, "LayoutController AnimationFinished");
387 _coreAnimation?.Clear();
391 /// Set up the animation from each LayoutItems position data.
392 /// Iterates the transition stack, adding an Animator to the core animation.
394 private bool SetupCoreAnimation()
396 // Initialize animation for this layout run.
397 bool animationPending = false;
399 Debug.WriteLineIf( LayoutDebugController,
400 "LayoutController SetupCoreAnimation for:" + _layoutTransitionDataQueue.Count);
402 if (_layoutTransitionDataQueue.Count > 0 ) // Something to animate
406 _coreAnimation = new Animation();
408 _coreAnimation.EndAction = Animation.EndActions.StopFinal;
409 _coreAnimation.Finished += AnimationFinished;
411 // Iterate all items that have been queued for repositioning.
412 foreach (LayoutData layoutPositionData in _layoutTransitionDataQueue)
414 AddAnimatorsToAnimation(layoutPositionData);
417 animationPending = true;
419 // transitions have now been applied, clear stack, ready for new transitions on
420 // next layout traversal.
421 _layoutTransitionDataQueue.Clear();
423 return animationPending;
426 private void SetupAnimationForPosition(LayoutData layoutPositionData, TransitionComponents positionTransitionComponents)
428 // A removed item does not have a valid target position within the layout so don't try to position.
429 if( layoutPositionData.ConditionForAnimation != TransitionCondition.Remove )
431 _coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Position",
432 new Vector3(layoutPositionData.Left,
433 layoutPositionData.Top,
434 layoutPositionData.Item.Owner.Position.Z),
435 positionTransitionComponents.Delay,
436 positionTransitionComponents.Duration,
437 positionTransitionComponents.AlphaFunction );
439 Debug.WriteLineIf( LayoutDebugController,
440 "LayoutController SetupAnimationForPosition View:" + layoutPositionData.Item.Owner.Name +
441 " left:" + layoutPositionData.Left +
442 " top:" + layoutPositionData.Top +
443 " delay:" + positionTransitionComponents.Delay +
444 " duration:" + positionTransitionComponents.Duration );
448 private void SetupAnimationForSize(LayoutData layoutPositionData, TransitionComponents sizeTransitionComponents)
450 // Text size cant be animated so is set to it's final size.
451 // It is due to the internals of the Text not being able to recalculate fast enough.
452 if (layoutPositionData.Item.Owner is TextLabel || layoutPositionData.Item.Owner is TextField)
454 float itemWidth = layoutPositionData.Right-layoutPositionData.Left;
455 float itemHeight = layoutPositionData.Bottom-layoutPositionData.Top;
456 // Set size directly.
457 layoutPositionData.Item.Owner.Size2D = new Size2D((int)itemWidth, (int)itemHeight);
461 _coreAnimation.AnimateTo(layoutPositionData.Item.Owner, "Size",
462 new Vector3(layoutPositionData.Right-layoutPositionData.Left,
463 layoutPositionData.Bottom-layoutPositionData.Top,
464 layoutPositionData.Item.Owner.Position.Z),
465 sizeTransitionComponents.Delay,
466 sizeTransitionComponents.Duration,
467 sizeTransitionComponents.AlphaFunction);
469 Debug.WriteLineIf( LayoutDebugController,
470 "LayoutController SetupAnimationForSize View:" + layoutPositionData.Item.Owner.Name +
471 " width:" + (layoutPositionData.Right-layoutPositionData.Left) +
472 " height:" + (layoutPositionData.Bottom-layoutPositionData.Top) +
473 " delay:" + sizeTransitionComponents.Delay +
474 " duration:" + sizeTransitionComponents.Duration );
478 void SetupAnimationForCustomTransitions(TransitionList transitionsToAnimate, View view )
480 if (transitionsToAnimate?.Count > 0)
482 foreach (LayoutTransition transition in transitionsToAnimate)
484 if ( transition.AnimatableProperty != AnimatableProperties.Position &&
485 transition.AnimatableProperty != AnimatableProperties.Size )
487 _coreAnimation.AnimateTo(view,
488 transition.AnimatableProperty.ToString(),
489 transition.TargetValue,
490 transition.Animator.Delay,
491 transition.Animator.Duration,
492 transition.Animator.AlphaFunction );
494 Debug.WriteLineIf( LayoutDebugController,
495 "LayoutController SetupAnimationForCustomTransitions View:" + view.Name +
496 " Property:" + transition.AnimatableProperty.ToString() +
497 " delay:" + transition.Animator.Delay +
498 " duration:" + transition.Animator.Duration );
505 /// Iterate transitions and replace Position Components if replacements found in list.
507 private void FindAndReplaceAnimatorComponentsForProperty(TransitionList sourceTransitionList,
508 AnimatableProperties propertyToMatch,
509 ref TransitionComponents transitionComponentToUpdate)
511 foreach( LayoutTransition transition in sourceTransitionList)
513 if (transition.AnimatableProperty == propertyToMatch)
515 // Matched property to animate is for the propertyToMatch so use provided Animator.
516 transitionComponentToUpdate = transition.Animator;
521 private TransitionComponents CreateDefaultTransitionComponent( int delay, int duration )
523 AlphaFunction alphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
524 return new TransitionComponents(delay, duration, alphaFunction);
528 /// Sets up the main animation with the animators for each item (each layoutPositionData structure)
530 private void AddAnimatorsToAnimation( LayoutData layoutPositionData )
532 LayoutTransition positionTransition = new LayoutTransition();
533 LayoutTransition sizeTransition = new LayoutTransition();
534 TransitionCondition conditionForAnimators = layoutPositionData.ConditionForAnimation;
536 // LayoutChanged transitions overrides ChangeOnAdd and ChangeOnRemove as siblings will
537 // reposition to the new layout not to the insertion/removal of a sibling.
538 if (layoutPositionData.ConditionForAnimation.HasFlag(TransitionCondition.LayoutChanged))
540 conditionForAnimators = TransitionCondition.LayoutChanged;
543 // Set up a default transitions, will be overwritten if inherited from parent or set explicitly.
544 TransitionComponents positionTransitionComponents = CreateDefaultTransitionComponent(0, 100);
545 TransitionComponents sizeTransitionComponents = CreateDefaultTransitionComponent(0, 0);
547 bool matchedCustomTransitions = false;
550 TransitionList transitionsForCurrentCondition = new TransitionList();
551 // Note, Transitions set on View rather than LayoutItem so if the Layout changes the transition persist.
553 // Check if item to animate has it's own Transitions for this condition.
554 // If a key exists then a List of atleast 1 transition exists.
555 if (layoutPositionData.Item.Owner.LayoutTransitions.ContainsKey(conditionForAnimators))
557 // Child has transitions for the condition
558 matchedCustomTransitions = layoutPositionData.Item.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out transitionsForCurrentCondition);
561 if (!matchedCustomTransitions)
563 // Inherit parent transitions as none already set on View for the condition.
564 ILayoutParent layoutParent = layoutPositionData.Item.GetParent();
565 if (layoutParent !=null)
567 // Item doesn't have it's own transitions for this condition so copy parents if
568 // has a parent with transitions.
569 LayoutGroup layoutGroup = layoutParent as LayoutGroup;
570 TransitionList parentTransitionList;
571 // Note TryGetValue returns null if key not matched.
572 if (layoutGroup !=null && layoutGroup.Owner.LayoutTransitions.TryGetValue(conditionForAnimators, out parentTransitionList))
574 // Copy parent transitions to temporary TransitionList. List contains transitions for the current condition.
575 LayoutTransitionsHelper.CopyTransitions(parentTransitionList,
576 transitionsForCurrentCondition);
582 // Position/Size transitions can be displayed for a layout changing to another layout or an item being added or removed.
584 // There can only be one position transition and one size position, they will be replaced if set multiple times.
585 // transitionsForCurrentCondition represent all non position (custom) properties that should be animated.
587 // Search for Position property in the transitionsForCurrentCondition list of custom transitions,
588 // and only use the particular parts of the animator as custom transitions should not effect all parameters of Position.
589 // Typically Delay, Duration and Alphafunction can be custom.
590 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
591 AnimatableProperties.Position,
592 ref positionTransitionComponents);
595 FindAndReplaceAnimatorComponentsForProperty(transitionsForCurrentCondition,
596 AnimatableProperties.Size,
597 ref sizeTransitionComponents);
599 // Add animators to the core Animation,
601 SetupAnimationForCustomTransitions(transitionsForCurrentCondition, layoutPositionData.Item.Owner);
603 SetupAnimationForPosition(layoutPositionData, positionTransitionComponents);
605 SetupAnimationForSize(layoutPositionData, sizeTransitionComponents);
608 } // class LayoutController
609 } // namespace Tizen.NUI