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 System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
27 /// [Draft] LayoutGroup class providing container functionality.
29 internal class LayoutGroup : LayoutItem, ILayoutParent
31 protected List<LayoutItem> _children{ get;} // Children of this LayoutGroup
34 /// [Draft] Constructor
38 _children = new List<LayoutItem>();
42 /// [Draft] Constructor setting the owner of this LayoutGroup.
44 /// <param name="owner">Owning View of this layout, currently a View but may be extending for Windows/Layers.</param>
45 public LayoutGroup(View owner) : base(owner)
47 _children = new List<LayoutItem>();
51 /// From ILayoutParent.<br />
53 public virtual void Add(LayoutItem childLayout)
55 _children.Add(childLayout);
56 childLayout.SetParent(this);
57 // Child added to use a Add transition.
58 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Add;
59 // Child's parent sets all other children not being added to a ChangeOnAdd transition.
60 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnAdd);
61 OnChildAdd(childLayout);
66 /// Remove all layout children.<br />
68 public void RemoveAll()
70 foreach( LayoutItem childLayout in _children )
72 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
73 childLayout.Owner = null;
76 // todo ensure child LayoutItems are still not parented to this group.
81 /// From ILayoutParent
83 public virtual void Remove(LayoutItem layoutItem)
85 bool childRemoved = false;
86 foreach( LayoutItem childLayout in _children.ToList() )
88 if( childLayout == layoutItem )
90 Window.Instance.LayoutController.AddToRemovalStack(childLayout);
91 _children.Remove(childLayout);
92 childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
93 // Add LayoutItem to the transition stack so can animate it out.
94 Window.Instance.LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0,0,0,0));
95 // Reset condition for animation ready for next transition when required.
96 // SetFrame usually would do this but this LayoutItem is being removed.
97 childLayout.ConditionForAnimation = TransitionCondition.Unspecified;
103 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
104 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
109 // Attaches to View ChildAdded signal so called when a View is added to a view.
110 private void AddChildToLayoutGroup(View child)
112 // Only give children a layout if their parent is an explicit container or a pure View.
113 // Pure View meaning not derived from a View, e.g a Legacy container.
114 // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
115 // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
117 // If child already has a Layout then don't change it.
118 if (! View.layoutingDisabled && (null == child.Layout))
120 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
121 if ((true == Owner.layoutSet || GetType() == typeof(View)))
123 // If child of this layout is a pure View then assign it a LayoutGroup
124 // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
125 if (child.GetType() == typeof(View))
127 child.Layout = new LayoutGroup();
131 // Adding child as a leaf, layouting will not propagate past this child.
132 // Legacy containers will be a LayoutItems too and layout their children how they wish.
133 child.Layout = new LayoutItem();
139 // Add child layout to this LayoutGroup (Setting parent in the process)
140 if(child.Layout != null)
145 // Parent transitions are not attached to children.
149 /// If the child has a layout then it is removed from the parent layout.
151 /// <param name="child">Child View to remove.</param>
152 internal void RemoveChildFromLayoutGroup(View child)
154 Debug.Assert(child.Layout !=null);
155 Remove(child.Layout);
159 /// Set all children in a LayoutGroup to the supplied condition.
160 /// Children with Add or Remove conditions should not be changed.
162 private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
164 foreach( LayoutItem childLayout in _children )
166 switch( conditionToSet )
168 case TransitionCondition.ChangeOnAdd :
170 // If other children also being added (TransitionCondition.Add) then do not change their
171 // conditions, Continue to use their Add transitions.
172 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
174 break; // Child being Added so don't update it's condition
178 // Set siblings for the child being added to use the ChangeOnAdd transition.
179 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
183 case TransitionCondition.ChangeOnRemove :
185 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
187 break; // Child being Removed so don't update it's condition
191 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
195 case TransitionCondition.LayoutChanged :
197 childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
206 /// Callback for View.ChildAdded event
208 /// <param name="sender">The object triggering the event.</param>
209 /// <param name="childAddedEvent">Arguments from the event.</param>
210 void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
212 AddChildToLayoutGroup(childAddedEvent.Added);
216 /// Callback for View.ChildRemoved event
218 /// <param name="sender">The object triggering the event.</param>
219 /// <param name="childRemovedEvent">Arguments from the event.</param>
220 void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
222 RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
226 /// Calculate the right measure spec for this child.
227 /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
228 /// pass to a particular child. This method figures out the right MeasureSpec
229 /// for one dimension (height or width) of one child view.<br />
231 /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
232 /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
233 /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
234 /// <returns>a MeasureSpec for the child.</returns>
235 public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
237 MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
238 MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
239 LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, (parentMeasureSpec.Size.AsDecimal() - padding.AsDecimal() ) )); // reduce available size by the owners padding
243 // Parent has imposed an exact size on us
244 case MeasureSpecification.ModeType.Exactly:
246 if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
248 // Child wants to be our size. So be it.
249 resultMode = MeasureSpecification.ModeType.Exactly;
251 else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
253 // Child wants to determine its own size. It can't be
255 resultMode = MeasureSpecification.ModeType.AtMost;
259 resultSize = childDimension;
260 resultMode = MeasureSpecification.ModeType.Exactly;
266 // Parent has imposed a maximum size on us
267 case MeasureSpecification.ModeType.AtMost:
269 if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
271 // Child wants to be our size, but our size is not fixed.
272 // Constrain child to not be bigger than us.
273 resultMode = MeasureSpecification.ModeType.AtMost;
275 else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
277 // Child wants to determine its own size. It can't be
279 resultMode = MeasureSpecification.ModeType.AtMost;
283 // Child wants a specific size... so be it
284 resultSize = childDimension + padding;
285 resultMode = MeasureSpecification.ModeType.Exactly;
291 // Parent asked to see how big we want to be
292 case MeasureSpecification.ModeType.Unspecified:
295 if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
297 // Child wants to be our size... find out how big it should be
298 resultMode = MeasureSpecification.ModeType.Unspecified;
300 else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
302 // Child wants to determine its own size.... find out how big
304 resultMode = MeasureSpecification.ModeType.Unspecified;
308 // Child wants a specific size... let him have it
309 resultSize = childDimension + padding;
310 resultMode = MeasureSpecification.ModeType.Exactly;
316 return new MeasureSpecification( resultSize, resultMode );
320 /// Measure the layout and its content to determine the measured width and the measured height.<br />
321 /// If this method is overridden, it is the subclass's responsibility to make
322 /// sure the measured height and width are at least the layout's minimum height
323 /// and width. <br />
325 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
326 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
327 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
329 LayoutLength measuredWidth = new LayoutLength(0.0f);
330 LayoutLength measuredHeight = new LayoutLength(0.0f);
332 // Layout takes size of largest child width and largest child height dimensions
333 foreach( LayoutItem childLayout in _children )
335 if( childLayout != null )
337 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
338 LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
339 LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size);
341 Extents childMargin = childLayout.Margin;
342 measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
343 measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
347 if( 0 == _children.Count )
349 // Must be a leaf as has no children
350 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
351 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
354 SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
355 new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
359 /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
360 /// Derived classes with children should override this method and call Layout() on each of their children.<br />
362 /// <param name="changed">This is a new size or position for this layout.</param>
363 /// <param name="left">Left position, relative to parent.</param>
364 /// <param name="top"> Top position, relative to parent.</param>
365 /// <param name="right">Right position, relative to parent.</param>
366 /// <param name="bottom">Bottom position, relative to parent.</param>
367 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
369 foreach( LayoutItem childLayout in _children )
371 if( childLayout !=null )
373 // Use position if explicitly set to child otherwise will be top left.
374 var childLeft = new LayoutLength( childLayout.Owner.Position2D.X );
375 var childTop = new LayoutLength( childLayout.Owner.Position2D.Y );
381 // Margin and Padding only supported when child anchor point is TOP_LEFT.
382 if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
384 childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
385 childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
388 childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
394 /// Overridden method called when the layout size changes.<br />
396 /// <param name="newSize">The new size of the layout.</param>
397 /// <param name="oldSize">The old size of the layout.</param>
398 protected override void OnSizeChanged(LayoutSize newSize, LayoutSize oldSize)
404 /// Overridden method called when the layout is attached to an owner.<br />
406 protected override void OnAttachedToOwner()
408 // Layout takes ownership of it's owner's children.
409 foreach (View view in Owner.Children)
411 AddChildToLayoutGroup(view);
414 // Connect to owner ChildAdded signal.
415 Owner.ChildAdded += OnChildAddedToOwner;
417 // Removing Child from the owners View will directly call the LayoutGroup removal API.
420 // Virtual Methods that can be overridden by derived classes.
423 /// Callback when child is added to container.<br />
424 /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
426 /// <param name="child">The Layout child.</param>
427 protected virtual void OnChildAdd(LayoutItem child)
432 /// Callback when child is removed from container.<br />
434 /// <param name="child">The Layout child.</param>
435 protected virtual void OnChildRemove(LayoutItem child)
440 /// Ask all of the children of this view to measure themselves, taking into
441 /// account both the MeasureSpec requirements for this view and its padding.<br />
442 /// The heavy lifting is done in GetChildMeasureSpec.<br />
444 /// <param name="widthMeasureSpec">The width requirements for this view.</param>
445 /// <param name="heightMeasureSpec">The height requirements for this view.</param>
446 protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
448 foreach( LayoutItem childLayout in _children )
450 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
455 /// Ask one of the children of this view to measure itself, taking into
456 /// account both the MeasureSpec requirements for this view and its padding.<br />
457 /// The heavy lifting is done in GetChildMeasureSpec.<br />
459 /// <param name="child">The child to measure.</param>
460 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
461 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
462 protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
464 View childOwner = child.Owner;
466 Extents padding = child.Padding; // Padding of this layout's owner, not of the child being measured.
468 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec,
469 new LayoutLength(padding.Start + padding.End ),
470 new LayoutLength(childOwner.WidthSpecification) );
472 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec,
473 new LayoutLength(padding.Top + padding.Bottom),
474 new LayoutLength(childOwner.HeightSpecification) );
476 child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
480 /// Ask one of the children of this view to measure itself, taking into
481 /// account both the MeasureSpec requirements for this view and its padding.<br />
482 /// and margins. The child must have MarginLayoutParams The heavy lifting is
483 /// done in GetChildMeasureSpecification.<br />
485 /// <param name="child">The child to measure.</param>
486 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
487 /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
488 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
489 /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
490 protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
492 View childOwner = child.Owner;
493 int desiredWidth = childOwner.WidthSpecification;
494 int desiredHeight = childOwner.HeightSpecification;
496 Extents padding = child.Padding; // Padding of this layout's owner, not of the child being measured.
498 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec,
499 new LayoutLength( padding.Start + padding.End ) +
500 widthUsed, new LayoutLength(desiredWidth) );
503 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec,
504 new LayoutLength( padding.Top + padding.Bottom )+
505 heightUsed, new LayoutLength(desiredHeight) );
507 child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );