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 : IDisposable
42 private bool disposed = false;
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 this.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 OnMeasure(widthMeasureSpec, heightMeasureSpec);
207 OnMeasureIndependentChildren(widthMeasureSpec, heightMeasureSpec);
208 flags = flags | LayoutFlags.LayoutRequired;
209 flags &= ~LayoutFlags.ForceLayout;
211 oldWidthMeasureSpec = widthMeasureSpec;
212 oldHeightMeasureSpec = heightMeasureSpec;
215 internal bool NeedsLayout(float widthSize, float heightSize, MeasureSpecification.ModeType widthMode, MeasureSpecification.ModeType heightMode)
222 // Check if relayouting is required.
223 bool specChanged = (widthSize != oldWidthMeasureSpec.Size.AsDecimal()) || (widthMode != oldWidthMeasureSpec.Mode) ||
224 (heightSize != oldHeightMeasureSpec.Size.AsDecimal()) || (heightMode != oldHeightMeasureSpec.Mode);
226 bool isSpecExactly = (widthMode == MeasureSpecification.ModeType.Exactly) &&
227 (heightMode == MeasureSpecification.ModeType.Exactly);
229 bool matchesSpecSize = (MeasuredWidth.Size.AsDecimal() == widthSize) && (MeasuredHeight.Size.AsDecimal() == heightSize);
231 bool needsLayout = specChanged && (!isSpecExactly || !matchesSpecSize);
237 /// Assign a size and position to a layout and all of its descendants. <br />
238 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
239 /// calls layout on all of its children to position them. This is typically done using the child<br />
240 /// measurements that were stored in the measure pass.<br />
242 /// <param name="left">Left position, relative to parent.</param>
243 /// <param name="top">Top position, relative to parent.</param>
244 /// <param name="right">Right position, relative to parent.</param>
245 /// <param name="bottom">Bottom position, relative to parent.</param>
246 /// <since_tizen> 6 </since_tizen>
247 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
249 Layout(left, top, right, bottom, Owner?.ExcludeLayouting ?? false);
253 /// Assign a size and position to a layout and all of its descendants. <br />
254 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
255 /// calls layout on all of its children to position them. This is typically done using the child<br />
256 /// measurements that were stored in the measure pass.<br />
258 /// <param name="left">Left position, relative to parent.</param>
259 /// <param name="top">Top position, relative to parent.</param>
260 /// <param name="right">Right position, relative to parent.</param>
261 /// <param name="bottom">Bottom position, relative to parent.</param>
262 /// <param name="independent">true, if this layout is not affected by parent layout.</param>
263 [EditorBrowsable(EditorBrowsableState.Never)]
264 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom, bool independent)
266 bool changed = SetFrame(left.AsRoundedValue(),
267 top.AsRoundedValue(),
268 right.AsRoundedValue(),
269 bottom.AsRoundedValue(), independent);
271 // Check if Measure needed before Layouting
272 if (changed || ((flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
274 OnLayout(changed, left, top, right, bottom);
275 OnLayoutIndependentChildren(changed, left, top, right, bottom);
277 flags &= ~LayoutFlags.LayoutRequired;
282 /// Utility to return a default size.<br />
283 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
284 /// MeasureSpecification.<br />
286 /// <param name="size"> Default size for this layout.</param>
287 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
288 /// <returns>The size this layout should be.</returns>
289 /// <since_tizen> 6 </since_tizen>
290 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
292 LayoutLength result = size;
293 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
294 LayoutLength specSize = measureSpecification.Size;
298 case MeasureSpecification.ModeType.Unspecified:
303 case MeasureSpecification.ModeType.AtMost:
305 // Ensure the default size does not exceed the spec size unless the default size is 0.
306 // Another container could provide a default size of 0.
308 // Do not set size to 0, use specSize in this case as could be a legacy container
309 if ((size.AsDecimal() < specSize.AsDecimal()) && (size.AsDecimal() > 0))
319 case MeasureSpecification.ModeType.Exactly:
330 /// Get the Layouts parent
332 /// <returns>Layout parent with an LayoutParent interface</returns>
333 /// <since_tizen> 6 </since_tizen>
334 public ILayoutParent GetParent()
340 /// Request that this layout is re-laid out.<br />
341 /// This will make this layout and all it's parent layouts dirty.<br />
343 /// <since_tizen> 6 </since_tizen>
344 public void RequestLayout()
346 flags = flags | LayoutFlags.ForceLayout;
349 LayoutGroup layoutGroup = parent as LayoutGroup;
350 if (layoutGroup != null && !layoutGroup.LayoutRequested)
352 layoutGroup.RequestLayout();
359 /// Predicate to determine if this layout has been requested to re-layout.<br />
362 internal bool LayoutRequested
366 return (flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
370 internal void SetReplaceFlag()
372 parentReplacement = true;
375 internal bool IsReplaceFlag()
377 return parentReplacement;
380 internal void ClearReplaceFlag()
382 parentReplacement = false;
386 /// Get the measured width (without any measurement flags).<br />
387 /// This method should be used only during measurement and layout calculations.<br />
389 /// <since_tizen> 6 </since_tizen>
390 public MeasuredSize MeasuredWidth { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
393 /// Get the measured height (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 MeasuredHeight { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
400 /// Returns the suggested minimum width that the layout should use.<br />
401 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
403 /// <since_tizen> 6 </since_tizen>
404 public LayoutLength SuggestedMinimumWidth
408 return Owner.SuggestedMinimumWidth;
413 /// Returns the suggested minimum height that the layout should use.<br />
414 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
416 /// <since_tizen> 6 </since_tizen>
417 public LayoutLength SuggestedMinimumHeight
421 return Owner.SuggestedMinimumHeight;
426 /// Sets the minimum width of the layout.<br />
427 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
428 /// layout constrains it with less available width).<br />
429 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
430 /// 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 />
431 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
433 internal LayoutLength MinimumWidth { get; set; }
436 /// Sets the minimum height of the layout.<br />
437 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
438 /// layout constrains it with less available height).<br />
439 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
440 /// 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 />
441 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
443 internal LayoutLength MinimumHeight { get; set; }
446 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
448 /// <param name="size"> How big the layout wants to be.</param>
449 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
450 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
451 /// <returns> A measured size, which may indicate that it is too small. </returns>
452 /// <since_tizen> 6 </since_tizen>
453 protected MeasuredSize ResolveSizeAndState(LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState)
455 var specMode = measureSpecification.Mode;
456 LayoutLength specSize = measureSpecification.Size;
457 MeasuredSize result = new MeasuredSize(size, childMeasuredState);
461 case MeasureSpecification.ModeType.AtMost:
463 if (specSize.AsRoundedValue() < size.AsRoundedValue())
465 result = new MeasuredSize(specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
470 case MeasureSpecification.ModeType.Exactly:
472 result.Size = specSize;
476 case MeasureSpecification.ModeType.Unspecified:
486 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
488 /// <param name="measuredWidth">The measured width of this layout.</param>
489 /// <param name="measuredHeight">The measured height of this layout.</param>
490 /// <since_tizen> 6 </since_tizen>
491 protected void SetMeasuredDimensions(MeasuredSize measuredWidth, MeasuredSize measuredHeight)
493 MeasuredWidth = measuredWidth;
494 MeasuredHeight = measuredHeight;
495 flags = flags | LayoutFlags.MeasuredDimensionSet;
499 /// Measure the layout and its content to determine the measured width and the
500 /// measured height.<br />
501 /// The base class implementation of measure defaults to the background size,
502 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
503 /// override to provide better measurements of their content.<br />
504 /// If this method is overridden, it is the subclass's responsibility to make sure the
505 /// measured height and width are at least the layout's minimum height and width.<br />
507 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
508 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
509 /// <since_tizen> 6 </since_tizen>
510 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
512 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
513 SetMeasuredDimensions(GetDefaultSize(SuggestedMinimumWidth, widthMeasureSpec),
514 GetDefaultSize(SuggestedMinimumHeight, heightMeasureSpec));
517 internal virtual void OnMeasureIndependentChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) { }
520 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
521 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
523 /// <param name="changed">This is a new size or position for this layout.</param>
524 /// <param name="left">Left position, relative to parent.</param>
525 /// <param name="top">Top position, relative to parent.</param>
526 /// <param name="right">Right position, relative to parent.</param>
527 /// <param name="bottom">Bottom position, relative to parent.</param>
528 /// <since_tizen> 6 </since_tizen>
529 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
531 internal virtual void OnLayoutIndependentChildren(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
534 /// Virtual method to allow derived classes to remove any children before it is removed from
537 /// <since_tizen> 6 </since_tizen>
538 protected virtual void OnUnparent() { }
541 /// Virtual method called when this Layout is attached to it's owner.
542 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
544 /// <since_tizen> 6 </since_tizen>
545 protected virtual void OnAttachedToOwner() { }
547 private bool SetFrame(float left, float top, float right, float bottom, bool independent)
549 bool changed = false;
551 if (layoutPositionData.Left != left ||
552 layoutPositionData.Right != right ||
553 layoutPositionData.Top != top ||
554 layoutPositionData.Bottom != bottom)
558 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
559 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
561 ConditionForAnimation = TransitionCondition.LayoutChanged;
564 // Store new layout position data
565 layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
567 NUILog.Debug("LayoutItem FramePositionData View:" + layoutPositionData.Item?.Owner?.Name +
568 " left:" + layoutPositionData.Left +
569 " top:" + layoutPositionData.Top +
570 " right:" + layoutPositionData.Right +
571 " bottom:" + layoutPositionData.Bottom);
573 View ownerView = Owner.GetParent() as View;
575 if (ownerView?.Layout?.LayoutWithTransition ?? false)
577 NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(layoutPositionData);
583 // If height or width specification is not explicitly defined,
584 // the size of the owner view must be reset even the ExcludeLayouting is true.
585 if (Owner.HeightSpecification < 0 || Owner.WidthSpecification < 0)
587 Owner.SetSize(right - left, bottom - top);
592 Owner.SetSize(right - left, bottom - top);
593 Owner.SetPosition(left, top);
597 // Reset condition for animation ready for next transition when required.
598 ConditionForAnimation = TransitionCondition.Unspecified;
605 [EditorBrowsable(EditorBrowsableState.Never)]
606 protected virtual void Dispose(bool disposing)
621 [EditorBrowsable(EditorBrowsableState.Never)]
622 public void Dispose()
625 global::System.GC.SuppressFinalize(this);