2 * Copyright (c) 2019 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;
55 /// [Draft] Condition event that is causing this Layout to transition.
57 internal TransitionCondition ConditionForAnimation{get; set;}
60 /// [Draft] The View that this Layout has been assigned to.
62 /// <since_tizen> 6 </since_tizen>
63 public View Owner{get; set;} // Should not keep a View alive.
66 /// [Draft] Margin for this LayoutItem
68 /// <since_tizen> 6 </since_tizen>
83 /// [Draft] Padding for this LayoutItem
85 /// <since_tizen> 6 </since_tizen>
86 public Extents Padding
100 /// [Draft] Constructor
102 /// <since_tizen> 6 </since_tizen>
109 /// [Draft] Set parent to this layout.
111 /// <param name="parent">Parent to set on this Layout.</param>
112 internal void SetParent( ILayoutParent parent)
114 Parent = parent as LayoutGroup;
118 /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
120 internal void Unparent()
122 // Enable directly derived types to first remove children
125 // Remove myself from parent
126 Parent?.Remove( this );
128 // Remove parent reference
131 // Lastly, clear layout from owning View.
132 Owner?.ResetLayout();
135 private void Initialize()
137 _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
138 _padding = new Extents(0,0,0,0);
139 _margin = new Extents(0,0,0,0);
143 /// Initialize the layout and allow derived classes to also perform any operations
145 /// <param name="owner">Owner of this Layout.</param>
146 internal void AttachToOwner(View owner)
148 // Assign the layout owner.
151 // Add layout to parent layout if a layout container
152 View parent = Owner.GetParent() as View;
153 (parent?.Layout as LayoutGroup)?.Add( this );
155 // If Add or ChangeOnAdd then do not update condition
156 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
158 ConditionForAnimation = TransitionCondition.LayoutChanged;
163 /// This is called to find out how big a layout should be. <br />
164 /// The parent supplies constraint information in the width and height parameters. <br />
165 /// The actual measurement work of a layout is performed in OnMeasure called by this
166 /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
168 /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
169 /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
170 /// <since_tizen> 6 </since_tizen>
171 public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
173 // Check if relayouting is required.
174 bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
175 (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
176 (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
177 (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
179 bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
180 (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
182 bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
183 (MeasuredHeight.Size == heightMeasureSpec.Size);
185 bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
186 needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
190 OnMeasure(widthMeasureSpec, heightMeasureSpec);
191 Flags = Flags | LayoutFlags.LayoutRequired;
192 Flags &= ~LayoutFlags.ForceLayout;
194 OldWidthMeasureSpec = widthMeasureSpec;
195 OldHeightMeasureSpec = heightMeasureSpec;
199 /// Assign a size and position to a layout and all of its descendants. <br />
200 /// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
201 /// calls layout on all of its children to position them. This is typically done using the child<br />
202 /// measurements that were stored in the measure pass.<br />
204 /// <param name="left">Left position, relative to parent.</param>
205 /// <param name="top">Top position, relative to parent.</param>
206 /// <param name="right">Right position, relative to parent.</param>
207 /// <param name="bottom">Bottom position, relative to parent.</param>
208 /// <since_tizen> 6 </since_tizen>
209 public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
211 bool changed = SetFrame(left.AsRoundedValue(),
212 top.AsRoundedValue(),
213 right.AsRoundedValue(),
214 bottom.AsRoundedValue());
216 // Check if Measure needed before Layouting
217 if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
219 OnLayout(changed, left, top, right, bottom);
221 Flags &= ~LayoutFlags.LayoutRequired;
226 /// Utility to return a default size.<br />
227 /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
228 /// MeasureSpecification.<br />
230 /// <param name="size"> Default size for this layout.</param>
231 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
232 /// <returns>The size this layout should be.</returns>
233 /// <since_tizen> 6 </since_tizen>
234 public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
236 LayoutLength result = size;
237 MeasureSpecification.ModeType specMode = measureSpecification.Mode;
238 LayoutLength specSize = measureSpecification.Size;
242 case MeasureSpecification.ModeType.Unspecified:
247 case MeasureSpecification.ModeType.AtMost:
249 // Ensure the default size does not exceed the spec size unless the default size is 0.
250 // Another container could provide a default size of 0.
252 // Do not set size to 0, use specSize in this case as could be a legacy container
253 if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
263 case MeasureSpecification.ModeType.Exactly:
274 /// Get the Layouts parent
276 /// <returns>Layout parent with an LayoutParent interface</returns>
277 /// <since_tizen> 6 </since_tizen>
278 public ILayoutParent GetParent()
284 /// Request that this layout is re-laid out.<br />
285 /// This will make this layout and all it's parent layouts dirty.<br />
287 /// <since_tizen> 6 </since_tizen>
288 public void RequestLayout()
290 Flags = Flags | LayoutFlags.ForceLayout;
291 Window.Instance.LayoutController.RequestLayout(this);
295 /// Predicate to determine if this layout has been requested to re-layout.<br />
298 internal bool LayoutRequested
302 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
307 /// Get the measured width (without any measurement flags).<br />
308 /// This method should be used only during measurement and layout calculations.<br />
310 /// <since_tizen> 6 </since_tizen>
311 public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
314 /// Get the measured height (without any measurement flags).<br />
315 /// This method should be used only during measurement and layout calculations.<br />
317 /// <since_tizen> 6 </since_tizen>
318 public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
321 /// Returns the suggested minimum width that the layout should use.<br />
322 /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
324 /// <since_tizen> 6 </since_tizen>
325 public LayoutLength SuggestedMinimumWidth
329 int naturalWidth = Owner.NaturalSize2D.Width;
330 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
335 /// Returns the suggested minimum height that the layout should use.<br />
336 /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
338 /// <since_tizen> 6 </since_tizen>
339 public LayoutLength SuggestedMinimumHeight
343 int naturalHeight = Owner.NaturalSize2D.Height;
344 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
349 /// Sets the minimum width of the layout.<br />
350 /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
351 /// layout constrains it with less available width).<br />
352 /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
353 /// 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 />
354 /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
356 internal LayoutLength MinimumWidth {get; set;}
359 /// Sets the minimum height of the layout.<br />
360 /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
361 /// layout constrains it with less available height).<br />
362 /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
363 /// 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 />
364 /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
366 internal LayoutLength MinimumHeight {get; set;}
369 /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
371 /// <param name="size"> How big the layout wants to be.</param>
372 /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
373 /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
374 /// <returns> A measured size, which may indicate that it is too small. </returns>
375 /// <since_tizen> 6 </since_tizen>
376 protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
378 var specMode = measureSpecification.Mode;
379 LayoutLength specSize = measureSpecification.Size;
380 MeasuredSize result = new MeasuredSize( size, childMeasuredState);
384 case MeasureSpecification.ModeType.AtMost:
386 if (specSize.AsRoundedValue() < size.AsRoundedValue())
388 result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
393 case MeasureSpecification.ModeType.Exactly:
395 result.Size = specSize;
399 case MeasureSpecification.ModeType.Unspecified:
409 /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
411 /// <param name="measuredWidth">The measured width of this layout.</param>
412 /// <param name="measuredHeight">The measured height of this layout.</param>
413 /// <since_tizen> 6 </since_tizen>
414 protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
416 MeasuredWidth = measuredWidth;
417 MeasuredHeight = measuredHeight;
418 Flags = Flags | LayoutFlags.MeasuredDimensionSet;
422 /// Measure the layout and its content to determine the measured width and the
423 /// measured height.<br />
424 /// The base class implementation of measure defaults to the background size,
425 /// unless a larger size is allowed by the MeasureSpec. Subclasses should
426 /// override to provide better measurements of their content.<br />
427 /// If this method is overridden, it is the subclass's responsibility to make sure the
428 /// measured height and width are at least the layout's minimum height and width.<br />
430 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
431 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
432 /// <since_tizen> 6 </since_tizen>
433 protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
435 // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
436 SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
437 GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
441 /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
442 /// Derived classes with children should override this method and call Layout() on each of their children. <br />
444 /// <param name="changed">This is a new size or position for this layout.</param>
445 /// <param name="left">Left position, relative to parent.</param>
446 /// <param name="top">Top position, relative to parent.</param>
447 /// <param name="right">Right position, relative to parent.</param>
448 /// <param name="bottom">Bottom position, relative to parent.</param>
449 /// <since_tizen> 6 </since_tizen>
450 protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
455 /// Virtual method to allow derived classes to remove any children before it is removed from
458 /// <since_tizen> 6 </since_tizen>
459 protected virtual void OnUnparent()
464 /// Virtual method called when this Layout is attached to it's owner.
465 /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
467 /// <since_tizen> 6 </since_tizen>
468 protected virtual void OnAttachedToOwner()
472 private bool SetFrame(float left, float top, float right, float bottom)
474 bool changed = false;
476 if ( _layoutPositionData.Left != left ||
477 _layoutPositionData.Right != right ||
478 _layoutPositionData.Top != top ||
479 _layoutPositionData.Bottom != bottom )
483 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
484 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
485 float newWidth = right - left;
486 float newHeight = bottom - top;
487 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
489 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
490 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
492 ConditionForAnimation = TransitionCondition.LayoutChanged;
495 // Store new layout position data
496 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
498 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
499 " left:" + _layoutPositionData.Left +
500 " top:" + _layoutPositionData.Top +
501 " right:" + _layoutPositionData.Right +
502 " bottom:" + _layoutPositionData.Bottom );
504 Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
506 // Reset condition for animation ready for next transition when required.
507 ConditionForAnimation = TransitionCondition.Unspecified;