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 static bool LayoutDebugFrameData = false; // Debug flag
44 private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
45 private MeasureSpecification OldHeightMeasureSpec; // Store measure specification to compare against later
47 private LayoutFlags Flags = LayoutFlags.None;
49 private ILayoutParent Parent;
51 LayoutData _layoutPositionData;
53 private Extents _padding;
54 private Extents _margin;
56 private bool parentReplacement = false;
57 private bool setPositionByLayout = true;
60 /// [Draft] Condition event that is causing this Layout to transition.
62 internal TransitionCondition ConditionForAnimation { get; set; }
65 /// [Draft] The View that this Layout has been assigned to.
67 /// <since_tizen> 6 </since_tizen>
68 public View Owner { get; set; } // Should not keep a View alive.
71 /// [Draft] Use transition for layouting child
73 /// <since_tizen> 6 </since_tizen>
74 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
75 [EditorBrowsable(EditorBrowsableState.Never)]
76 public bool LayoutWithTransition { get; set; }
79 /// [Draft] Set position by layouting result
81 [EditorBrowsable(EditorBrowsableState.Never)]
82 public bool SetPositionByLayout
86 return setPositionByLayout;
90 setPositionByLayout = value;
91 if (Owner != null && Owner.ExcludeLayouting == value)
93 Owner.ExcludeLayouting = !value;
99 /// [Draft] Margin for this LayoutItem
101 /// <since_tizen> 6 </since_tizen>
102 public Extents Margin
116 /// [Draft] Padding for this LayoutItem
118 /// <since_tizen> 6 </since_tizen>
119 public Extents Padding
133 /// [Draft] Constructor
135 /// <since_tizen> 6 </since_tizen>
142 /// [Draft] Set parent to this layout.
144 /// <param name="parent">Parent to set on this Layout.</param>
145 internal void SetParent(ILayoutParent parent)
147 Parent = parent as LayoutGroup;
151 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
153 internal void Unparent()
155 // Enable directly derived types to first remove children
158 // Remove myself from parent
159 Parent?.Remove(this);
161 // Remove parent reference
164 // Lastly, clear layout from owning View.
165 Owner?.ResetLayout();
168 private void Initialize()
170 LayoutWithTransition = false;
171 _layoutPositionData = new LayoutData(this, TransitionCondition.Unspecified, 0, 0, 0, 0);
172 _padding = new Extents(0, 0, 0, 0);
173 _margin = new Extents(0, 0, 0, 0);
177 /// Initialize the layout and allow derived classes to also perform any operations
179 /// <param name="owner">Owner of this Layout.</param>
180 internal void AttachToOwner(View owner)
182 // Assign the layout owner.
185 // Add layout to parent layout if a layout container
186 View parent = Owner.GetParent() as View;
187 (parent?.Layout as LayoutGroup)?.Add(this);
189 // If Add or ChangeOnAdd then do not update condition
190 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
192 ConditionForAnimation = TransitionCondition.LayoutChanged;
197 /// This is called to find out how big a layout should be. <br />
198 /// The parent supplies constraint information in the width and height parameters. <br />
199 /// The actual measurement work of a layout is performed in OnMeasure called by this
200 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
202 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
203 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
204 /// <since_tizen> 6 </since_tizen>
205 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
207 // Check if relayouting is required.
208 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
209 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
210 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
211 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
213 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
214 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
216 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
217 (MeasuredHeight.Size == heightMeasureSpec.Size);
219 bool needsLayout = specChanged && (!isSpecExactly || !matchesSpecSize);
220 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
224 OnMeasure(widthMeasureSpec, heightMeasureSpec);
225 OnMeasureIndependentChildren(widthMeasureSpec, heightMeasureSpec);
226 Flags = Flags | LayoutFlags.LayoutRequired;
227 Flags &= ~LayoutFlags.ForceLayout;
229 OldWidthMeasureSpec = widthMeasureSpec;
230 OldHeightMeasureSpec = heightMeasureSpec;
234 /// Assign a size and position to a layout and all of its descendants. <br />
235 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
236 /// calls layout on all of its children to position them. This is typically done using the child<br />
237 /// measurements that were stored in the measure pass.<br />
239 /// <param name="left">Left position, relative to parent.</param>
240 /// <param name="top">Top position, relative to parent.</param>
241 /// <param name="right">Right position, relative to parent.</param>
242 /// <param name="bottom">Bottom position, relative to parent.</param>
243 /// <since_tizen> 6 </since_tizen>
244 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
246 Layout(left, top, right, bottom, Owner?.ExcludeLayouting ?? false);
250 /// Assign a size and position to a layout and all of its descendants. <br />
251 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
252 /// calls layout on all of its children to position them. This is typically done using the child<br />
253 /// measurements that were stored in the measure pass.<br />
255 /// <param name="left">Left position, relative to parent.</param>
256 /// <param name="top">Top position, relative to parent.</param>
257 /// <param name="right">Right position, relative to parent.</param>
258 /// <param name="bottom">Bottom position, relative to parent.</param>
259 /// <param name="independent">true, if this layout is not affected by parent layout.</param>
260 [EditorBrowsable(EditorBrowsableState.Never)]
261 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom, bool independent)
263 bool changed = SetFrame(left.AsRoundedValue(),
264 top.AsRoundedValue(),
265 right.AsRoundedValue(),
266 bottom.AsRoundedValue(), independent);
268 // Check if Measure needed before Layouting
269 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
271 OnLayout(changed, left, top, right, bottom);
272 OnLayoutIndependentChildren(changed, left, top, right, bottom);
274 Flags &= ~LayoutFlags.LayoutRequired;
279 /// Utility to return a default size.<br />
280 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
281 /// MeasureSpecification.<br />
283 /// <param name="size"> Default size for this layout.</param>
284 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
285 /// <returns>The size this layout should be.</returns>
286 /// <since_tizen> 6 </since_tizen>
287 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
289 LayoutLength result = size;
290 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
291 LayoutLength specSize = measureSpecification.Size;
295 case MeasureSpecification.ModeType.Unspecified:
300 case MeasureSpecification.ModeType.AtMost:
302 // Ensure the default size does not exceed the spec size unless the default size is 0.
303 // Another container could provide a default size of 0.
305 // Do not set size to 0, use specSize in this case as could be a legacy container
306 if ((size.AsDecimal() < specSize.AsDecimal()) && (size.AsDecimal() > 0))
316 case MeasureSpecification.ModeType.Exactly:
327 /// Get the Layouts parent
329 /// <returns>Layout parent with an LayoutParent interface</returns>
330 /// <since_tizen> 6 </since_tizen>
331 public ILayoutParent GetParent()
337 /// Request that this layout is re-laid out.<br />
338 /// This will make this layout and all it's parent layouts dirty.<br />
340 /// <since_tizen> 6 </since_tizen>
341 public void RequestLayout()
343 Flags = Flags | LayoutFlags.ForceLayout;
346 LayoutGroup layoutGroup = Parent as LayoutGroup;
347 if (layoutGroup != null && !layoutGroup.LayoutRequested)
349 layoutGroup.RequestLayout();
356 /// Predicate to determine if this layout has been requested to re-layout.<br />
359 internal bool LayoutRequested
363 return (Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
367 internal void SetReplaceFlag()
369 parentReplacement = true;
372 internal bool IsReplaceFlag()
374 return parentReplacement;
377 internal void ClearReplaceFlag()
379 parentReplacement = false;
383 /// Get the measured width (without any measurement flags).<br />
384 /// This method should be used only during measurement and layout calculations.<br />
386 /// <since_tizen> 6 </since_tizen>
387 public MeasuredSize MeasuredWidth { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
390 /// Get the measured height (without any measurement flags).<br />
391 /// This method should be used only during measurement and layout calculations.<br />
393 /// <since_tizen> 6 </since_tizen>
394 public MeasuredSize MeasuredHeight { get; set; } = new MeasuredSize(new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
397 /// Returns the suggested minimum width that the layout should use.<br />
398 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
400 /// <since_tizen> 6 </since_tizen>
401 public LayoutLength SuggestedMinimumWidth
405 float maximumWidth = Owner.MaximumSize.Width;
406 float minimumWidth = Owner.MinimumSize.Width;
408 float baseHeight = Owner.MaximumSize.Height > 0 ? Math.Min(Owner.MaximumSize.Height, Owner.NaturalSize.Height) : Owner.NaturalSize.Height;
409 float baseWidth = Owner.GetWidthForHeight(baseHeight);
411 float result = minimumWidth > 0 ? Math.Max(baseWidth, minimumWidth) : baseWidth;
412 result = maximumWidth > 0 ? Math.Min(result, maximumWidth) : result;
414 return new LayoutLength(result);
419 /// Returns the suggested minimum height that the layout should use.<br />
420 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
422 /// <since_tizen> 6 </since_tizen>
423 public LayoutLength SuggestedMinimumHeight
427 float maximumHeight = Owner.MaximumSize.Height;
428 float minimumHeight = Owner.MinimumSize.Height;
430 float baseWidth = Owner.MaximumSize.Width > 0 ? Math.Min(Owner.MaximumSize.Width, Owner.NaturalSize.Width) : Owner.NaturalSize.Width;
431 float baseHeight = Owner.GetHeightForWidth(baseWidth);
433 float result = minimumHeight > 0 ? Math.Max(baseHeight, minimumHeight) : baseHeight;
434 result = maximumHeight > 0 ? Math.Min(result, maximumHeight) : result;
436 return new LayoutLength(result);
441 /// Sets the minimum width of the layout.<br />
442 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
443 /// layout constrains it with less available width).<br />
444 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
445 /// 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 />
446 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
448 internal LayoutLength MinimumWidth { get; set; }
451 /// Sets the minimum height of the layout.<br />
452 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
453 /// layout constrains it with less available height).<br />
454 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
455 /// 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 />
456 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
458 internal LayoutLength MinimumHeight { get; set; }
461 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
463 /// <param name="size"> How big the layout wants to be.</param>
464 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
465 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
466 /// <returns> A measured size, which may indicate that it is too small. </returns>
467 /// <since_tizen> 6 </since_tizen>
468 protected MeasuredSize ResolveSizeAndState(LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState)
470 var specMode = measureSpecification.Mode;
471 LayoutLength specSize = measureSpecification.Size;
472 MeasuredSize result = new MeasuredSize(size, childMeasuredState);
476 case MeasureSpecification.ModeType.AtMost:
478 if (specSize.AsRoundedValue() < size.AsRoundedValue())
480 result = new MeasuredSize(specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
485 case MeasureSpecification.ModeType.Exactly:
487 result.Size = specSize;
491 case MeasureSpecification.ModeType.Unspecified:
501 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
503 /// <param name="measuredWidth">The measured width of this layout.</param>
504 /// <param name="measuredHeight">The measured height of this layout.</param>
505 /// <since_tizen> 6 </since_tizen>
506 protected void SetMeasuredDimensions(MeasuredSize measuredWidth, MeasuredSize measuredHeight)
508 MeasuredWidth = measuredWidth;
509 MeasuredHeight = measuredHeight;
510 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
514 /// Measure the layout and its content to determine the measured width and the
515 /// measured height.<br />
516 /// The base class implementation of measure defaults to the background size,
517 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
518 /// override to provide better measurements of their content.<br />
519 /// If this method is overridden, it is the subclass's responsibility to make sure the
520 /// measured height and width are at least the layout's minimum height and width.<br />
522 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
523 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
524 /// <since_tizen> 6 </since_tizen>
525 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
527 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
528 SetMeasuredDimensions(GetDefaultSize(SuggestedMinimumWidth, widthMeasureSpec),
529 GetDefaultSize(SuggestedMinimumHeight, heightMeasureSpec));
532 internal virtual void OnMeasureIndependentChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) { }
535 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
536 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
538 /// <param name="changed">This is a new size or position for this layout.</param>
539 /// <param name="left">Left position, relative to parent.</param>
540 /// <param name="top">Top position, relative to parent.</param>
541 /// <param name="right">Right position, relative to parent.</param>
542 /// <param name="bottom">Bottom position, relative to parent.</param>
543 /// <since_tizen> 6 </since_tizen>
544 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
546 internal virtual void OnLayoutIndependentChildren(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
549 /// Virtual method to allow derived classes to remove any children before it is removed from
552 /// <since_tizen> 6 </since_tizen>
553 protected virtual void OnUnparent() { }
556 /// Virtual method called when this Layout is attached to it's owner.
557 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
559 /// <since_tizen> 6 </since_tizen>
560 protected virtual void OnAttachedToOwner() { }
562 private bool SetFrame(float left, float top, float right, float bottom, bool independent)
564 bool changed = false;
566 if (_layoutPositionData.Left != left ||
567 _layoutPositionData.Right != right ||
568 _layoutPositionData.Top != top ||
569 _layoutPositionData.Bottom != bottom)
573 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
574 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
575 float newWidth = right - left;
576 float newHeight = bottom - top;
577 bool sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
579 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
580 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
582 ConditionForAnimation = TransitionCondition.LayoutChanged;
585 // Store new layout position data
586 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
588 Debug.WriteLineIf(LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
589 " left:" + _layoutPositionData.Left +
590 " top:" + _layoutPositionData.Top +
591 " right:" + _layoutPositionData.Right +
592 " bottom:" + _layoutPositionData.Bottom);
594 Container onwerContainer = Owner.GetParent();
595 View onwerView = onwerContainer is Layer ? new View(Layer.getCPtr(onwerContainer).Handle, false) : onwerContainer as View;
597 if (onwerView != null && onwerView.Layout != null && onwerView.Layout.LayoutWithTransition)
599 NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(_layoutPositionData);
603 if (Owner.Position != null)
607 // If height or width specification is not explicitly defined,
608 // the size of the owner view must be reset even the ExcludeLayouting is true.
609 if (Owner.HeightSpecification < 0 || Owner.WidthSpecification < 0)
611 Owner.SetSize(right - left, bottom - top);
616 Owner.SetSize(right - left, bottom - top);
617 Owner.SetPosition(left, top);
622 // Reset condition for animation ready for next transition when required.
623 ConditionForAnimation = TransitionCondition.Unspecified;
630 [EditorBrowsable(EditorBrowsableState.Never)]
631 protected virtual void Dispose(bool disposing)
646 [EditorBrowsable(EditorBrowsableState.Never)]
647 public void Dispose()
650 global::System.GC.SuppressFinalize(this);