Merge "Controls are LayoutGroups instead of LayoutItems" 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 const int HORIZONTAL_ALIGNMENT_MASK  = ( Dali::Toolkit::LinearLayout::Alignment::BEGIN | Dali::Toolkit::LinearLayout::Alignment::CENTER_HORIZONTAL | Dali::Toolkit::LinearLayout::Alignment::END );
45 const int VERTICAL_ALIGNMENT_MASK = ( Dali::Toolkit::LinearLayout::Alignment::TOP | Dali::Toolkit::LinearLayout::Alignment::CENTER_VERTICAL | Dali::Toolkit::LinearLayout::Alignment::BOTTOM );
46
47 LinearLayoutPtr LinearLayout::New()
48 {
49   LinearLayoutPtr layout( new LinearLayout() );
50   return layout;
51 }
52
53 LinearLayout::LinearLayout()
54 : LayoutGroup(),
55   mCellPadding( 0, 0 ),
56   mOrientation( Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL ),
57   mAlignment( Dali::Toolkit::LinearLayout::Alignment::BEGIN | Dali::Toolkit::LinearLayout::Alignment::CENTER_VERTICAL ),
58   mTotalLength( 0 )
59 {
60 }
61
62 LinearLayout::~LinearLayout()
63 {
64 }
65
66 void LinearLayout::SetCellPadding( LayoutSize size )
67 {
68   if ( mCellPadding != size )
69   {
70     mCellPadding = size;
71     RequestLayout();
72   }
73 }
74
75 LayoutSize LinearLayout::GetCellPadding() const
76 {
77   return mCellPadding;
78 }
79
80 void LinearLayout::SetOrientation( Dali::Toolkit::LinearLayout::Orientation orientation )
81 {
82   if ( mOrientation != orientation )
83   {
84     mOrientation = orientation;
85     RequestLayout();
86   }
87 }
88
89 Dali::Toolkit::LinearLayout::Orientation LinearLayout::GetOrientation() const
90 {
91   return mOrientation;
92 }
93
94 void LinearLayout::SetAlignment( unsigned int alignment )
95 {
96   if ( mAlignment != alignment )
97   {
98     mAlignment = alignment;
99     RequestLayout();
100   }
101 }
102
103 unsigned int LinearLayout::GetAlignment() const
104 {
105   return mAlignment;
106 }
107
108 void LinearLayout::OnMeasure( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
109 {
110 #if defined(DEBUG_ENABLED)
111   auto actor = Actor::DownCast(GetOwner());
112
113   std::ostringstream oss;
114   oss << "LinearLayout::OnMeasure  ";
115   if( actor )
116   {
117     oss << "Actor Id:" << actor.GetId() << " Name:" << actor.GetName() << "  ";
118   }
119   oss << "widthMeasureSpec:" << widthMeasureSpec << " heightMeasureSpec:" << heightMeasureSpec << std::endl;
120   DALI_LOG_INFO( gLogFilter, Debug::Concise, oss.str().c_str() );
121 #endif
122
123   if( mOrientation == Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL )
124   {
125     MeasureHorizontal( widthMeasureSpec, heightMeasureSpec );
126   }
127   else
128   {
129     MeasureVertical( widthMeasureSpec, heightMeasureSpec );
130   }
131 }
132
133 void LinearLayout::OnLayout( bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
134 {
135 #if defined(DEBUG_ENABLED)
136   auto actor = Actor::DownCast(GetOwner());
137
138   std::ostringstream oss;
139   oss << "LinearLayout::OnLayout  ";
140   if( actor )
141   {
142     oss << "Actor Id:" << actor.GetId() << " Name:" << actor.GetName() << "  ";
143   }
144   oss << "left:" << left << " top:" << top << " right:" << right << " bottom:" << bottom << std::endl;
145   DALI_LOG_INFO( gLogFilter, Debug::Concise, oss.str().c_str() );
146 #endif
147
148   if( mOrientation == Dali::Toolkit::LinearLayout::Orientation::HORIZONTAL )
149   {
150     LayoutHorizontal( left, top, right, bottom );
151   }
152   else
153   {
154     LayoutVertical( left, top, right, bottom );
155   }
156 }
157
158 void LinearLayout::MeasureHorizontal( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
159 {
160   auto widthMode = widthMeasureSpec.GetMode();
161   auto heightMode = heightMeasureSpec.GetMode();
162   bool isExactly = (widthMode == MeasureSpec::Mode::EXACTLY);
163   bool matchHeight = false;
164   bool allFillParent = true;
165   LayoutLength maxHeight = 0;
166   LayoutLength alternativeMaxHeight = 0;
167   struct
168   {
169     MeasuredSize::State widthState;
170     MeasuredSize::State heightState;
171   } childState = { MeasuredSize::State::MEASURED_SIZE_OK, MeasuredSize::State::MEASURED_SIZE_OK };
172
173   // Reset total length
174   mTotalLength = 0;
175
176   // measure children, and determine if further resolution is required
177   for( unsigned int i=0; i<GetChildCount(); ++i )
178   {
179     auto childLayout = GetChildAt( i );
180     if( childLayout )
181     {
182       auto childOwner = childLayout->GetOwner();
183       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
184
185       MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
186       auto childWidth = childLayout->GetMeasuredWidth();
187       auto childMargin = childLayout->GetMargin();
188
189       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "LinearLayout::OnMeasure childWidth(%d)\n", MeasureSpec::IntType( childWidth ) );
190
191       auto length = childWidth + LayoutLength::IntType(childMargin.start + childMargin.end);
192
193       auto cellPadding = i<GetChildCount()-1 ? mCellPadding.width: 0;
194
195       if( isExactly )
196       {
197         mTotalLength += length;
198       }
199       else
200       {
201         auto totalLength = mTotalLength;
202         mTotalLength = std::max( totalLength, totalLength + length + cellPadding );
203       }
204
205       bool matchHeightLocally = false;
206       if( heightMode != MeasureSpec::Mode::EXACTLY && desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT )
207       {
208         // Will have to re-measure at least this child when we know exact height.
209         matchHeight = true;
210         matchHeightLocally = true;
211       }
212
213       auto marginHeight = LayoutLength( childMargin.top + childMargin.bottom );
214       auto childHeight = childLayout->GetMeasuredHeight() + marginHeight;
215
216       if( childLayout->GetMeasuredWidthAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
217       {
218         childState.widthState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
219       }
220       if( childLayout->GetMeasuredHeightAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
221       {
222         childState.heightState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
223       }
224
225       maxHeight = std::max( maxHeight, childHeight );
226       allFillParent = ( allFillParent && desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT );
227       alternativeMaxHeight = std::max( alternativeMaxHeight, matchHeightLocally ? marginHeight : childHeight );
228     }
229   }
230
231   Extents padding = GetPadding();
232   mTotalLength += padding.start + padding.end;
233   auto widthSize = mTotalLength;
234   widthSize = std::max( widthSize, GetSuggestedMinimumWidth() );
235   MeasuredSize widthSizeAndState = ResolveSizeAndState( widthSize, widthMeasureSpec, MeasuredSize::State::MEASURED_SIZE_OK);
236   widthSize = widthSizeAndState.GetSize();
237
238   if( !allFillParent && heightMode != MeasureSpec::Mode::EXACTLY )
239   {
240     maxHeight = alternativeMaxHeight;
241   }
242   maxHeight += padding.top + padding.bottom;
243   maxHeight = std::max( maxHeight, GetSuggestedMinimumHeight() );
244
245   widthSizeAndState.SetState( childState.widthState );
246
247   SetMeasuredDimensions( widthSizeAndState,
248                          ResolveSizeAndState( maxHeight, heightMeasureSpec, childState.heightState ) );
249
250   if( matchHeight )
251   {
252     ForceUniformHeight( GetChildCount(), widthMeasureSpec );
253   }
254 }
255
256 void LinearLayout::ForceUniformHeight( int count, MeasureSpec widthMeasureSpec )
257 {
258   // Pretend that the linear layout has an exact size. This is the measured height of
259   // ourselves. The measured height should be the max height of the children, changed
260   // to accommodate the heightMeasureSpec from the parent
261   auto uniformMeasureSpec = MeasureSpec( GetMeasuredHeight(), MeasureSpec::Mode::EXACTLY );
262   for (int i = 0; i < count; ++i)
263   {
264     LayoutItemPtr childLayout = GetChildAt(i);
265     if( childLayout != nullptr )
266     {
267       auto childOwner = childLayout->GetOwner();
268       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
269       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
270
271       if( desiredHeight == Toolkit::ChildLayoutData::MATCH_PARENT )
272       {
273         // Temporarily force children to reuse their old measured width
274         int oldWidth = desiredWidth;
275         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION, childLayout->GetMeasuredWidth().mValue );
276
277         // Remeasure with new dimensions
278         MeasureChildWithMargins( childLayout, widthMeasureSpec, 0, uniformMeasureSpec, 0);
279
280         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION, oldWidth );
281       }
282     }
283   }
284 }
285
286 void LinearLayout::LayoutHorizontal( LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
287 {
288   auto owner = GetOwner();
289   auto actor = Actor::DownCast(owner);
290   bool isLayoutRtl = actor ? actor.GetProperty<bool>( Actor::Property::LAYOUT_DIRECTION ) : false;
291
292   Extents padding = GetPadding();
293
294   LayoutLength childTop( padding.top );
295   LayoutLength childLeft( padding.start );
296
297   // Where bottom of child should go
298   auto height = bottom - top;
299
300   // Space available for child
301   auto childSpace = height - padding.top - padding.bottom;
302
303   auto count = GetChildCount();
304
305   switch ( mAlignment & HORIZONTAL_ALIGNMENT_MASK )
306   {
307     case Dali::Toolkit::LinearLayout::Alignment::BEGIN:
308     default:
309     {
310       // mTotalLength contains the padding already
311       // In case of RTL map BEGIN alignment to the right edge
312       if ( isLayoutRtl ) {
313         childLeft = LayoutLength( padding.start ) + right - left - mTotalLength;
314       }
315       else {
316         childLeft = LayoutLength( padding.start );
317       }
318       break;
319     }
320     case Dali::Toolkit::LinearLayout::Alignment::END:
321     {
322       // mTotalLength contains the padding already
323       // In case of RTL map END alignment to the left edge
324       if ( isLayoutRtl ) {
325         childLeft = LayoutLength( padding.start );
326       }
327       else {
328         childLeft = LayoutLength( padding.start ) + right - left - mTotalLength;
329       }
330       break;
331     }
332     case Dali::Toolkit::LinearLayout::Alignment::CENTER_HORIZONTAL:
333     {
334       // mTotalLength contains the padding already
335       childLeft = LayoutLength( padding.start ) + ( right - left - mTotalLength ) / 2;
336       break;
337     }
338   }
339
340   int start = 0;
341   int dir = 1;
342
343   // In case of RTL, start drawing from the last child.
344   if( isLayoutRtl ) {
345     start = count - 1;
346     dir = -1;
347   }
348
349   for( unsigned int i = 0; i < count; i++)
350   {
351     int childIndex = start + dir * i;
352     LayoutItemPtr childLayout = GetChildAt( childIndex );
353     if( childLayout != nullptr )
354     {
355       auto childWidth = childLayout->GetMeasuredWidth();
356       auto childHeight = childLayout->GetMeasuredHeight();
357       auto childMargin = childLayout->GetMargin();
358
359       switch ( mAlignment & VERTICAL_ALIGNMENT_MASK )
360       {
361         case Dali::Toolkit::LinearLayout::Alignment::TOP:
362         {
363           childTop = LayoutLength( padding.top ) + childMargin.top;
364           break;
365         }
366         case Dali::Toolkit::LinearLayout::Alignment::BOTTOM:
367         {
368           childTop = height - padding.bottom - childHeight - childMargin.bottom;
369           break;
370         }
371         case Dali::Toolkit::LinearLayout::Alignment::CENTER_VERTICAL:
372         default:
373         {
374           childTop = LayoutLength( padding.top ) + ( ( childSpace - childHeight ) / 2 ) + childMargin.top - childMargin.bottom;
375           break;
376         }
377       }
378       childLeft += childMargin.start;
379       childLayout->Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
380       childLeft += childWidth + childMargin.end + mCellPadding.width;
381     }
382   }
383 }
384
385 void LinearLayout::MeasureVertical( MeasureSpec widthMeasureSpec, MeasureSpec heightMeasureSpec )
386 {
387   auto widthMode = widthMeasureSpec.GetMode();
388
389   bool matchWidth = false;
390   bool allFillParent = true;
391   LayoutLength maxWidth = 0;
392   LayoutLength alternativeMaxWidth = 0;
393
394   struct
395   {
396     MeasuredSize::State widthState;
397     MeasuredSize::State heightState;
398   } childState = { MeasuredSize::State::MEASURED_SIZE_OK, MeasuredSize::State::MEASURED_SIZE_OK };
399
400   // Reset total length
401   mTotalLength = 0;
402
403   // measure children, and determine if further resolution is required
404   for( unsigned int i=0; i<GetChildCount(); ++i )
405   {
406     auto childLayout = GetChildAt( i );
407     if( childLayout )
408     {
409       auto childOwner = childLayout->GetOwner();
410       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
411
412       MeasureChildWithMargins( childLayout, widthMeasureSpec, 0, heightMeasureSpec, 0 );
413       auto childHeight = childLayout->GetMeasuredHeight();
414       auto childMargin = childLayout->GetMargin();
415       auto length = childHeight + LayoutLength::IntType(childMargin.top + childMargin.bottom );
416
417       auto cellPadding = i<GetChildCount()-1 ? mCellPadding.height : 0;
418       auto totalLength = mTotalLength;
419       mTotalLength = std::max( totalLength, totalLength + length + cellPadding);
420
421       bool matchWidthLocally = false;
422       if( widthMode != MeasureSpec::Mode::EXACTLY && desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT )
423       {
424         // Will have to re-measure at least this child when we know exact height.
425         matchWidth = true;
426         matchWidthLocally = true;
427       }
428
429       auto marginWidth = LayoutLength( childMargin.start + childMargin.end );
430       auto childWidth = childLayout->GetMeasuredWidth() + marginWidth;
431
432       // was combineMeasuredStates()
433       if( childLayout->GetMeasuredWidthAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
434       {
435         childState.widthState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
436       }
437       if( childLayout->GetMeasuredHeightAndState().GetState() == MeasuredSize::State::MEASURED_SIZE_TOO_SMALL )
438       {
439         childState.heightState = MeasuredSize::State::MEASURED_SIZE_TOO_SMALL;
440       }
441
442       maxWidth = std::max( maxWidth, childWidth );
443       allFillParent = ( allFillParent && desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT );
444       alternativeMaxWidth = std::max( alternativeMaxWidth, matchWidthLocally ? marginWidth : childWidth );
445     }
446   }
447   Extents padding = GetPadding();
448   mTotalLength += padding.top + padding.bottom;
449   auto heightSize = mTotalLength;
450   heightSize = std::max( heightSize, GetSuggestedMinimumHeight() );
451   MeasuredSize heightSizeAndState = ResolveSizeAndState( heightSize, heightMeasureSpec, MeasuredSize::State::MEASURED_SIZE_OK);
452   heightSize = heightSizeAndState.GetSize();
453
454   if( !allFillParent && widthMode != MeasureSpec::Mode::EXACTLY )
455   {
456     maxWidth = alternativeMaxWidth;
457   }
458   maxWidth += padding.start + padding.end;
459   maxWidth = std::max( maxWidth, GetSuggestedMinimumWidth() );
460
461   heightSizeAndState.SetState( childState.heightState );
462
463   SetMeasuredDimensions( ResolveSizeAndState( maxWidth, widthMeasureSpec, childState.widthState ),
464                          heightSizeAndState );
465
466   if( matchWidth )
467   {
468     ForceUniformWidth( GetChildCount(), heightMeasureSpec );
469   }
470 }
471
472 void LinearLayout::ForceUniformWidth( int count, MeasureSpec heightMeasureSpec )
473 {
474   // Pretend that the linear layout has an exact size.
475   auto uniformMeasureSpec = MeasureSpec( GetMeasuredWidth(), MeasureSpec::Mode::EXACTLY );
476   for (int i = 0; i < count; ++i)
477   {
478     LayoutItemPtr childLayout = GetChildAt(i);
479     if( childLayout != nullptr )
480     {
481       auto childOwner = childLayout->GetOwner();
482       auto desiredWidth = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::WIDTH_SPECIFICATION );
483       auto desiredHeight = childOwner.GetProperty<int>( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION );
484
485       if( desiredWidth == Toolkit::ChildLayoutData::MATCH_PARENT )
486       {
487         // Temporarily force children to reuse their old measured height
488         int oldHeight = desiredHeight;
489         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION, childLayout->GetMeasuredHeight().mValue );
490
491         // Remeasure with new dimensions
492         MeasureChildWithMargins( childLayout, uniformMeasureSpec, 0, heightMeasureSpec, 0 );
493
494         childOwner.SetProperty( Toolkit::LayoutItem::ChildProperty::HEIGHT_SPECIFICATION, oldHeight );
495       }
496     }
497   }
498 }
499
500 void LinearLayout::LayoutVertical( LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
501 {
502   Extents padding = GetPadding();
503
504   LayoutLength childTop( padding.top );
505   LayoutLength childLeft( padding.start );
506
507   // Where end of child should go
508   auto width = right - left;
509
510   // Space available for child
511   auto childSpace = width - padding.start - padding.end;
512   auto count = GetChildCount();
513
514   switch ( mAlignment & VERTICAL_ALIGNMENT_MASK )
515   {
516     case Dali::Toolkit::LinearLayout::Alignment::TOP:
517     {
518       // mTotalLength contains the padding already
519       childTop = LayoutLength( padding.top );
520       break;
521     }
522     case Dali::Toolkit::LinearLayout::Alignment::BOTTOM:
523     {
524       // mTotalLength contains the padding already
525       childTop = LayoutLength( padding.top ) + bottom - top - mTotalLength;
526       break;
527     }
528     case Dali::Toolkit::LinearLayout::Alignment::CENTER_VERTICAL:
529     default:
530     {
531       // mTotalLength contains the padding already
532       childTop = LayoutLength( padding.top ) + ( bottom - top - mTotalLength ) / 2;
533       break;
534     }
535   }
536
537   for( unsigned int childIndex = 0; childIndex < count; childIndex++)
538   {
539     LayoutItemPtr childLayout = GetChildAt( childIndex );
540     if( childLayout != nullptr )
541     {
542       auto childWidth = childLayout->GetMeasuredWidth();
543       auto childHeight = childLayout->GetMeasuredHeight();
544       auto childMargin = childLayout->GetMargin();
545
546       childTop += childMargin.top;
547       switch ( mAlignment & HORIZONTAL_ALIGNMENT_MASK )
548       {
549         case Dali::Toolkit::LinearLayout::Alignment::BEGIN:
550         default:
551         {
552           childLeft = LayoutLength( padding.start ) + childMargin.start;
553           break;
554         }
555         case Dali::Toolkit::LinearLayout::Alignment::END:
556         {
557           childLeft = width - padding.end - childWidth - childMargin.end;
558           break;
559         }
560         case Dali::Toolkit::LinearLayout::Alignment::CENTER_HORIZONTAL:
561         {
562           childLeft = LayoutLength( padding.start ) + ( childSpace - childWidth ) / 2 + childMargin.start - childMargin.end;
563           break;
564         }
565       }
566       childLayout->Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
567       childTop += childHeight + childMargin.bottom + mCellPadding.height;
568     }
569   }
570 }
571
572 } // namespace Internal
573 } // namespace Toolkit
574 } // namespace Dali