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.
19 using System.ComponentModel;
20 using System.Diagnostics;
22 using Tizen.NUI.BaseComponents;
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;
56 private bool setPositionByLayout = true;
59 /// [Draft] Condition event that is causing this Layout to transition.
61 internal TransitionCondition ConditionForAnimation { get; set; }
64 /// [Draft] The View that this Layout has been assigned to.
66 /// <since_tizen> 6 </since_tizen>
67 public View Owner { get; set; } // Should not keep a View alive.
70 /// [Draft] Use transition for layouting child
72 /// <since_tizen> 6 </since_tizen>
73 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
74 [EditorBrowsable(EditorBrowsableState.Never)]
75 public bool LayoutWithTransition { get; set; }
78 /// [Draft] Set position by layouting result
80 [EditorBrowsable(EditorBrowsableState.Never)]
81 public bool SetPositionByLayout
85 return setPositionByLayout;
89 setPositionByLayout = value;
90 if (Owner != null && Owner.ExcludeLayouting == value)
92 Owner.ExcludeLayouting = !value;
98 /// [Draft] Margin for this LayoutItem
100 /// <since_tizen> 6 </since_tizen>
101 public Extents Margin
115 /// [Draft] Padding for this LayoutItem
117 /// <since_tizen> 6 </since_tizen>
118 public Extents Padding
132 /// [Draft] Constructor
134 /// <since_tizen> 6 </since_tizen>
141 /// [Draft] Set parent to this layout.
143 /// <param name="parent">Parent to set on this Layout.</param>
144 internal void SetParent(ILayoutParent parent)
146 Parent = parent as LayoutGroup;
150 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
152 internal void Unparent()
154 // Enable directly derived types to first remove children
157 // Remove myself from parent
158 Parent?.Remove(this);
160 // Remove parent reference
163 // Lastly, clear layout from owning View.
164 Owner?.ResetLayout();
167 private void Initialize()
169 LayoutWithTransition = false;
170 _layoutPositionData = new LayoutData(this, TransitionCondition.Unspecified, 0, 0, 0, 0);
171 _padding = new Extents(0, 0, 0, 0);
172 _margin = new Extents(0, 0, 0, 0);
176 /// Initialize the layout and allow derived classes to also perform any operations
178 /// <param name="owner">Owner of this Layout.</param>
179 internal void AttachToOwner(View owner)
181 // Assign the layout owner.
184 // Add layout to parent layout if a layout container
185 View parent = Owner.GetParent() as View;
186 (parent?.Layout as LayoutGroup)?.Add(this);
188 // If Add or ChangeOnAdd then do not update condition
189 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
191 ConditionForAnimation = TransitionCondition.LayoutChanged;
196 /// This is called to find out how big a layout should be. <br />
197 /// The parent supplies constraint information in the width and height parameters. <br />
198 /// The actual measurement work of a layout is performed in OnMeasure called by this
199 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
201 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
202 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
203 /// <since_tizen> 6 </since_tizen>
204 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
206 // Check if relayouting is required.
207 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
208 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
209 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
210 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
212 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
213 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
215 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
216 (MeasuredHeight.Size == heightMeasureSpec.Size);
218 bool needsLayout = specChanged && (!isSpecExactly || !matchesSpecSize);
219 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
223 OnMeasure(widthMeasureSpec, heightMeasureSpec);
224 Flags = Flags | LayoutFlags.LayoutRequired;
225 Flags &= ~LayoutFlags.ForceLayout;
227 OldWidthMeasureSpec = widthMeasureSpec;
228 OldHeightMeasureSpec = heightMeasureSpec;
232 /// Assign a size and position to a layout and all of its descendants. <br />
233 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
234 /// calls layout on all of its children to position them. This is typically done using the child<br />
235 /// measurements that were stored in the measure pass.<br />
237 /// <param name="left">Left position, relative to parent.</param>
238 /// <param name="top">Top position, relative to parent.</param>
239 /// <param name="right">Right position, relative to parent.</param>
240 /// <param name="bottom">Bottom position, relative to parent.</param>
241 /// <since_tizen> 6 </since_tizen>
242 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
244 Layout(left, top, right, bottom, false);
248 /// Assign a size and position to a layout and all of its descendants. <br />
249 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
250 /// calls layout on all of its children to position them. This is typically done using the child<br />
251 /// measurements that were stored in the measure pass.<br />
253 /// <param name="left">Left position, relative to parent.</param>
254 /// <param name="top">Top position, relative to parent.</param>
255 /// <param name="right">Right position, relative to parent.</param>
256 /// <param name="bottom">Bottom position, relative to parent.</param>
257 /// <param name="independent">true, if this layout is not affected by parent layout.</param>
258 [EditorBrowsable(EditorBrowsableState.Never)]
259 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom, bool independent)
264 changed = SetFrame(left.AsRoundedValue(),
265 top.AsRoundedValue(),
266 right.AsRoundedValue(),
267 bottom.AsRoundedValue());
271 // If height or width specification is not explicitly defined,
272 // the size of the owner view must be reset even the ExcludeLayouting is true.
273 if (Owner.HeightSpecification < 0 || Owner.WidthSpecification < 0)
275 Owner.SetSize(right.AsRoundedValue() - left.AsRoundedValue(), bottom.AsRoundedValue() - top.AsRoundedValue());
279 // Check if Measure needed before Layouting
280 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
282 OnLayout(changed, left, top, right, bottom);
284 Flags &= ~LayoutFlags.LayoutRequired;
289 /// Utility to return a default size.<br />
290 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
291 /// MeasureSpecification.<br />
293 /// <param name="size"> Default size for this layout.</param>
294 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
295 /// <returns>The size this layout should be.</returns>
296 /// <since_tizen> 6 </since_tizen>
297 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
299 LayoutLength result = size;
300 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
301 LayoutLength specSize = measureSpecification.Size;
305 case MeasureSpecification.ModeType.Unspecified:
310 case MeasureSpecification.ModeType.AtMost:
312 // Ensure the default size does not exceed the spec size unless the default size is 0.
313 // Another container could provide a default size of 0.
315 // Do not set size to 0, use specSize in this case as could be a legacy container
316 if ((size.AsDecimal() < specSize.AsDecimal()) && (size.AsDecimal() > 0))
326 case MeasureSpecification.ModeType.Exactly:
337 /// Get the Layouts parent
339 /// <returns>Layout parent with an LayoutParent interface</returns>
340 /// <since_tizen> 6 </since_tizen>
341 public ILayoutParent GetParent()
347 /// Request that this layout is re-laid out.<br />
348 /// This will make this layout and all it's parent layouts dirty.<br />
350 /// <since_tizen> 6 </since_tizen>
351 public void RequestLayout()
353 Flags = Flags | LayoutFlags.ForceLayout;
356 LayoutGroup layoutGroup = Parent as LayoutGroup;
357 if (layoutGroup != null && !layoutGroup.LayoutRequested)
359 layoutGroup.RequestLayout();
366 /// Predicate to determine if this layout has been requested to re-layout.<br />
369 internal bool LayoutRequested
373 return (Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
377 internal void SetReplaceFlag()
379 parentReplacement = true;
382 internal bool IsReplaceFlag()
384 return parentReplacement;
387 internal void ClearReplaceFlag()
389 parentReplacement = false;
393 /// Get the measured width (without any measurement flags).<br />
394 /// This method should be used only during measurement and layout calculations.<br />
396 /// <since_tizen> 6 </since_tizen>
397 public MeasuredSize MeasuredWidth { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
400 /// Get the measured height (without any measurement flags).<br />
401 /// This method should be used only during measurement and layout calculations.<br />
403 /// <since_tizen> 6 </since_tizen>
404 public MeasuredSize MeasuredHeight { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
407 /// Returns the suggested minimum width that the layout should use.<br />
408 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
410 /// <since_tizen> 6 </since_tizen>
411 public LayoutLength SuggestedMinimumWidth
415 float maximumWidth = Owner.MaximumSize.Width;
416 float minimumWidth = Owner.MinimumSize.Width;
418 float baseHeight = Owner.MaximumSize.Height > 0 ? Math.Min(Owner.MaximumSize.Height, Owner.NaturalSize.Height) : Owner.NaturalSize.Height;
419 float baseWidth = Owner.GetWidthForHeight(baseHeight);
421 float result = minimumWidth > 0 ? Math.Max(baseWidth, minimumWidth) : baseWidth;
422 result = maximumWidth > 0 ? Math.Min(result, maximumWidth) : result;
424 return new LayoutLength(result);
429 /// Returns the suggested minimum height that the layout should use.<br />
430 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
432 /// <since_tizen> 6 </since_tizen>
433 public LayoutLength SuggestedMinimumHeight
437 float maximumHeight = Owner.MaximumSize.Height;
438 float minimumHeight = Owner.MinimumSize.Height;
440 float baseWidth = Owner.MaximumSize.Width > 0 ? Math.Min(Owner.MaximumSize.Width, Owner.NaturalSize.Width) : Owner.NaturalSize.Width;
441 float baseHeight = Owner.GetHeightForWidth(baseWidth);
443 float result = minimumHeight > 0 ? Math.Max(baseHeight, minimumHeight) : baseHeight;
444 result = maximumHeight > 0 ? Math.Min(result, maximumHeight) : result;
446 return new LayoutLength(result);
451 /// Sets the minimum width of the layout.<br />
452 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
453 /// layout constrains it with less available width).<br />
454 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
455 /// 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 />
456 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
458 internal LayoutLength MinimumWidth { get; set; }
461 /// Sets the minimum height of the layout.<br />
462 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
463 /// layout constrains it with less available height).<br />
464 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
465 /// 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 />
466 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
468 internal LayoutLength MinimumHeight { get; set; }
471 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
473 /// <param name="size"> How big the layout wants to be.</param>
474 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
475 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
476 /// <returns> A measured size, which may indicate that it is too small. </returns>
477 /// <since_tizen> 6 </since_tizen>
478 protected MeasuredSize ResolveSizeAndState(LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState)
480 var specMode = measureSpecification.Mode;
481 LayoutLength specSize = measureSpecification.Size;
482 MeasuredSize result = new MeasuredSize(size, childMeasuredState);
486 case MeasureSpecification.ModeType.AtMost:
488 if (specSize.AsRoundedValue() < size.AsRoundedValue())
490 result = new MeasuredSize(specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
495 case MeasureSpecification.ModeType.Exactly:
497 result.Size = specSize;
501 case MeasureSpecification.ModeType.Unspecified:
511 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
513 /// <param name="measuredWidth">The measured width of this layout.</param>
514 /// <param name="measuredHeight">The measured height of this layout.</param>
515 /// <since_tizen> 6 </since_tizen>
516 protected void SetMeasuredDimensions(MeasuredSize measuredWidth, MeasuredSize measuredHeight)
518 MeasuredWidth = measuredWidth;
519 MeasuredHeight = measuredHeight;
520 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
524 /// Measure the layout and its content to determine the measured width and the
525 /// measured height.<br />
526 /// The base class implementation of measure defaults to the background size,
527 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
528 /// override to provide better measurements of their content.<br />
529 /// If this method is overridden, it is the subclass's responsibility to make sure the
530 /// measured height and width are at least the layout's minimum height and width.<br />
532 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
533 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
534 /// <since_tizen> 6 </since_tizen>
535 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
537 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
538 SetMeasuredDimensions(GetDefaultSize(SuggestedMinimumWidth, widthMeasureSpec),
539 GetDefaultSize(SuggestedMinimumHeight, heightMeasureSpec));
543 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
544 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
546 /// <param name="changed">This is a new size or position for this layout.</param>
547 /// <param name="left">Left position, relative to parent.</param>
548 /// <param name="top">Top position, relative to parent.</param>
549 /// <param name="right">Right position, relative to parent.</param>
550 /// <param name="bottom">Bottom position, relative to parent.</param>
551 /// <since_tizen> 6 </since_tizen>
552 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
555 /// Virtual method to allow derived classes to remove any children before it is removed from
558 /// <since_tizen> 6 </since_tizen>
559 protected virtual void OnUnparent() { }
562 /// Virtual method called when this Layout is attached to it's owner.
563 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
565 /// <since_tizen> 6 </since_tizen>
566 protected virtual void OnAttachedToOwner() { }
568 private bool SetFrame(float left, float top, float right, float bottom)
570 bool changed = false;
572 if (_layoutPositionData.Left != left ||
573 _layoutPositionData.Right != right ||
574 _layoutPositionData.Top != top ||
575 _layoutPositionData.Bottom != bottom)
579 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
580 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
581 float newWidth = right - left;
582 float newHeight = bottom - top;
583 bool sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
585 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
586 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
588 ConditionForAnimation = TransitionCondition.LayoutChanged;
591 // Store new layout position data
592 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
594 Debug.WriteLineIf(LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
595 " left:" + _layoutPositionData.Left +
596 " top:" + _layoutPositionData.Top +
597 " right:" + _layoutPositionData.Right +
598 " bottom:" + _layoutPositionData.Bottom);
600 Container onwerContainer = Owner.GetParent();
601 View onwerView = onwerContainer is Layer ? new View(Layer.getCPtr(onwerContainer).Handle, false) : onwerContainer as View;
603 if (onwerView != null && onwerView.Layout != null && onwerView.Layout.LayoutWithTransition)
605 NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(_layoutPositionData);
609 if (Owner.Position != null)
611 Owner.SetSize(right - left, bottom - top);
612 Owner.SetPosition(left, top);
616 // Reset condition for animation ready for next transition when required.
617 ConditionForAnimation = TransitionCondition.Unspecified;