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;
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] Set position by layouting result
79 [EditorBrowsable(EditorBrowsableState.Never)]
80 public bool SetPositionByLayout{get;set;} = true;
83 /// [Draft] Margin for this LayoutItem
85 /// <since_tizen> 6 </since_tizen>
100 /// [Draft] Padding for this LayoutItem
102 /// <since_tizen> 6 </since_tizen>
103 public Extents Padding
117 /// [Draft] Constructor
119 /// <since_tizen> 6 </since_tizen>
126 /// [Draft] Set parent to this layout.
128 /// <param name="parent">Parent to set on this Layout.</param>
129 internal void SetParent( ILayoutParent parent)
131 Parent = parent as LayoutGroup;
135 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
137 internal void Unparent()
139 // Enable directly derived types to first remove children
142 // Remove myself from parent
143 Parent?.Remove( this );
145 // Remove parent reference
148 // Lastly, clear layout from owning View.
149 Owner?.ResetLayout();
152 private void Initialize()
154 LayoutWithTransition = false;
155 _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
156 _padding = new Extents(0,0,0,0);
157 _margin = new Extents(0,0,0,0);
161 /// Initialize the layout and allow derived classes to also perform any operations
163 /// <param name="owner">Owner of this Layout.</param>
164 internal void AttachToOwner(View owner)
166 // Assign the layout owner.
169 // Add layout to parent layout if a layout container
170 View parent = Owner.GetParent() as View;
171 (parent?.Layout as LayoutGroup)?.Add( this );
173 // If Add or ChangeOnAdd then do not update condition
174 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
176 ConditionForAnimation = TransitionCondition.LayoutChanged;
181 /// This is called to find out how big a layout should be. <br />
182 /// The parent supplies constraint information in the width and height parameters. <br />
183 /// The actual measurement work of a layout is performed in OnMeasure called by this
184 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
186 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
187 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
188 /// <since_tizen> 6 </since_tizen>
189 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
191 // Check if relayouting is required.
192 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
193 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
194 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
195 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
197 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
198 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
200 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
201 (MeasuredHeight.Size == heightMeasureSpec.Size);
203 bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
204 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
208 OnMeasure(widthMeasureSpec, heightMeasureSpec);
209 Flags = Flags | LayoutFlags.LayoutRequired;
210 Flags &= ~LayoutFlags.ForceLayout;
212 OldWidthMeasureSpec = widthMeasureSpec;
213 OldHeightMeasureSpec = heightMeasureSpec;
217 /// Assign a size and position to a layout and all of its descendants. <br />
218 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
219 /// calls layout on all of its children to position them. This is typically done using the child<br />
220 /// measurements that were stored in the measure pass.<br />
222 /// <param name="left">Left position, relative to parent.</param>
223 /// <param name="top">Top position, relative to parent.</param>
224 /// <param name="right">Right position, relative to parent.</param>
225 /// <param name="bottom">Bottom position, relative to parent.</param>
226 /// <since_tizen> 6 </since_tizen>
227 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
229 bool changed = SetFrame(left.AsRoundedValue(),
230 top.AsRoundedValue(),
231 right.AsRoundedValue(),
232 bottom.AsRoundedValue());
234 // Check if Measure needed before Layouting
235 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
237 OnLayout(changed, left, top, right, bottom);
239 Flags &= ~LayoutFlags.LayoutRequired;
244 /// Utility to return a default size.<br />
245 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
246 /// MeasureSpecification.<br />
248 /// <param name="size"> Default size for this layout.</param>
249 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
250 /// <returns>The size this layout should be.</returns>
251 /// <since_tizen> 6 </since_tizen>
252 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
254 LayoutLength result = size;
255 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
256 LayoutLength specSize = measureSpecification.Size;
260 case MeasureSpecification.ModeType.Unspecified:
265 case MeasureSpecification.ModeType.AtMost:
267 // Ensure the default size does not exceed the spec size unless the default size is 0.
268 // Another container could provide a default size of 0.
270 // Do not set size to 0, use specSize in this case as could be a legacy container
271 if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
281 case MeasureSpecification.ModeType.Exactly:
292 /// Get the Layouts parent
294 /// <returns>Layout parent with an LayoutParent interface</returns>
295 /// <since_tizen> 6 </since_tizen>
296 public ILayoutParent GetParent()
302 /// Request that this layout is re-laid out.<br />
303 /// This will make this layout and all it's parent layouts dirty.<br />
305 /// <since_tizen> 6 </since_tizen>
306 public void RequestLayout()
308 Flags = Flags | LayoutFlags.ForceLayout;
311 LayoutGroup layoutGroup = Parent as LayoutGroup;
312 if(layoutGroup != null && !layoutGroup.LayoutRequested)
314 layoutGroup.RequestLayout();
321 /// Predicate to determine if this layout has been requested to re-layout.<br />
324 internal bool LayoutRequested
328 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
332 internal void SetReplaceFlag()
334 parentReplacement = true;
337 internal bool IsReplaceFlag()
339 return parentReplacement;
342 internal void ClearReplaceFlag()
344 parentReplacement = false;
348 /// Get the measured width (without any measurement flags).<br />
349 /// This method should be used only during measurement and layout calculations.<br />
351 /// <since_tizen> 6 </since_tizen>
352 public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
355 /// Get the measured height (without any measurement flags).<br />
356 /// This method should be used only during measurement and layout calculations.<br />
358 /// <since_tizen> 6 </since_tizen>
359 public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
362 /// Returns the suggested minimum width that the layout should use.<br />
363 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
365 /// <since_tizen> 6 </since_tizen>
366 public LayoutLength SuggestedMinimumWidth
370 float maximumWidth = Owner.MaximumSize.Width;
371 float minimumWidth = Owner.MinimumSize.Width;
373 float baseHeight = Owner.MaximumSize.Height > 0 ? Math.Min(Owner.MaximumSize.Height,Owner.NaturalSize.Height) : Owner.NaturalSize.Height;
374 float baseWidth = Owner.GetWidthForHeight(baseHeight);
376 float result = minimumWidth > 0 ? Math.Max(baseWidth, minimumWidth) : baseWidth;
377 result = maximumWidth > 0 ? Math.Min(result, maximumWidth) : result;
379 return new LayoutLength(result);
384 /// Returns the suggested minimum height that the layout should use.<br />
385 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
387 /// <since_tizen> 6 </since_tizen>
388 public LayoutLength SuggestedMinimumHeight
392 float maximumHeight = Owner.MaximumSize.Height;
393 float minimumHeight = Owner.MinimumSize.Height;
395 float baseWidth = Owner.MaximumSize.Width > 0 ? Math.Min(Owner.MaximumSize.Width,Owner.NaturalSize.Width) : Owner.NaturalSize.Width;
396 float baseHeight = Owner.GetHeightForWidth(baseWidth);
398 float result = minimumHeight > 0 ? Math.Max(baseHeight, minimumHeight) : baseHeight;
399 result = maximumHeight > 0 ? Math.Min(result, maximumHeight) : result;
401 return new LayoutLength(result);
406 /// Sets the minimum width of the layout.<br />
407 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
408 /// layout constrains it with less available width).<br />
409 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
410 /// 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 />
411 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
413 internal LayoutLength MinimumWidth {get; set;}
416 /// Sets the minimum height of the layout.<br />
417 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
418 /// layout constrains it with less available height).<br />
419 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
420 /// 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 />
421 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
423 internal LayoutLength MinimumHeight {get; set;}
426 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
428 /// <param name="size"> How big the layout wants to be.</param>
429 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
430 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
431 /// <returns> A measured size, which may indicate that it is too small. </returns>
432 /// <since_tizen> 6 </since_tizen>
433 protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
435 var specMode = measureSpecification.Mode;
436 LayoutLength specSize = measureSpecification.Size;
437 MeasuredSize result = new MeasuredSize( size, childMeasuredState );
441 case MeasureSpecification.ModeType.AtMost:
443 if (specSize.AsRoundedValue() < size.AsRoundedValue())
445 result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
450 case MeasureSpecification.ModeType.Exactly:
452 result.Size = specSize;
456 case MeasureSpecification.ModeType.Unspecified:
466 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
468 /// <param name="measuredWidth">The measured width of this layout.</param>
469 /// <param name="measuredHeight">The measured height of this layout.</param>
470 /// <since_tizen> 6 </since_tizen>
471 protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
473 MeasuredWidth = measuredWidth;
474 MeasuredHeight = measuredHeight;
475 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
479 /// Measure the layout and its content to determine the measured width and the
480 /// measured height.<br />
481 /// The base class implementation of measure defaults to the background size,
482 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
483 /// override to provide better measurements of their content.<br />
484 /// If this method is overridden, it is the subclass's responsibility to make sure the
485 /// measured height and width are at least the layout's minimum height and width.<br />
487 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
488 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
489 /// <since_tizen> 6 </since_tizen>
490 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
492 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
493 SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
494 GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
498 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
499 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
501 /// <param name="changed">This is a new size or position for this layout.</param>
502 /// <param name="left">Left position, relative to parent.</param>
503 /// <param name="top">Top position, relative to parent.</param>
504 /// <param name="right">Right position, relative to parent.</param>
505 /// <param name="bottom">Bottom position, relative to parent.</param>
506 /// <since_tizen> 6 </since_tizen>
507 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { }
510 /// Virtual method to allow derived classes to remove any children before it is removed from
513 /// <since_tizen> 6 </since_tizen>
514 protected virtual void OnUnparent() { }
517 /// Virtual method called when this Layout is attached to it's owner.
518 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
520 /// <since_tizen> 6 </since_tizen>
521 protected virtual void OnAttachedToOwner() { }
523 private bool SetFrame(float left, float top, float right, float bottom)
525 bool changed = false;
527 if ( _layoutPositionData.Left != left ||
528 _layoutPositionData.Right != right ||
529 _layoutPositionData.Top != top ||
530 _layoutPositionData.Bottom != bottom )
534 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
535 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
536 float newWidth = right - left;
537 float newHeight = bottom - top;
538 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
540 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
541 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
543 ConditionForAnimation = TransitionCondition.LayoutChanged;
546 // Store new layout position data
547 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
549 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
550 " left:" + _layoutPositionData.Left +
551 " top:" + _layoutPositionData.Top +
552 " right:" + _layoutPositionData.Right +
553 " bottom:" + _layoutPositionData.Bottom );
555 if (Owner.Parent != null && Owner.Parent.Layout != null && Owner.Parent.Layout.LayoutWithTransition)
557 NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(_layoutPositionData);
561 if (Owner.Position != null)
563 Owner.SetSize(right - left, bottom - top, Owner.Position.Z);
564 if (SetPositionByLayout)
566 Owner.SetPosition(left, top, Owner.Position.Z);
571 // Reset condition for animation ready for next transition when required.
572 ConditionForAnimation = TransitionCondition.Unspecified;