[NUI] Fix GridLayout logic (#1814)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / GridLayout.cs
1 /* Copyright (c) 2019 Samsung Electronics Co., Ltd.
2 .*
3 .* Licensed under the Apache License, Version 2.0 (the "License");
4 .* you may not use this file except in compliance with the License.
5 .* You may obtain a copy of the License at
6 .*
7 .* http://www.apache.org/licenses/LICENSE-2.0
8 .*
9 .* Unless required by applicable law or agreed to in writing, software
10 .* distributed under the License is distributed on an "AS IS" BASIS,
11 .* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 .* See the License for the specific language governing permissions and
13 .* limitations under the License.
14 .*
15 .*/
16
17 using System;
18 using System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20 using Tizen.NUI.Binding;
21
22 namespace Tizen.NUI
23 {
24     /// <summary>
25     /// [Draft] This class implements a grid layout
26     /// </summary>
27     public partial class GridLayout : LayoutGroup
28     {
29         /// <summary>
30         /// ColumnProperty
31         /// </summary>
32         [EditorBrowsable(EditorBrowsableState.Never)]
33         public static readonly BindableProperty ColumnProperty = BindableProperty.CreateAttached("Column", typeof(int), typeof(GridLayout), CellUndefined, validateValue: (bindable, value) => (int)value >= 0, propertyChanged: OnChildPropertyChanged);
34
35         /// <summary>
36         /// ColumnSpanProperty
37         /// </summary>
38         [EditorBrowsable(EditorBrowsableState.Never)]
39         public static readonly BindableProperty ColumnSpanProperty = BindableProperty.CreateAttached("ColumnSpan", typeof(int), typeof(GridLayout), 1, validateValue: (bindable, value) => (int)value >= 1, propertyChanged: OnChildPropertyChanged);
40
41         /// <summary>
42         /// RowProperty
43         /// </summary>
44         [EditorBrowsable(EditorBrowsableState.Never)]
45         public static readonly BindableProperty RowProperty = BindableProperty.CreateAttached("Row", typeof(int), typeof(GridLayout), CellUndefined, validateValue: (bindable, value) => (int)value >= 0, propertyChanged: OnChildPropertyChanged);
46
47         /// <summary>
48         /// RowSpanProperty
49         /// </summary>
50         [EditorBrowsable(EditorBrowsableState.Never)]
51         public static readonly BindableProperty RowSpanProperty = BindableProperty.CreateAttached("RowSpan", typeof(int), typeof(GridLayout), 1, validateValue: (bindable, value) => (int)value >= 1, propertyChanged: OnChildPropertyChanged);
52
53         /// <summary>
54         /// HorizontalStretchProperty
55         /// </summary>
56         [EditorBrowsable(EditorBrowsableState.Never)]
57         public static readonly BindableProperty HorizontalStretchProperty = BindableProperty.CreateAttached("HorizontalStretch", typeof(StretchFlags), typeof(GridLayout), StretchFlags.Fill, propertyChanged: OnChildPropertyChanged);
58
59         /// <summary>
60         /// VerticalStretchProperty
61         /// </summary>
62         [EditorBrowsable(EditorBrowsableState.Never)]
63         public static readonly BindableProperty VerticalStretchProperty = BindableProperty.CreateAttached("VerticalStretch", typeof(StretchFlags), typeof(GridLayout), StretchFlags.Fill, propertyChanged: OnChildPropertyChanged);
64
65         /// <summary>
66         /// HorizontalAlignmentProperty
67         /// </summary>
68         [EditorBrowsable(EditorBrowsableState.Never)]
69         public static readonly BindableProperty HorizontalAlignmentProperty = BindableProperty.CreateAttached("HorizontalAlignment", typeof(Alignment), typeof(GridLayout), Alignment.Start, propertyChanged: OnChildPropertyChanged);
70
71         /// <summary>
72         /// VerticalAlignmentProperty
73         /// </summary>
74         [EditorBrowsable(EditorBrowsableState.Never)]
75         public static readonly BindableProperty VerticalAlignmentProperty = BindableProperty.CreateAttached("VerticalAlignment", typeof(Alignment), typeof(GridLayout), Alignment.Start, propertyChanged: OnChildPropertyChanged);
76
77         private const int CellUndefined = int.MinValue;
78         private Orientation gridOrientation = Orientation.Vertical;
79         private int columns = 1;
80         private int rows = 1;
81         private float columnSpacing = 0;
82         private float rowSpacing = 0;
83
84         /// <summary>
85         /// [Draft] Enumeration for the direction in which the content is laid out
86         /// </summary>
87         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
88         [EditorBrowsable(EditorBrowsableState.Never)]
89         public enum Orientation
90         {
91             /// <summary>
92             /// Horizontal (row)
93             /// </summary>
94             Horizontal,
95             /// <summary>
96             /// Vertical (column)
97             /// </summary>
98             Vertical
99         }
100
101         /// <summary>
102         /// Get the column index.
103         /// </summary>
104         [EditorBrowsable(EditorBrowsableState.Never)]
105         public static int GetColumn(View view)
106         {
107             return (int)view.GetValue(ColumnProperty);
108         }
109
110         /// <summary>
111         /// Get the column span.
112         /// </summary>
113         [EditorBrowsable(EditorBrowsableState.Never)]
114         public static int GetColumnSpan(View view)
115         {
116             return (int)view.GetValue(ColumnSpanProperty);
117         }
118
119         /// <summary>
120         /// Get the row index.
121         /// </summary>
122         [EditorBrowsable(EditorBrowsableState.Never)]
123         public static int GetRow(View view)
124         {
125             return (int)view.GetValue(RowProperty);
126         }
127
128         /// <summary>
129         /// Get the row span.
130         /// </summary>
131         [EditorBrowsable(EditorBrowsableState.Never)]
132         public static int GetRowSpan(View view)
133         {
134             return (int)view.GetValue(RowSpanProperty);
135         }
136
137         /// <summary>
138         /// Get the value how child is resized within its horizontal space. <see cref="StretchFlags.Fill"/> by default.
139         /// </summary>
140         [EditorBrowsable(EditorBrowsableState.Never)]
141         public static StretchFlags GetHorizontalStretch(View view)
142         {
143             return (StretchFlags)view.GetValue(HorizontalStretchProperty);
144         }
145
146         /// <summary>
147         /// Get the value how child is resized within its vertical space. <see cref="StretchFlags.Fill"/> by default.
148         /// </summary>
149         [EditorBrowsable(EditorBrowsableState.Never)]
150         public static StretchFlags GetVerticalStretch(View view)
151         {
152             return (StretchFlags)view.GetValue(VerticalStretchProperty);
153         }
154
155         /// <summary>
156         /// Get the horizontal alignment of this child.
157         /// </summary>
158         [EditorBrowsable(EditorBrowsableState.Never)]
159         public static Alignment GetHorizontalAlignment(View view)
160         {
161             return (Alignment)view.GetValue(HorizontalAlignmentProperty);
162         }
163
164         /// <summary>
165         /// Get the vertical alignment of this child.
166         /// </summary>
167         [EditorBrowsable(EditorBrowsableState.Never)]
168         public static Alignment GetVerticalAlignment(View view)
169         {
170             return (Alignment)view.GetValue(VerticalAlignmentProperty);
171         }
172
173         /// <summary>
174         /// Set the column index.
175         /// </summary>
176         [EditorBrowsable(EditorBrowsableState.Never)]
177         public static void SetColumn(View view, int value)
178         {
179             SetChildValue(view, ColumnProperty, value);
180         }
181
182         /// <summary>
183         /// Set the column span.
184         /// </summary>
185         [EditorBrowsable(EditorBrowsableState.Never)]
186         public static void SetColumnSpan(View view, int value)
187         {
188             SetChildValue(view, ColumnSpanProperty, value);
189         }
190
191         /// <summary>
192         /// Set the row index.
193         /// </summary>
194         [EditorBrowsable(EditorBrowsableState.Never)]
195         public static void SetRow(View view, int value)
196         {
197             SetChildValue(view, RowProperty, value);
198         }
199
200         /// <summary>
201         /// Set the row span.
202         /// </summary>
203         [EditorBrowsable(EditorBrowsableState.Never)]
204         public static void SetRowSpan(View view, int value)
205         {
206             SetChildValue(view, RowSpanProperty, value);
207         }
208
209         /// <summary>
210         /// Set the value how child is resized within its horizontal space. <see cref="StretchFlags.Fill"/> by default.
211         /// </summary>
212         [EditorBrowsable(EditorBrowsableState.Never)]
213         public static void SetHorizontalStretch(View view, StretchFlags value)
214         {
215             SetChildValue(view, HorizontalStretchProperty, value);
216         }
217
218         /// <summary>
219         /// Set the value how child is resized within its vertical space. <see cref="StretchFlags.Fill"/> by default.
220         /// </summary>
221         [EditorBrowsable(EditorBrowsableState.Never)]
222         public static void SetVerticalStretch(View view, StretchFlags value)
223         {
224             SetChildValue(view, VerticalStretchProperty, value);
225         }
226
227         /// <summary>
228         /// Set the horizontal alignment of this child inside the cells.
229         /// </summary>
230         [EditorBrowsable(EditorBrowsableState.Never)]
231         public static void SetHorizontalAlignment(View view, Alignment value)
232         {
233             SetChildValue(view, HorizontalAlignmentProperty, value);
234         }
235
236         /// <summary>
237         /// Set the vertical alignment of this child inside the cells.
238         /// </summary>
239         [EditorBrowsable(EditorBrowsableState.Never)]
240         public static void SetVerticalAlignment(View view, Alignment value)
241         {
242             SetChildValue(view, VerticalAlignmentProperty, value);
243         }
244
245         /// <summary>
246         /// [Draft] The Distance between Column
247         /// </summary>
248         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
249         [EditorBrowsable(EditorBrowsableState.Never)]
250         public float ColumnSpacing
251         {
252             get => columnSpacing;
253             set
254             {
255                 if (columnSpacing == value) return;
256                 if (columnSpacing < 0) columnSpacing = 0;
257                 columnSpacing = value;
258
259                 RequestLayout();
260             }
261         }
262
263         /// <summary>
264         /// [Draft] The Distance between Rows
265         /// </summary>
266         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
267         [EditorBrowsable(EditorBrowsableState.Never)]
268         public float RowSpacing
269         {
270             get => rowSpacing;
271             set
272             {
273                 if (rowSpacing == value) return;
274                 if (rowSpacing < 0) rowSpacing = 0;
275                 rowSpacing = value;
276
277                 RequestLayout();
278             }
279         }
280
281         /// <summary>
282         /// [Draft] Get/Set the orientation in the layout
283         /// </summary>
284         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
285         [EditorBrowsable(EditorBrowsableState.Never)]
286         public Orientation GridOrientation
287         {
288             get => gridOrientation;
289             set
290             {
291                 if (gridOrientation == value) return;
292                 gridOrientation = value;
293                 RequestLayout();
294             }
295         }
296
297         /// <summary>
298         /// [draft] GridLayout Constructor/>
299         /// </summary>
300         /// <returns> New Grid object.</returns>
301         /// <since_tizen> 6 </since_tizen>
302         public GridLayout()
303         {
304         }
305
306         /// <summary>
307         /// [Draft] Get/Set the number of columns in the GridLayout should have.
308         /// </summary>
309         /// <since_tizen> 6 </since_tizen>
310         public int Columns
311         {
312             get => columns;
313             set
314             {
315                 if (value == columns) return;
316
317                 if (value < 1) value = 1;
318                 columns = value;
319                 RequestLayout();
320             }
321         }
322
323         /// <summary>
324         /// [draft ]Get/Set the number of rows in the grid
325         /// </summary>
326         // This will be public opened after ACR done. (Before ACR, need to be hidden as Inhouse API)
327         [EditorBrowsable(EditorBrowsableState.Never)]
328         public int Rows
329         {
330             get => rows;
331             set
332             {
333                 if (value == rows) return;
334
335                 if (value < 1) value = 1;
336                 rows = value;
337                 RequestLayout();
338             }
339         }
340
341         /// <summary>
342         /// Measure the layout and its content to determine the measured width and the measured height.<br />
343         /// </summary>
344         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
345         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
346         /// <since_tizen> 6 </since_tizen>
347         protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
348         {
349             int widthSize;
350             int heightSize;
351             var widthMode = widthMeasureSpec.Mode;
352             var heightMode = heightMeasureSpec.Mode;
353
354             InitChildren(widthMeasureSpec, heightMeasureSpec);
355
356             if (widthMode == MeasureSpecification.ModeType.Exactly)
357                 widthSize = (int)widthMeasureSpec.Size.AsRoundedValue();
358             else
359                 widthSize = (int)(hLocations[maxColumnConut] - hLocations[0] - columnSpacing);
360
361             if (heightMode == MeasureSpecification.ModeType.Exactly)
362                 heightSize = (int)heightMeasureSpec.Size.AsRoundedValue();
363             else
364                 heightSize = (int)(vLocations[maxRowCount] - vLocations[0] - rowSpacing);
365
366             LayoutLength widthLength = new LayoutLength(widthSize + Padding.Start + Padding.End);
367             LayoutLength heightLenght = new LayoutLength(heightSize + Padding.Top + Padding.Bottom);
368
369             MeasuredSize widthMeasuredSize = ResolveSizeAndState(widthLength, widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
370             MeasuredSize heightMeasuredSize = ResolveSizeAndState(heightLenght, heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
371
372             SetMeasuredDimensions(widthMeasuredSize, heightMeasuredSize);
373         }
374
375         /// <summary>
376         /// Assign a size and position to each of its children.<br />
377         /// </summary>
378         /// <param name="changed">This is a new size or position for this layout.</param>
379         /// <param name="left">Left position, relative to parent.</param>
380         /// <param name="top"> Top position, relative to parent.</param>
381         /// <param name="right">Right position, relative to parent.</param>
382         /// <param name="bottom">Bottom position, relative to parent.</param>
383         /// <since_tizen> 6 </since_tizen>
384         protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
385         {
386             InitChildrenWithExpand(MeasuredWidth.Size - Padding.Start - Padding.End, MeasuredHeight.Size - Padding.Top - Padding.Bottom);
387
388             for (int i = 0; i < gridChildren.Length; i++)
389             {
390                 GridChild child = gridChildren[i];
391                 View view = child.LayoutItem?.Owner;
392
393                 if (view == null) continue;
394
395                 Alignment halign = GetHorizontalAlignment(view);
396                 Alignment valign = GetVerticalAlignment(view);
397
398                 int column = child.Column.Start;
399                 int row = child.Row.Start;
400                 int columnEnd = child.Column.End;
401                 int rowEnd = child.Row.End;
402                 float l = hLocations[column] + Padding.Start;
403                 float t = vLocations[row] + Padding.Top;
404                 float width = hLocations[columnEnd] - hLocations[column] - ColumnSpacing;
405                 float height = vLocations[rowEnd] - vLocations[row] - RowSpacing;
406
407                 if (!child.Column.Stretch.HasFlag(StretchFlags.Fill))
408                 {
409                     l += (width - child.LayoutItem.MeasuredWidth.Size.AsDecimal()) * halign.ToFloat();
410                     width = child.LayoutItem.MeasuredWidth.Size.AsDecimal();
411                 }
412
413                 if (!child.Row.Stretch.HasFlag(StretchFlags.Fill))
414                 {
415                     t += (height - child.LayoutItem.MeasuredHeight.Size.AsDecimal()) * valign.ToFloat();
416                     height = child.LayoutItem.MeasuredHeight.Size.AsDecimal();
417                 }
418
419                 child.LayoutItem.Layout(new LayoutLength(l), new LayoutLength(t), new LayoutLength(l + width), new LayoutLength(t + height));
420             }
421         }
422
423         /// <summary>
424         /// The value how child is resized within its space.
425         /// </summary>
426         [EditorBrowsable(EditorBrowsableState.Never)]
427         [Flags]
428         public enum StretchFlags
429         {
430             /// <summary>
431             /// Respect mesured size of the child.
432             /// </summary>
433             [EditorBrowsable(EditorBrowsableState.Never)]
434             None = 0,
435             /// <summary>
436             /// Resize to completely fill the space.
437             /// </summary>
438             [EditorBrowsable(EditorBrowsableState.Never)]
439             Fill = 1,
440             /// <summary>
441             /// Expand to share available space in GridLayout.
442             /// </summary>
443             [EditorBrowsable(EditorBrowsableState.Never)]
444             Expand = 2,
445             /// <summary>
446             /// Expand to share available space in GridLayout and fill the space.
447             /// </summary>
448             [EditorBrowsable(EditorBrowsableState.Never)]
449             ExpandAndFill = Fill + Expand,
450         }
451
452         /// <summary>
453         /// The alignment of the grid layout child.
454         /// </summary>
455         [EditorBrowsable(EditorBrowsableState.Never)]
456         public enum Alignment
457         {
458             /// <summary>
459             /// At the start of the container.
460             /// </summary>
461             [EditorBrowsable(EditorBrowsableState.Never)]
462             Start = 0,
463             /// <summary>
464             /// At the center of the container
465             /// </summary>
466             [EditorBrowsable(EditorBrowsableState.Never)]
467             Center = 1,
468             /// <summary>
469             /// At the end of the container.
470             /// </summary>
471             [EditorBrowsable(EditorBrowsableState.Never)]
472             End = 2,
473         }
474     }
475
476     // Extension Method of GridLayout.Alignment.
477     internal static class AlignmentExtension
478     {
479         public static float ToFloat(this GridLayout.Alignment align)
480         {
481             return 0.5f * (float)align;
482         }
483     }
484 }