1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2024 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #import "GridLayout.h"
30
31
32
33 @implementation GridLayout
34
35 @synthesize container = _container;
36
37 - (GridLayout*)init {
38 self = [super init];
39 _columnspacing = 0;
40 _rowspacing = 0;
41 _children = cxArrayListCreateSimple(sizeof(GridElm), 32);
42 _preferredSize.width = -1;
43 _preferredSize.height = -1;
44
45 return self;
46 }
47
48 /*
49 - (void) layout {
50 [super layout];
51
52 NSRect r1 = _test.frame;
53 NSSize s1 = _test.intrinsicContentSize;
54 NSEdgeInsets e1 = _test.alignmentRectInsets;
55
56 }
57 */
58
59 - (BOOL)isFlipped {
60 return YES;
61 }
62
63 - (void) layout {
64 int ncols = _cols+1;
65 int nrows = _rows+1;
66
67 GridDef *cols = calloc(ncols, sizeof(GridDef));
68 GridDef *rows = calloc(nrows, sizeof(GridDef));
69
70 //NSRect viewFrame = self.frame;
71 NSRect viewFrame = self.bounds;
72
73 int colspacing = _columnspacing;
74 int rowspacing = _rowspacing;
75
76 int span_max = 1;
77 for(int r=0;r<2;r++) {
78 CxIterator i = cxListIterator(_children);
79 cx_foreach(GridElm *, elm, i) {
80 int x = elm->x;
81 int y = elm->y;
82 GridDef *col = &cols[x];
83 GridDef *row = &rows[y];
84
85 NSSize size = elm->view.intrinsicContentSize;
86 NSSize size2 = elm->view.fittingSize;
87 if(size.width == NSViewNoIntrinsicMetric) {
88 size.width = size2.width;
89 }
90 if(size.height == NSViewNoIntrinsicMetric) {
91 size.height = size2.height;
92 }
93 if(size.width != NSViewNoIntrinsicMetric) {
94 CGFloat width = size.width + elm->margin.left + elm->margin.right;
95 if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) {
96 cols[elm->x].preferred_size = width;
97 }
98 elm->preferred_width = width;
99 }
100 if(size.height != NSViewNoIntrinsicMetric) {
101 CGFloat height = size.height + elm->margin.top + elm->margin.bottom;
102 if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) {
103 rows[elm->y].preferred_size = height;
104 }
105 elm->preferred_height = height;
106 }
107
108
109 if(elm->rowspan > span_max || elm->colspan > span_max) {
110 continue;
111 }
112
113 int end_col = x+elm->colspan;
114 if(end_col > ncols) {
115 end_col = ncols;
116 }
117 int end_row = y+elm->rowspan;
118 if(end_row > nrows) {
119 end_row = nrows;
120 }
121
122 // are all columns in the span > preferred_width?
123 if(elm->colspan > 1) {
124 int span_width = 0;
125 GridDef *last_col = col;
126 for(int c=x;c<end_col;c++) {
127 span_width += cols[c].size;
128 last_col = &cols[c];
129 }
130 if(span_width < elm->preferred_width) {
131 last_col->size += elm->preferred_width - span_width;
132 }
133 }
134 // are all rows in the span > preferred_height?
135 if(elm->rowspan > 1) {
136 int span_height = 0;
137 GridDef *last_row = row;
138 for(int c=x;c<end_row;c++) {
139 span_height += rows[c].size;
140 last_row = &rows[c];
141 }
142 if(span_height < elm->preferred_height) {
143 last_row->size += elm->preferred_height - span_height;
144 }
145 }
146
147 if(elm->hexpand) {
148 if(elm->colspan > 1) {
149 // check if any column in the span is expanding
150 // if not, make the last column expanding
151 GridDef *last_col = col;
152 for(int c=x;c<end_col;c++) {
153 last_col = &cols[c];
154 if(last_col->expand) {
155 break;
156 }
157 }
158 last_col->expand = TRUE;
159 } else {
160 col->expand = TRUE;
161 }
162 }
163 if(elm->vexpand) {
164 if(elm->rowspan > 1) {
165 // same as colspan
166 GridDef *last_row = row;
167 for(int c=x;c<nrows;c++) {
168 last_row = &rows[c];
169 if(last_row->expand) {
170 break;
171 }
172 }
173 last_row->expand = TRUE;
174 } else {
175 row->expand = TRUE;
176 }
177 }
178 }
179 span_max = 50000; // not sure if this is unreasonable low or high
180 }
181
182
183 int col_ext = 0;
184 int row_ext = 0;
185
186 int preferred_width = 0;
187 int preferred_height = 0;
188 for(int j=0;j<ncols;j++) {
189 preferred_width += cols[j].preferred_size;
190 if(cols[j].expand) {
191 col_ext++;
192 }
193 }
194 for(int j=0;j<nrows;j++) {
195 preferred_height += rows[j].preferred_size;
196 if(rows[j].expand) {
197 row_ext++;
198 }
199 }
200 if(ncols > 0) {
201 preferred_width += (ncols-1) * colspacing;
202 }
203 if(nrows > 0) {
204 preferred_height += (nrows-1) * rowspacing;
205 }
206
207 _preferredSize.width = preferred_width;
208 _preferredSize.height = preferred_height;
209
210
211 int hremaining = viewFrame.size.width - preferred_width;
212 int vremaining = viewFrame.size.height - preferred_height;
213 int hext = hremaining/col_ext;
214 int vext = vremaining/row_ext;
215
216 for(int j=0;j<ncols;j++) {
217 GridDef *col = &cols[j];
218 if(col->expand) {
219 col->size = col->preferred_size + hext;
220 } else {
221 col->size = col->preferred_size;
222 }
223 }
224 for(int j=0;j<nrows;j++) {
225 GridDef *row = &rows[j];
226 if(row->expand) {
227 row->size = row->preferred_size + vext;
228 } else {
229 row->size = row->preferred_size;
230 }
231 }
232
233 int pos = 0;
234 for(int j=0;j<ncols;j++) {
235 cols[j].pos = pos;
236 pos += cols[j].size + colspacing;
237 }
238 pos = 0;
239 for(int j=0;j<nrows;j++) {
240 rows[j].pos = pos;
241 pos += rows[j].size + rowspacing;
242 }
243
244 CxIterator i = cxListIterator(_children);
245 cx_foreach(GridElm *, elm, i) {
246 //NSSize size = elm->view.intrinsicContentSize;
247 GridDef *col = &cols[elm->x];
248 GridDef *row = &rows[elm->y];
249
250 NSRect frame;
251 if(elm->hfill) {
252 if(elm->colspan > 1) {
253 int cwidth = 0;
254 int end_col = elm->x + elm->colspan;
255 if(end_col > ncols) {
256 end_col = ncols;
257 }
258 int real_span = 0;
259 for(int c=elm->x;c<end_col;c++) {
260 cwidth += cols[c].size;
261 real_span++;
262 }
263 if(real_span > 0) {
264 cwidth += (real_span-1) * colspacing;
265 }
266 frame.size.width = cwidth;
267 } else {
268 frame.size.width = col->size;
269 }
270 } else {
271 frame.size.width = elm->preferred_width;
272 }
273 frame.size.width -= elm->margin.left + elm->margin.right;
274 if(elm->vfill) {
275 if(elm->rowspan > 1) {
276 int rheight = 0;
277 int end_row = elm->y + elm->rowspan;
278 if(end_row > nrows) {
279 end_row = nrows;
280 }
281 int real_span = 0;
282 for(int r=elm->y;r<end_row;r++) {
283 rheight += rows[r].size;
284 real_span++;
285 }
286 if(real_span > 0) {
287 rheight += (real_span-1) * rowspacing;
288 }
289 frame.size.height = rheight;
290 }
291 frame.size.height = row->size;
292 } else {
293 frame.size.height = elm->preferred_height;
294 }
295 frame.size.height -= elm->margin.top + elm->margin.bottom;
296
297 frame.origin.x = col->pos + elm->margin.left;
298 frame.origin.y = row->pos + elm->margin.top;
299 NSRect viewFrame = [elm->view frameForAlignmentRect:frame];
300 elm->view.frame = viewFrame;
301 }
302
303 free(cols);
304 free(rows);
305 }
306
307
308 - (NSSize)intrinsicContentSize {
309 if(_preferredSize.width == -1) {
310 [self layout];
311 }
312 return self.preferredSize;
313 }
314
315 - (void) addView:(NSView*)view layout:(UiLayout*)layout {
316 _preferredSize.width = -1;
317 _preferredSize.height = -1;
318
319 if(self.container != nil && self.container->newline) {
320 _y++;
321 _x = 0;
322 self.container->newline = FALSE;
323 }
324
325 GridElm elm;
326 elm.x = _x;
327 elm.y = _y;
328 elm.margin = NSEdgeInsetsMake(layout->margin_top, layout->margin_left, layout->margin_bottom, layout->margin_right);
329 elm.colspan = layout->colspan;
330 elm.rowspan = layout->rowspan;
331 if(layout->fill) {
332 elm.hfill = TRUE;
333 elm.vfill = TRUE;
334 elm.hexpand = TRUE;
335 elm.vexpand = TRUE;
336 } else {
337 elm.hfill = layout->hfill;
338 elm.vfill = layout->vfill;
339 elm.hexpand = layout->hexpand;
340 elm.vexpand = layout->vexpand;
341 }
342 elm.view = view;
343 cxListAdd(_children, &elm);
344
345 [self addSubview:view];
346 self.needsLayout = YES;
347
348 if(_x > _cols) {
349 _cols = _x;
350 }
351 if(_y > _rows) {
352 _rows = _y;
353 }
354 _x++;
355 }
356
357 - (void) dealloc {
358 cxListFree(_children);
359 }
360
361 @end
362