Merge "Fix nested layout issue" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / layouting / linear-layout-impl.cpp
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 //CLASS HEADER
18 #include "linear-layout-impl.h"
19
20 //PUBLIC INCLUDES
21 #include <dali/integration-api/debug.h>
22 #include <dali/public-api/common/extents.h>
23 #include <dali/public-api/actors/actor.h>
24
25 //INTERNAL INCLUDES
26 #include <dali-toolkit/devel-api/layouting/layout-item.h>
27 #include <dali-toolkit/public-api/controls/control-impl.h>
28 #include <dali-toolkit/internal/controls/control/control-data-impl.h>
29
30 namespace
31 {
32 #if defined(DEBUG_ENABLED)
33 static Debug::Filter* gLogFilter = Debug::Filter::New( Debug::NoLogging, false, "LOG_LAYOUT" );
34 #endif
35 }
36
37 namespace Dali
38 {
39 namespace Toolkit
40 {
41 namespace Internal
42 {
43
44 LinearLayoutPtr LinearLayout::New()
45 {
46   LinearLayoutPtr layout( new LinearLayout() );
47   return layout;
48 }
49
50 LinearLayout::LinearLayout()
51 : LayoutGroup(),
52   mCellPadding( 0, 0 ),
53   mOrientation( Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL ),
54   mTotalLength( 0 )
55 {
56 }
57
58 LinearLayout::~LinearLayout()
59 {
60 }
61
62 void LinearLayout::SetCellPadding( LayoutSize size )
63 {
64   mCellPadding = size;
65 }
66
67 LayoutSize LinearLayout::GetCellPadding()
68 {
69   return mCellPadding;
70 }
71
72 void LinearLayout::SetOrientation( Dali::Toolkit::LinearLayout::Orientation orientation )
73 {
74   mOrientation = orientation;
75 }
76
77 Dali::Toolkit::LinearLayout::Orientation LinearLayout::GetOrientation()
78 {
79   return mOrientation;
80 }
81
82 void LinearLayout::OnMeasure( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
83 {
84 #if defined(DEBUG_ENABLED)
85   auto actor = Actor::DownCast(GetOwner());
86
87   std::ostringstream oss;
88   oss << "LinearLayout::OnMeasure  ";
89   if( actor )
90   {
91     oss << "Actor Id:" << actor.GetId() << " Name:" << actor.GetName() << "  ";
92   }
93   oss << "widthMeasureSpec:" << widthMeasureSpec << " heightMeasureSpec:" << heightMeasureSpec << std::endl;
94   DALI_LOG_INFO( gLogFilter, Debug::Concise, oss.str().c_str() );
95 #endif
96
97   if( mOrientation == Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL )
98   {
99     MeasureHorizontal( widthMeasureSpec, heightMeasureSpec );
100   }
101   else
102   {
103     MeasureVertical( widthMeasureSpec, heightMeasureSpec );
104   }
105 }
106
107 void LinearLayout::OnLayout( bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
108 {
109 #if defined(DEBUG_ENABLED)
110   auto actor = Actor::DownCast(GetOwner());
111
112   std::ostringstream oss;
113   oss << "LinearLayout::OnLayout  ";
114   if( actor )
115   {
116     oss << "Actor Id:" << actor.GetId() << " Name:" << actor.GetName() << "  ";
117   }
118   oss << "left:" << left << " top:" << top << " right:" << right << " bottom:" << bottom << std::endl;
119   DALI_LOG_INFO( gLogFilter, Debug::Concise, oss.str().c_str() );
120 #endif
121
122   if( mOrientation == Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL )
123   {
124     LayoutHorizontal( left, top, right, bottom );
125   }
126   else
127   {
128     LayoutVertical( left, top, right, bottom );
129   }
130 }
131
132 void LinearLayout::MeasureHorizontal( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
133 {
134   auto widthMode = widthMeasureSpec.GetMode();
135   auto heightMode = heightMeasureSpec.GetMode();
136   bool isExactly = (widthMode == MeasureSpec::Mode::EXACTLY);
137   bool matchHeight = false;
138   bool allFillParent = true;
139   LayoutLength maxHeight = 0;
140   LayoutLength alternativeMaxHeight = 0;
141   struct
142   {
143     MeasuredSize::State widthState;
144     MeasuredSize::State heightState;
145   } childState = { MeasuredSize::State::MEASURED_SIZE_OK, MeasuredSize::State::MEASURED_SIZE_OK };
146
147   // measure children, and determine if further resolution is required
148   for( unsigned int i=0; i<GetChildCount(); ++i )
149   {
150     auto childLayout = GetChildAt( i );
151     if( childLayout )
152     {
153       auto childOwner = childLayout->GetOwner();
154       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
155
156       MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
157       auto childWidth = childLayout->GetMeasuredWidth();
158       auto childMargin = childLayout->GetMargin();
159
160       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "LinearLayout::OnMeasure childWidth(%d)\n", MeasureSpec::IntType( childWidth ) );
161
162       auto length = childWidth + LayoutLength::IntType(childMargin.start + childMargin.end);
163
164       auto cellPadding = i<GetChildCount()-1 ? mCellPadding.width: 0;
165
166       if( isExactly )
167       {
168         mTotalLength += length;
169       }
170       else
171       {
172         auto totalLength = mTotalLength;
173         mTotalLength = std::max( totalLength, totalLength + length + cellPadding );
174       }
175
176       bool matchHeightLocally = false;
177       if( heightMode != MeasureSpec::Mode::EXACTLY && desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT )
178       {
179         // Will have to re-measure at least this child when we know exact height.
180         matchHeight = true;
181         matchHeightLocally = true;
182       }
183
184       auto marginHeight = LayoutLength( childMargin.top + childMargin.bottom );
185       auto childHeight = childLayout->GetMeasuredHeight() + marginHeight;
186
187       if( childLayout->GetMeasuredWidthAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
188       {
189         childState.widthState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
190       }
191       if( childLayout->GetMeasuredHeightAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
192       {
193         childState.heightState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
194       }
195
196       maxHeight = std::max( maxHeight, childHeight );
197       allFillParent = ( allFillParent && desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT );
198       alternativeMaxHeight = std::max( alternativeMaxHeight, matchHeightLocally ? marginHeight : childHeight );
199     }
200   }
201
202   Extents padding = GetPadding();
203   mTotalLength += padding.start + padding.end;
204   auto widthSize = mTotalLength;
205   widthSize = std::max( widthSize, GetSuggestedMinimumWidth() );
206   MeasuredSize widthSizeAndState = ResolveSizeAndState( widthSize, widthMeasureSpec, MeasuredSize::State::MEASURED_SIZE_OK);
207   widthSize = widthSizeAndState.GetSize();
208
209   if( !allFillParent && heightMode != MeasureSpec::Mode::EXACTLY )
210   {
211     maxHeight = alternativeMaxHeight;
212   }
213   maxHeight += padding.top + padding.bottom;
214   maxHeight = std::max( maxHeight, GetSuggestedMinimumHeight() );
215
216   widthSizeAndState.SetState( childState.widthState );
217
218   SetMeasuredDimensions( widthSizeAndState,
219                          ResolveSizeAndState( maxHeight, heightMeasureSpec, childState.heightState ) );
220
221   if( matchHeight )
222   {
223     ForceUniformHeight( GetChildCount(), widthMeasureSpec );
224   }
225 }
226
227 void LinearLayout::ForceUniformHeight( int count, MeasureSpec widthMeasureSpec )
228 {
229   // Pretend that the linear layout has an exact size. This is the measured height of
230   // ourselves. The measured height should be the max height of the children, changed
231   // to accommodate the heightMeasureSpec from the parent
232   auto uniformMeasureSpec = MeasureSpec( GetMeasuredHeight(), MeasureSpec::Mode::EXACTLY );
233   for (int i = 0; i < count; ++i)
234   {
235     LayoutItemPtr childLayout = GetChildAt(i);
236     if( childLayout != nullptr )
237     {
238       auto childOwner = childLayout->GetOwner();
239       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
240       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
241
242       if( desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT )
243       {
244         // Temporarily force children to reuse their old measured width
245         int oldWidth = desiredWidth;
246         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION, childLayout->GetMeasuredWidth().mValue );
247
248         // Remeasure with new dimensions
249         MeasureChildWithMargins( childLayout, widthMeasureSpec, 0, uniformMeasureSpec, 0);
250
251         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION, oldWidth );
252       }
253     }
254   }
255 }
256
257 void LinearLayout::LayoutHorizontal( LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
258 {
259   auto owner = GetOwner();
260   auto actor = Actor::DownCast(owner);
261   bool isLayoutRtl = actor ? actor.GetProperty<bool>( Actor::Property::LAYOUT_DIRECTION ) : false;
262
263   Extents padding = GetPadding();
264
265   LayoutLength childTop( 0 );
266   LayoutLength childLeft( padding.start );
267
268   // Where bottom of child should go
269   auto height = bottom - top;
270
271   // Space available for child
272   auto childSpace = height - padding.top - padding.bottom;
273
274   auto count = GetChildCount();
275
276   int start = 0;
277   int dir = 1;
278
279   // In case of RTL, start drawing from the last child.
280   // @todo re-work to draw the first child from the right edge, and move leftwards.
281   // (Should have an alignment also)
282   if( isLayoutRtl ) {
283     start = count - 1;
284     dir = -1;
285   }
286
287   for( unsigned int i = 0; i < count; i++)
288   {
289     int childIndex = start + dir * i;
290     LayoutItemPtr childLayout = GetChildAt( childIndex );
291     if( childLayout != nullptr )
292     {
293       auto childWidth = childLayout->GetMeasuredWidth();
294       auto childHeight = childLayout->GetMeasuredHeight();
295       auto childMargin = childLayout->GetMargin();
296
297       childTop = LayoutLength(padding.top) + ((childSpace - childHeight) / 2) + childMargin.top - childMargin.bottom;
298
299       childLeft += childMargin.start;
300       childLayout->Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
301       childLeft += childWidth + childMargin.end + mCellPadding.width;
302     }
303   }
304 }
305
306 void LinearLayout::MeasureVertical( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
307 {
308   auto widthMode = widthMeasureSpec.GetMode();
309
310   bool matchWidth = false;
311   bool allFillParent = true;
312   LayoutLength maxWidth = 0;
313   LayoutLength alternativeMaxWidth = 0;
314
315   struct
316   {
317     MeasuredSize::State widthState;
318     MeasuredSize::State heightState;
319   } childState = { MeasuredSize::State::MEASURED_SIZE_OK, MeasuredSize::State::MEASURED_SIZE_OK };
320
321   // measure children, and determine if further resolution is required
322   for( unsigned int i=0; i<GetChildCount(); ++i )
323   {
324     auto childLayout = GetChildAt( i );
325     if( childLayout )
326     {
327       auto childOwner = childLayout->GetOwner();
328       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
329
330       MeasureChildWithMargins( childLayout, widthMeasureSpec, 0, heightMeasureSpec, 0 );
331       auto childHeight = childLayout->GetMeasuredHeight();
332       auto childMargin = childLayout->GetMargin();
333       auto length = childHeight + LayoutLength::IntType(childMargin.top + childMargin.bottom );
334
335       auto cellPadding = i<GetChildCount()-1 ? mCellPadding.height : 0;
336       auto totalLength = mTotalLength;
337       mTotalLength = std::max( totalLength, totalLength + length + cellPadding);
338
339       bool matchWidthLocally = false;
340       if( widthMode != MeasureSpec::Mode::EXACTLY && desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT )
341       {
342         // Will have to re-measure at least this child when we know exact height.
343         matchWidth = true;
344         matchWidthLocally = true;
345       }
346
347       auto marginWidth = LayoutLength( childMargin.start + childMargin.end );
348       auto childWidth = childLayout->GetMeasuredWidth() + marginWidth;
349
350       // was combineMeasuredStates()
351       if( childLayout->GetMeasuredWidthAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
352       {
353         childState.widthState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
354       }
355       if( childLayout->GetMeasuredHeightAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
356       {
357         childState.heightState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
358       }
359
360       maxWidth = std::max( maxWidth, childWidth );
361       allFillParent = ( allFillParent && desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT );
362       alternativeMaxWidth = std::max( alternativeMaxWidth, matchWidthLocally ? marginWidth : childWidth );
363     }
364   }
365   Extents padding = GetPadding();
366   mTotalLength += padding.top + padding.bottom;
367   auto heightSize = mTotalLength;
368   heightSize = std::max( heightSize, GetSuggestedMinimumHeight() );
369   MeasuredSize heightSizeAndState = ResolveSizeAndState( heightSize, heightMeasureSpec, MeasuredSize::State::MEASURED_SIZE_OK);
370   heightSize = heightSizeAndState.GetSize();
371
372   if( !allFillParent && widthMode != MeasureSpec::Mode::EXACTLY )
373   {
374     maxWidth = alternativeMaxWidth;
375   }
376   maxWidth += padding.start + padding.end;
377   maxWidth = std::max( maxWidth, GetSuggestedMinimumWidth() );
378
379   heightSizeAndState.SetState( childState.heightState );
380
381   SetMeasuredDimensions( ResolveSizeAndState( maxWidth, widthMeasureSpec, childState.widthState ),
382                          heightSizeAndState );
383
384   if( matchWidth )
385   {
386     ForceUniformWidth( GetChildCount(), heightMeasureSpec );
387   }
388 }
389
390 void LinearLayout::ForceUniformWidth( int count, MeasureSpec heightMeasureSpec )
391 {
392   // Pretend that the linear layout has an exact size.
393   auto uniformMeasureSpec = MeasureSpec( GetMeasuredWidth(), MeasureSpec::Mode::EXACTLY );
394   for (int i = 0; i < count; ++i)
395   {
396     LayoutItemPtr childLayout = GetChildAt(i);
397     if( childLayout != nullptr )
398     {
399       auto childOwner = childLayout->GetOwner();
400       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
401       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
402
403       if( desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT )
404       {
405         // Temporarily force children to reuse their old measured height
406         int oldHeight = desiredHeight;
407         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION, childLayout->GetMeasuredHeight().mValue );
408
409         // Remeasure with new dimensions
410         MeasureChildWithMargins( childLayout, uniformMeasureSpec, 0, heightMeasureSpec, 0 );
411
412         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION, oldHeight );
413       }
414     }
415   }
416 }
417
418 void LinearLayout::LayoutVertical( LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
419 {
420   Extents padding = GetPadding();
421
422   LayoutLength childTop( 0 );
423   LayoutLength childLeft( padding.start );
424
425   // Where bottom of child should go
426   auto width = right - left;
427
428   // Space available for child
429   auto childSpace = width - padding.start - padding.end;
430   auto count = GetChildCount();
431
432   for( unsigned int childIndex = 0; childIndex < count; childIndex++)
433   {
434     LayoutItemPtr childLayout = GetChildAt( childIndex );
435     if( childLayout != nullptr )
436     {
437       auto childWidth = childLayout->GetMeasuredWidth();
438       auto childHeight = childLayout->GetMeasuredHeight();
439       auto childMargin = childLayout->GetMargin();
440
441       childTop += childMargin.top;
442       childLeft = ( childSpace - childWidth ) / 2 + childMargin.start - childMargin.end;
443
444       childLayout->Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
445       childTop += childHeight + childMargin.bottom + mCellPadding.height;
446     }
447   }
448 }
449
450 } // namespace Internal
451 } // namespace Toolkit
452 } // namespace Dali