2 * Copyright (c) 2020 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;
22 using System.ComponentModel;
28 enum LayoutFlags : short
33 MeasuredDimensionSet = 4
37 /// [Draft] Base class for layouts. It is used to layout a View
38 /// It can be laid out by a LayoutGroup.
40 public class LayoutItem
42 static bool LayoutDebugFrameData = false; // Debug flag
43 private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
44 private MeasureSpecification OldHeightMeasureSpec;// Store measure specification to compare against later
46 private LayoutFlags Flags = LayoutFlags.None;
48 private ILayoutParent Parent;
50 LayoutData _layoutPositionData;
52 private Extents _padding;
53 private Extents _margin;
55 private bool parentReplacement = false;
58 /// [Draft] Condition event that is causing this Layout to transition.
60 internal TransitionCondition ConditionForAnimation{get; set;}
63 /// [Draft] The View that this Layout has been assigned to.
65 /// <since_tizen> 6 </since_tizen>
66 public View Owner{get; set;} // Should not keep a View alive.
69 /// [Draft] Use transition for layouting child
71 /// <since_tizen> 6 </since_tizen>
72 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
73 [EditorBrowsable(EditorBrowsableState.Never)]
74 public bool LayoutWithTransition{get; set;}
77 /// [Draft] Margin for this LayoutItem
79 /// <since_tizen> 6 </since_tizen>
94 /// [Draft] Padding for this LayoutItem
96 /// <since_tizen> 6 </since_tizen>
97 public Extents Padding
111 /// [Draft] Constructor
113 /// <since_tizen> 6 </since_tizen>
120 /// [Draft] Set parent to this layout.
122 /// <param name="parent">Parent to set on this Layout.</param>
123 internal void SetParent( ILayoutParent parent)
125 Parent = parent as LayoutGroup;
129 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
131 internal void Unparent()
133 // Enable directly derived types to first remove children
136 // Remove myself from parent
137 Parent?.Remove( this );
139 // Remove parent reference
142 // Lastly, clear layout from owning View.
143 Owner?.ResetLayout();
146 private void Initialize()
148 LayoutWithTransition = false;
149 _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
150 _padding = new Extents(0,0,0,0);
151 _margin = new Extents(0,0,0,0);
155 /// Initialize the layout and allow derived classes to also perform any operations
157 /// <param name="owner">Owner of this Layout.</param>
158 internal void AttachToOwner(View owner)
160 // Assign the layout owner.
163 // Add layout to parent layout if a layout container
164 View parent = Owner.GetParent() as View;
165 (parent?.Layout as LayoutGroup)?.Add( this );
167 // If Add or ChangeOnAdd then do not update condition
168 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
170 ConditionForAnimation = TransitionCondition.LayoutChanged;
175 /// This is called to find out how big a layout should be. <br />
176 /// The parent supplies constraint information in the width and height parameters. <br />
177 /// The actual measurement work of a layout is performed in OnMeasure called by this
178 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
180 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
181 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
182 /// <since_tizen> 6 </since_tizen>
183 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
185 // Check if relayouting is required.
186 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
187 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
188 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
189 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
191 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
192 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
194 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
195 (MeasuredHeight.Size == heightMeasureSpec.Size);
197 bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
198 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
202 OnMeasure(widthMeasureSpec, heightMeasureSpec);
203 Flags = Flags | LayoutFlags.LayoutRequired;
204 Flags &= ~LayoutFlags.ForceLayout;
206 OldWidthMeasureSpec = widthMeasureSpec;
207 OldHeightMeasureSpec = heightMeasureSpec;
211 /// Assign a size and position to a layout and all of its descendants. <br />
212 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
213 /// calls layout on all of its children to position them. This is typically done using the child<br />
214 /// measurements that were stored in the measure pass.<br />
216 /// <param name="left">Left position, relative to parent.</param>
217 /// <param name="top">Top position, relative to parent.</param>
218 /// <param name="right">Right position, relative to parent.</param>
219 /// <param name="bottom">Bottom position, relative to parent.</param>
220 /// <since_tizen> 6 </since_tizen>
221 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
223 bool changed = SetFrame(left.AsRoundedValue(),
224 top.AsRoundedValue(),
225 right.AsRoundedValue(),
226 bottom.AsRoundedValue());
228 // Check if Measure needed before Layouting
229 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
231 OnLayout(changed, left, top, right, bottom);
233 Flags &= ~LayoutFlags.LayoutRequired;
238 /// Utility to return a default size.<br />
239 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
240 /// MeasureSpecification.<br />
242 /// <param name="size"> Default size for this layout.</param>
243 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
244 /// <returns>The size this layout should be.</returns>
245 /// <since_tizen> 6 </since_tizen>
246 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
248 LayoutLength result = size;
249 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
250 LayoutLength specSize = measureSpecification.Size;
254 case MeasureSpecification.ModeType.Unspecified:
259 case MeasureSpecification.ModeType.AtMost:
261 // Ensure the default size does not exceed the spec size unless the default size is 0.
262 // Another container could provide a default size of 0.
264 // Do not set size to 0, use specSize in this case as could be a legacy container
265 if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
275 case MeasureSpecification.ModeType.Exactly:
286 /// Get the Layouts parent
288 /// <returns>Layout parent with an LayoutParent interface</returns>
289 /// <since_tizen> 6 </since_tizen>
290 public ILayoutParent GetParent()
296 /// Request that this layout is re-laid out.<br />
297 /// This will make this layout and all it's parent layouts dirty.<br />
299 /// <since_tizen> 6 </since_tizen>
300 public void RequestLayout()
302 Flags = Flags | LayoutFlags.ForceLayout;
303 Window.Instance.LayoutController.RequestLayout(this);
307 /// Predicate to determine if this layout has been requested to re-layout.<br />
310 internal bool LayoutRequested
314 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
318 internal void SetReplaceFlag()
320 parentReplacement = true;
323 internal bool IsReplaceFlag()
325 return parentReplacement;
328 internal void ClearReplaceFlag()
330 parentReplacement = false;
334 /// Get the measured width (without any measurement flags).<br />
335 /// This method should be used only during measurement and layout calculations.<br />
337 /// <since_tizen> 6 </since_tizen>
338 public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
341 /// Get the measured height (without any measurement flags).<br />
342 /// This method should be used only during measurement and layout calculations.<br />
344 /// <since_tizen> 6 </since_tizen>
345 public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
348 /// Returns the suggested minimum width that the layout should use.<br />
349 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
351 /// <since_tizen> 6 </since_tizen>
352 public LayoutLength SuggestedMinimumWidth
356 int naturalWidth = Owner.NaturalSize2D.Width;
357 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
362 /// Returns the suggested minimum height that the layout should use.<br />
363 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
365 /// <since_tizen> 6 </since_tizen>
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 internal 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 internal 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 /// <since_tizen> 6 </since_tizen>
403 protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
405 var specMode = measureSpecification.Mode;
406 LayoutLength specSize = measureSpecification.Size;
407 MeasuredSize result = new MeasuredSize( size, childMeasuredState);
411 case MeasureSpecification.ModeType.AtMost:
413 if (specSize.AsRoundedValue() < size.AsRoundedValue())
415 result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
420 case MeasureSpecification.ModeType.Exactly:
422 result.Size = specSize;
426 case MeasureSpecification.ModeType.Unspecified:
436 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
438 /// <param name="measuredWidth">The measured width of this layout.</param>
439 /// <param name="measuredHeight">The measured height of this layout.</param>
440 /// <since_tizen> 6 </since_tizen>
441 protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
443 MeasuredWidth = measuredWidth;
444 MeasuredHeight = measuredHeight;
445 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
449 /// Measure the layout and its content to determine the measured width and the
450 /// measured height.<br />
451 /// The base class implementation of measure defaults to the background size,
452 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
453 /// override to provide better measurements of their content.<br />
454 /// If this method is overridden, it is the subclass's responsibility to make sure the
455 /// measured height and width are at least the layout's minimum height and width.<br />
457 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
458 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
459 /// <since_tizen> 6 </since_tizen>
460 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
462 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
463 SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
464 GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
468 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
469 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
471 /// <param name="changed">This is a new size or position for this layout.</param>
472 /// <param name="left">Left position, relative to parent.</param>
473 /// <param name="top">Top position, relative to parent.</param>
474 /// <param name="right">Right position, relative to parent.</param>
475 /// <param name="bottom">Bottom position, relative to parent.</param>
476 /// <since_tizen> 6 </since_tizen>
477 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
482 /// Virtual method to allow derived classes to remove any children before it is removed from
485 /// <since_tizen> 6 </since_tizen>
486 protected virtual void OnUnparent()
491 /// Virtual method called when this Layout is attached to it's owner.
492 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
494 /// <since_tizen> 6 </since_tizen>
495 protected virtual void OnAttachedToOwner()
499 private bool SetFrame(float left, float top, float right, float bottom)
501 bool changed = false;
503 if ( _layoutPositionData.Left != left ||
504 _layoutPositionData.Right != right ||
505 _layoutPositionData.Top != top ||
506 _layoutPositionData.Bottom != bottom )
510 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
511 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
512 float newWidth = right - left;
513 float newHeight = bottom - top;
514 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
516 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
517 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
519 ConditionForAnimation = TransitionCondition.LayoutChanged;
522 // Store new layout position data
523 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
525 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
526 " left:" + _layoutPositionData.Left +
527 " top:" + _layoutPositionData.Top +
528 " right:" + _layoutPositionData.Right +
529 " bottom:" + _layoutPositionData.Bottom );
531 if (Owner.Parent != null && Owner.Parent.Layout != null && Owner.Parent.Layout.LayoutWithTransition)
533 Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
537 Owner.Size = new Size(right - left, bottom - top, Owner.Position.Z);
538 Owner.Position = new Position(left, top, Owner.Position.Z);
542 // Reset condition for animation ready for next transition when required.
543 ConditionForAnimation = TransitionCondition.Unspecified;