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.
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
27 enum LayoutFlags : short
32 MeasuredDimensionSet = 4
36 /// [Draft] Base class for layouts. It is used to layout a View
37 /// It can be laid out by a LayoutGroup.
39 internal class LayoutItem
41 static bool LayoutDebugFrameData = false; // Debug flag
42 private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
43 private MeasureSpecification OldHeightMeasureSpec;// Store measure specification to compare against later
45 private LayoutFlags Flags = LayoutFlags.None;
47 private ILayoutParent Parent;
49 LayoutData _layoutPositionData;
51 private Extents _padding;
52 private Extents _margin;
54 public TransitionCondition ConditionForAnimation{get; set;}
57 /// [Draft] The View that this Layout has been assigned to.
59 public View Owner{get; set;} // Should not keep a View alive.
62 /// [Draft] Is this Layout set to animate its content.
64 public bool Animate{get; set;}
67 /// [Draft] Margin for this LayoutItem
83 /// [Draft] Padding for this LayoutItem
85 public Extents Padding
99 /// [Draft] Constructor
107 /// [Draft] Constructor setting the owner of this LayoutItem.
109 /// <param name="owner">Owning View of this layout, currently a View but may be extending for Windows/Layers.</param>
110 public LayoutItem(View owner)
117 /// [Draft] Set parent to this layout.
119 /// <param name="parent">Parent to set on this Layout.</param>
120 public void SetParent( ILayoutParent parent)
122 Parent = parent as LayoutGroup;
126 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
128 public void Unparent()
130 // Enable directly derived types to first remove children
133 // Remove myself from parent
134 Parent?.Remove( this );
136 // Remove parent reference
139 // Lastly, clear layout from owning View.
140 Owner?.ResetLayout();
143 private void Initialize()
145 _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
146 _padding = new Extents(0,0,0,0);
147 _margin = new Extents(0,0,0,0);
151 /// Get the View owning this LayoutItem
153 internal View GetOwner()
159 /// Initialize the layout and allow derived classes to also perform any operations
161 /// <param name="owner">Owner of this Layout.</param>
162 internal void AttachToOwner(View owner)
164 // Assign the layout owner.
167 // Add layout to parent layout if a layout container
168 View parent = Owner.GetParent() as View;
169 (parent?.Layout as LayoutGroup)?.Add( this );
171 // If Add or ChangeOnAdd then do not update condition
172 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
174 ConditionForAnimation = TransitionCondition.LayoutChanged;
179 /// This is called to find out how big a layout should be. <br />
180 /// The parent supplies constraint information in the width and height parameters. <br />
181 /// The actual measurement work of a layout is performed in OnMeasure called by this
182 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
184 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
185 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
186 internal void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
188 // Check if relayouting is required.
189 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
190 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
191 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
192 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
194 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
195 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
197 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
198 (MeasuredHeight.Size == heightMeasureSpec.Size);
200 bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
201 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
205 OnMeasure(widthMeasureSpec, heightMeasureSpec);
206 Flags = Flags | LayoutFlags.LayoutRequired;
207 Flags &= ~LayoutFlags.ForceLayout;
209 OldWidthMeasureSpec = widthMeasureSpec;
210 OldHeightMeasureSpec = heightMeasureSpec;
214 /// Assign a size and position to a layout and all of its descendants. <br />
215 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
216 /// calls layout on all of its children to position them. This is typically done using the child<br />
217 /// measurements that were stored in the measure pass.<br />
219 /// <param name="left">Left position, relative to parent.</param>
220 /// <param name="top">Top position, relative to parent.</param>
221 /// <param name="right">Right position, relative to parent.</param>
222 /// <param name="bottom">Bottom position, relative to parent.</param>
223 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
225 bool changed = SetFrame(left.AsRoundedValue(),
226 top.AsRoundedValue(),
227 right.AsRoundedValue(),
228 bottom.AsRoundedValue());
230 // Check if Measure needed before Layouting
231 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
233 OnLayout(changed, left, top, right, bottom);
235 Flags &= ~LayoutFlags.LayoutRequired;
240 /// Utility to return a default size.<br />
241 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
242 /// MeasureSpecification.<br />
244 /// <param name="size"> Default size for this layout.</param>
245 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
246 /// <returns>The size this layout should be.</returns>
247 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
249 LayoutLength result = size;
250 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
251 LayoutLength specSize = measureSpecification.Size;
255 case MeasureSpecification.ModeType.Unspecified:
260 case MeasureSpecification.ModeType.AtMost:
262 // Ensure the default size does not exceed the spec size unless the default size is 0.
263 // Another container could provide a default size of 0.
265 // Do not set size to 0, use specSize in this case as could be a legacy container
266 if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
276 case MeasureSpecification.ModeType.Exactly:
286 public ILayoutParent GetParent()
292 /// Request that this layout is re-laid out.<br />
293 /// This will make this layout and all it's parent layouts dirty.<br />
295 public void RequestLayout()
297 Flags = Flags | LayoutFlags.ForceLayout;
298 Window.Instance.LayoutController.RequestLayout(this);
302 /// Predicate to determine if this layout has been requested to re-layout.<br />
304 public bool LayoutRequested
308 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
313 /// Get the measured width (without any measurement flags).<br />
314 /// This method should be used only during measurement and layout calculations.<br />
316 public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
319 /// Get the measured height (without any measurement flags).<br />
320 /// This method should be used only during measurement and layout calculations.<br />
322 public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
325 /// Get the measured width and state.<br />
326 /// This method should be used only during measurement and layout calculations.<br />
328 public MeasuredSize MeasuredWidthAndState
332 return MeasuredWidth; // Not bitmasking State unless proven to be required.
338 /// Get the measured height and state.<br />
339 /// This method should be used only during measurement and layout calculations.<br />
341 public MeasuredSize MeasuredHeightAndState
345 return MeasuredHeight; // Not bitmasking State unless proven to be required.
350 /// Returns the suggested minimum width that the layout should use.<br />
351 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
353 public LayoutLength SuggestedMinimumWidth
357 int naturalWidth = Owner.NaturalSize2D.Width;
358 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
363 /// Returns the suggested minimum height that the layout should use.<br />
364 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
366 public LayoutLength SuggestedMinimumHeight
370 int naturalHeight = Owner.NaturalSize2D.Height;
371 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
376 /// Sets the minimum width of the layout.<br />
377 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
378 /// layout constrains it with less available width).<br />
379 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
380 /// 2. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's width is set based on the suggested minimum width. (@see GetSuggestedMinimumWidth()).<br />
381 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
383 public LayoutLength MinimumWidth {get; set;}
386 /// Sets the minimum height of the layout.<br />
387 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
388 /// layout constrains it with less available height).<br />
389 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
390 /// 2. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's height is set based on the suggested minimum height. (@see GetSuggestedMinimumHeight()).<br />
391 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
393 public LayoutLength MinimumHeight {get; set;}
396 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
398 /// <param name="size"> How big the layout wants to be.</param>
399 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
400 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
401 /// <returns> A measured size, which may indicate that it is too small. </returns>
402 protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
404 var specMode = measureSpecification.Mode;
405 LayoutLength specSize = measureSpecification.Size;
406 MeasuredSize result = new MeasuredSize( size, childMeasuredState);
410 case MeasureSpecification.ModeType.AtMost:
412 if (specSize.AsRoundedValue() < size.AsRoundedValue())
414 result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
419 case MeasureSpecification.ModeType.Exactly:
421 result.Size = specSize;
425 case MeasureSpecification.ModeType.Unspecified:
435 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
437 /// <param name="measuredWidth">The measured width of this layout.</param>
438 /// <param name="measuredHeight">The measured height of this layout.</param>
439 protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
441 MeasuredWidth = measuredWidth;
442 MeasuredHeight = measuredHeight;
443 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
447 /// Measure the layout and its content to determine the measured width and the
448 /// measured height.<br />
449 /// The base class implementation of measure defaults to the background size,
450 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
451 /// override to provide better measurements of their content.<br />
452 /// If this method is overridden, it is the subclass's responsibility to make sure the
453 /// measured height and width are at least the layout's minimum height and width.<br />
455 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
456 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
457 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
459 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
460 SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
461 GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
465 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
466 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
468 /// <param name="changed">This is a new size or position for this layout.</param>
469 /// <param name="left">Left position, relative to parent.</param>
470 /// <param name="top">Top position, relative to parent.</param>
471 /// <param name="right">Right position, relative to parent.</param>
472 /// <param name="bottom">Bottom position, relative to parent.</param>
473 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
478 /// Virtual method to inform derived classes when the layout size changed. <br />
480 /// <param name="newSize">The new size of the layout.</param>
481 /// <param name="oldSize">The old size of the layout.</param>
482 protected virtual void OnSizeChanged(LayoutSize newSize, LayoutSize oldSize)
487 /// Virtual method to allow derived classes to remove any children before it is removed from
490 public virtual void OnUnparent()
495 /// Virtual method called when this Layout is attached to it's owner.
496 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
498 protected virtual void OnAttachedToOwner()
502 private bool SetFrame(float left, float top, float right, float bottom)
504 bool changed = false;
506 if ( _layoutPositionData.Left != left ||
507 _layoutPositionData.Right != right ||
508 _layoutPositionData.Top != top ||
509 _layoutPositionData.Bottom != bottom )
513 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
514 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
515 float newWidth = right - left;
516 float newHeight = bottom - top;
517 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
519 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
520 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
522 ConditionForAnimation = TransitionCondition.LayoutChanged;
525 // Store new layout position data
526 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
528 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
529 " left:" + _layoutPositionData.Left +
530 " top:" + _layoutPositionData.Top +
531 " right:" + _layoutPositionData.Right +
532 " bottom:" + _layoutPositionData.Bottom );
534 Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
536 // Reset condition for animation ready for next transition when required.
537 ConditionForAnimation = TransitionCondition.Unspecified;