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;
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 public 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 private bool parentReplacement = false;
57 /// [Draft] Condition event that is causing this Layout to transition.
59 internal TransitionCondition ConditionForAnimation{get; set;}
62 /// [Draft] The View that this Layout has been assigned to.
64 /// <since_tizen> 6 </since_tizen>
65 public View Owner{get; set;} // Should not keep a View alive.
68 /// [Draft] Margin for this LayoutItem
70 /// <since_tizen> 6 </since_tizen>
85 /// [Draft] Padding for this LayoutItem
87 /// <since_tizen> 6 </since_tizen>
88 public Extents Padding
102 /// [Draft] Constructor
104 /// <since_tizen> 6 </since_tizen>
111 /// [Draft] Set parent to this layout.
113 /// <param name="parent">Parent to set on this Layout.</param>
114 internal void SetParent( ILayoutParent parent)
116 Parent = parent as LayoutGroup;
120 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
122 internal void Unparent()
124 // Enable directly derived types to first remove children
127 // Remove myself from parent
128 Parent?.Remove( this );
130 // Remove parent reference
133 // Lastly, clear layout from owning View.
134 Owner?.ResetLayout();
137 private void Initialize()
139 _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
140 _padding = new Extents(0,0,0,0);
141 _margin = new Extents(0,0,0,0);
145 /// Initialize the layout and allow derived classes to also perform any operations
147 /// <param name="owner">Owner of this Layout.</param>
148 internal void AttachToOwner(View owner)
150 // Assign the layout owner.
153 // Add layout to parent layout if a layout container
154 View parent = Owner.GetParent() as View;
155 (parent?.Layout as LayoutGroup)?.Add( this );
157 // If Add or ChangeOnAdd then do not update condition
158 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
160 ConditionForAnimation = TransitionCondition.LayoutChanged;
165 /// This is called to find out how big a layout should be. <br />
166 /// The parent supplies constraint information in the width and height parameters. <br />
167 /// The actual measurement work of a layout is performed in OnMeasure called by this
168 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
170 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
171 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
172 /// <since_tizen> 6 </since_tizen>
173 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
175 // Check if relayouting is required.
176 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
177 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
178 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
179 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
181 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
182 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
184 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
185 (MeasuredHeight.Size == heightMeasureSpec.Size);
187 bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
188 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
192 OnMeasure(widthMeasureSpec, heightMeasureSpec);
193 Flags = Flags | LayoutFlags.LayoutRequired;
194 Flags &= ~LayoutFlags.ForceLayout;
196 OldWidthMeasureSpec = widthMeasureSpec;
197 OldHeightMeasureSpec = heightMeasureSpec;
201 /// Assign a size and position to a layout and all of its descendants. <br />
202 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
203 /// calls layout on all of its children to position them. This is typically done using the child<br />
204 /// measurements that were stored in the measure pass.<br />
206 /// <param name="left">Left position, relative to parent.</param>
207 /// <param name="top">Top position, relative to parent.</param>
208 /// <param name="right">Right position, relative to parent.</param>
209 /// <param name="bottom">Bottom position, relative to parent.</param>
210 /// <since_tizen> 6 </since_tizen>
211 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
213 bool changed = SetFrame(left.AsRoundedValue(),
214 top.AsRoundedValue(),
215 right.AsRoundedValue(),
216 bottom.AsRoundedValue());
218 // Check if Measure needed before Layouting
219 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
221 OnLayout(changed, left, top, right, bottom);
223 Flags &= ~LayoutFlags.LayoutRequired;
228 /// Utility to return a default size.<br />
229 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
230 /// MeasureSpecification.<br />
232 /// <param name="size"> Default size for this layout.</param>
233 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
234 /// <returns>The size this layout should be.</returns>
235 /// <since_tizen> 6 </since_tizen>
236 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
238 LayoutLength result = size;
239 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
240 LayoutLength specSize = measureSpecification.Size;
244 case MeasureSpecification.ModeType.Unspecified:
249 case MeasureSpecification.ModeType.AtMost:
251 // Ensure the default size does not exceed the spec size unless the default size is 0.
252 // Another container could provide a default size of 0.
254 // Do not set size to 0, use specSize in this case as could be a legacy container
255 if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
265 case MeasureSpecification.ModeType.Exactly:
276 /// Get the Layouts parent
278 /// <returns>Layout parent with an LayoutParent interface</returns>
279 /// <since_tizen> 6 </since_tizen>
280 public ILayoutParent GetParent()
286 /// Request that this layout is re-laid out.<br />
287 /// This will make this layout and all it's parent layouts dirty.<br />
289 /// <since_tizen> 6 </since_tizen>
290 public void RequestLayout()
292 Flags = Flags | LayoutFlags.ForceLayout;
293 Window.Instance.LayoutController.RequestLayout(this);
297 /// Predicate to determine if this layout has been requested to re-layout.<br />
300 internal bool LayoutRequested
304 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
308 internal void SetReplaceFlag()
310 parentReplacement = true;
313 internal bool IsReplaceFlag()
315 return parentReplacement;
318 internal void ClearReplaceFlag()
320 parentReplacement = false;
324 /// Get the measured width (without any measurement flags).<br />
325 /// This method should be used only during measurement and layout calculations.<br />
327 /// <since_tizen> 6 </since_tizen>
328 public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
331 /// Get the measured height (without any measurement flags).<br />
332 /// This method should be used only during measurement and layout calculations.<br />
334 /// <since_tizen> 6 </since_tizen>
335 public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
338 /// Returns the suggested minimum width that the layout should use.<br />
339 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
341 /// <since_tizen> 6 </since_tizen>
342 public LayoutLength SuggestedMinimumWidth
346 int naturalWidth = Owner.NaturalSize2D.Width;
347 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
352 /// Returns the suggested minimum height that the layout should use.<br />
353 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
355 /// <since_tizen> 6 </since_tizen>
356 public LayoutLength SuggestedMinimumHeight
360 int naturalHeight = Owner.NaturalSize2D.Height;
361 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
366 /// Sets the minimum width of the layout.<br />
367 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
368 /// layout constrains it with less available width).<br />
369 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
370 /// 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 />
371 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
373 internal LayoutLength MinimumWidth {get; set;}
376 /// Sets the minimum height of the layout.<br />
377 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
378 /// layout constrains it with less available height).<br />
379 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
380 /// 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 />
381 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
383 internal LayoutLength MinimumHeight {get; set;}
386 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
388 /// <param name="size"> How big the layout wants to be.</param>
389 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
390 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
391 /// <returns> A measured size, which may indicate that it is too small. </returns>
392 /// <since_tizen> 6 </since_tizen>
393 protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
395 var specMode = measureSpecification.Mode;
396 LayoutLength specSize = measureSpecification.Size;
397 MeasuredSize result = new MeasuredSize( size, childMeasuredState);
401 case MeasureSpecification.ModeType.AtMost:
403 if (specSize.AsRoundedValue() < size.AsRoundedValue())
405 result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
410 case MeasureSpecification.ModeType.Exactly:
412 result.Size = specSize;
416 case MeasureSpecification.ModeType.Unspecified:
426 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
428 /// <param name="measuredWidth">The measured width of this layout.</param>
429 /// <param name="measuredHeight">The measured height of this layout.</param>
430 /// <since_tizen> 6 </since_tizen>
431 protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
433 MeasuredWidth = measuredWidth;
434 MeasuredHeight = measuredHeight;
435 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
439 /// Measure the layout and its content to determine the measured width and the
440 /// measured height.<br />
441 /// The base class implementation of measure defaults to the background size,
442 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
443 /// override to provide better measurements of their content.<br />
444 /// If this method is overridden, it is the subclass's responsibility to make sure the
445 /// measured height and width are at least the layout's minimum height and width.<br />
447 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
448 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
449 /// <since_tizen> 6 </since_tizen>
450 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
452 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
453 SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
454 GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
458 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
459 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
461 /// <param name="changed">This is a new size or position for this layout.</param>
462 /// <param name="left">Left position, relative to parent.</param>
463 /// <param name="top">Top position, relative to parent.</param>
464 /// <param name="right">Right position, relative to parent.</param>
465 /// <param name="bottom">Bottom position, relative to parent.</param>
466 /// <since_tizen> 6 </since_tizen>
467 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
472 /// Virtual method to allow derived classes to remove any children before it is removed from
475 /// <since_tizen> 6 </since_tizen>
476 protected virtual void OnUnparent()
481 /// Virtual method called when this Layout is attached to it's owner.
482 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
484 /// <since_tizen> 6 </since_tizen>
485 protected virtual void OnAttachedToOwner()
489 private bool SetFrame(float left, float top, float right, float bottom)
491 bool changed = false;
493 if ( _layoutPositionData.Left != left ||
494 _layoutPositionData.Right != right ||
495 _layoutPositionData.Top != top ||
496 _layoutPositionData.Bottom != bottom )
500 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
501 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
502 float newWidth = right - left;
503 float newHeight = bottom - top;
504 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
506 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
507 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
509 ConditionForAnimation = TransitionCondition.LayoutChanged;
512 // Store new layout position data
513 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
515 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
516 " left:" + _layoutPositionData.Left +
517 " top:" + _layoutPositionData.Top +
518 " right:" + _layoutPositionData.Right +
519 " bottom:" + _layoutPositionData.Bottom );
521 Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
523 // Reset condition for animation ready for next transition when required.
524 ConditionForAnimation = TransitionCondition.Unspecified;