1 /*
2 * Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6 #include "GroupView.h"
7
8 #include <stdio.h>
9
10 #include <LayoutUtils.h>
11
12
13 // #pragma mark - GroupView
14
15
16 struct GroupView::LayoutInfo {
17 int32 min;
18 int32 max;
19 int32 preferred;
20 int32 size;
21
LayoutInfoGroupView::LayoutInfo22 LayoutInfo()
23 : min(0),
24 max(B_SIZE_UNLIMITED),
25 preferred(0)
26 {
27 }
28
AddConstraintsGroupView::LayoutInfo29 void AddConstraints(float addMin, float addMax, float addPreferred)
30 {
31 if (addMin >= min)
32 min = (int32)addMin + 1;
33 if (addMax <= max)
34 max = (int32)addMax + 1;
35 if (addPreferred >= preferred)
36 preferred = (int32)addPreferred + 1;
37 }
38
NormalizeGroupView::LayoutInfo39 void Normalize()
40 {
41 if (max < min)
42 max = min;
43 if (preferred < min)
44 preferred = min;
45 if (preferred > max)
46 preferred = max;
47 }
48 };
49
50
GroupView(enum orientation orientation,int32 lineCount)51 GroupView::GroupView(enum orientation orientation, int32 lineCount)
52 : View(BRect(0, 0, 0, 0)),
53 fOrientation(orientation),
54 fLineCount(lineCount),
55 fColumnSpacing(0),
56 fRowSpacing(0),
57 fInsets(0, 0, 0, 0),
58 fMinMaxValid(false),
59 fColumnInfos(NULL),
60 fRowInfos(NULL)
61 {
62 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
63
64 if (fLineCount < 1)
65 fLineCount = 1;
66 }
67
68
~GroupView()69 GroupView::~GroupView()
70 {
71 delete fColumnInfos;
72 delete fRowInfos;
73 }
74
75
76 void
SetSpacing(float horizontal,float vertical)77 GroupView::SetSpacing(float horizontal, float vertical)
78 {
79 if (horizontal != fColumnSpacing || vertical != fRowSpacing) {
80 fColumnSpacing = horizontal;
81 fRowSpacing = vertical;
82
83 InvalidateLayout();
84 }
85 }
86
87
88 void
SetInsets(float left,float top,float right,float bottom)89 GroupView::SetInsets(float left, float top, float right, float bottom)
90 {
91 BRect newInsets(left, top, right, bottom);
92 if (newInsets != fInsets) {
93 fInsets = newInsets;
94 InvalidateLayout();
95 }
96 }
97
98
99 BSize
MinSize()100 GroupView::MinSize()
101 {
102 _ValidateMinMax();
103 return _AddInsetsAndSpacing(BSize(fMinWidth - 1, fMinHeight - 1));
104 }
105
106
107 BSize
MaxSize()108 GroupView::MaxSize()
109 {
110 _ValidateMinMax();
111 return _AddInsetsAndSpacing(BSize(fMaxWidth - 1, fMaxHeight - 1));
112 }
113
114
115 BSize
PreferredSize()116 GroupView::PreferredSize()
117 {
118 _ValidateMinMax();
119 return _AddInsetsAndSpacing(BSize(fPreferredWidth - 1,
120 fPreferredHeight - 1));
121 }
122
123
124 BAlignment
Alignment()125 GroupView::Alignment()
126 {
127 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
128 }
129
130
131 void
InvalidateLayout()132 GroupView::InvalidateLayout()
133 {
134 fMinMaxValid = false;
135 View::InvalidateLayout();
136 }
137
138
139 void
Layout()140 GroupView::Layout()
141 {
142 //printf("%p->GroupView::Layout()\n", this);
143 _ValidateMinMax();
144 // actually a little late already
145
146 BSize size = _SubtractInsetsAndSpacing(Size());
147 _LayoutLine(size.IntegerWidth() + 1, fColumnInfos, fColumnCount);
148 _LayoutLine(size.IntegerHeight() + 1, fRowInfos, fRowCount);
149
150 // layout children
151 BPoint location = fInsets.LeftTop();
152 for (int32 column = 0; column < fColumnCount; column++) {
153 LayoutInfo& columnInfo = fColumnInfos[column];
154 location.y = fInsets.top;
155 for (int32 row = 0; row < fRowCount; row++) {
156 View* child = _ChildAt(column, row);
157 if (!child)
158 continue;
159
160 // get the grid cell frame
161 BRect cellFrame(location,
162 BSize(columnInfo.size - 1, fRowInfos[row].size - 1));
163
164 // align the child frame in the grid cell
165 BRect childFrame = BLayoutUtils::AlignInFrame(cellFrame,
166 child->MaxSize(), child->Alignment());
167
168 // layout child
169 child->SetFrame(childFrame);
170
171 location.y += fRowInfos[row].size + fRowSpacing;
172 }
173
174 location.x += columnInfo.size + fColumnSpacing;
175 }
176 //printf("%p->GroupView::Layout() done\n", this);
177 }
178
179
180 void
_ValidateMinMax()181 GroupView::_ValidateMinMax()
182 {
183 if (fMinMaxValid)
184 return;
185
186 //printf("%p->GroupView::_ValidateMinMax()\n", this);
187 delete fColumnInfos;
188 delete fRowInfos;
189
190 fColumnCount = _ColumnCount();
191 fRowCount = _RowCount();
192
193 fColumnInfos = new LayoutInfo[fColumnCount];
194 fRowInfos = new LayoutInfo[fRowCount];
195
196 // collect the children's min/max constraints
197 for (int32 column = 0; column < fColumnCount; column++) {
198 for (int32 row = 0; row < fRowCount; row++) {
199 View* child = _ChildAt(column, row);
200 if (!child)
201 continue;
202
203 BSize min = child->MinSize();
204 BSize max = child->MaxSize();
205 BSize preferred = child->PreferredSize();
206
207 // apply constraints to column/row info
208 fColumnInfos[column].AddConstraints(min.width, max.width,
209 preferred.width);
210 fRowInfos[row].AddConstraints(min.height, max.height,
211 preferred.height);
212 }
213 }
214
215 // normalize the column/row constraints and compute sum min/max
216 fMinWidth = 0;
217 fMinHeight = 0;
218 fMaxWidth = 0;
219 fMaxHeight = 0;
220 fPreferredWidth = 0;
221 fPreferredHeight = 0;
222
223 for (int32 column = 0; column < fColumnCount; column++) {
224 fColumnInfos[column].Normalize();
225 fMinWidth = BLayoutUtils::AddSizesInt32(fMinWidth,
226 fColumnInfos[column].min);
227 fMaxWidth = BLayoutUtils::AddSizesInt32(fMaxWidth,
228 fColumnInfos[column].max);
229 fPreferredWidth = BLayoutUtils::AddSizesInt32(fPreferredWidth,
230 fColumnInfos[column].preferred);
231 //printf(" column %ld: min: %ld, max: %ld, preferred: %ld\n", column, fColumnInfos[column].min, fColumnInfos[column].max, fColumnInfos[column].preferred);
232 }
233
234 for (int32 row = 0; row < fRowCount; row++) {
235 fRowInfos[row].Normalize();
236 fMinHeight = BLayoutUtils::AddSizesInt32(fMinHeight,
237 fRowInfos[row].min);
238 fMaxHeight = BLayoutUtils::AddSizesInt32(fMaxHeight,
239 fRowInfos[row].max);
240 fPreferredHeight = BLayoutUtils::AddSizesInt32(fPreferredHeight,
241 fRowInfos[row].preferred);
242 //printf(" row %ld: min: %ld, max: %ld, preferred: %ld\n", row, fRowInfos[row].min, fRowInfos[row].max, fRowInfos[row].preferred);
243 }
244
245 fMinMaxValid = true;
246 //printf("%p->GroupView::_ValidateMinMax() done\n", this);
247 }
248
249
250 void
_LayoutLine(int32 size,LayoutInfo * infos,int32 infoCount)251 GroupView::_LayoutLine(int32 size, LayoutInfo* infos, int32 infoCount)
252 {
253 BList infosToLayout;
254 for (int32 i = 0; i < infoCount; i++) {
255 infos[i].size = 0;
256 infosToLayout.AddItem(infos + i);
257 }
258
259 // Distribute the available space over the infos. Each iteration we
260 // try to distribute the remaining space evenly. We respect min and
261 // max constraints, though, add up the space we failed to assign
262 // due to the constraints, and use the sum as remaining space for the
263 // next iteration (can be negative). Then we ignore infos that can't
264 // shrink or grow anymore (depending on what would be needed).
265 while (!infosToLayout.IsEmpty()) {
266 BList canShrinkInfos;
267 BList canGrowInfos;
268
269 int32 sizeDiff = 0;
270 int32 infosToLayoutCount = infosToLayout.CountItems();
271 for (int32 i = 0; i < infosToLayoutCount; i++) {
272 LayoutInfo* info = (LayoutInfo*)infosToLayout.ItemAt(i);
273 info->size += (i + 1) * size / infosToLayoutCount
274 - i * size / infosToLayoutCount;
275 if (info->size < info->min) {
276 sizeDiff -= info->min - info->size;
277 info->size = info->min;
278 } else if (info->size > info->max) {
279 sizeDiff += info->size - info->max;
280 info->size = info->max;
281 }
282
283 if (info->size > info->min)
284 canShrinkInfos.AddItem(info);
285 if (info->size < info->max)
286 canGrowInfos.AddItem(info);
287 }
288
289 size = sizeDiff;
290 if (size == 0)
291 break;
292
293 if (size > 0)
294 infosToLayout = canGrowInfos;
295 else
296 infosToLayout = canShrinkInfos;
297 }
298
299 // If unassigned space is remaining, that means, that the group has
300 // been resized beyond its max size. We distribute the excess space
301 // evenly.
302 if (size > 0) {
303 for (int32 i = 0; i < infoCount; i++) {
304 infos[i].size += (i + 1) * size / infoCount
305 - i * size / infoCount;
306 }
307 }
308 }
309
310
311 BSize
_AddInsetsAndSpacing(BSize size)312 GroupView::_AddInsetsAndSpacing(BSize size)
313 {
314 size.width = BLayoutUtils::AddDistances(size.width,
315 fInsets.left + fInsets.right - 1
316 + (fColumnCount - 1) * fColumnSpacing);
317 size.height = BLayoutUtils::AddDistances(size.height,
318 fInsets.top + fInsets.bottom - 1
319 + (fRowCount - 1) * fRowSpacing);
320 return size;
321 }
322
323
324 BSize
_SubtractInsetsAndSpacing(BSize size)325 GroupView::_SubtractInsetsAndSpacing(BSize size)
326 {
327 size.width = BLayoutUtils::SubtractDistances(size.width,
328 fInsets.left + fInsets.right - 1
329 + (fColumnCount - 1) * fColumnSpacing);
330 size.height = BLayoutUtils::SubtractDistances(size.height,
331 fInsets.top + fInsets.bottom - 1
332 + (fRowCount - 1) * fRowSpacing);
333 return size;
334 }
335
336 int32
_RowCount() const337 GroupView::_RowCount() const
338 {
339 int32 childCount = CountChildren();
340 int32 count;
341 if (fOrientation == B_HORIZONTAL)
342 count = min_c(fLineCount, childCount);
343 else
344 count = (childCount + fLineCount - 1) / fLineCount;
345
346 return max_c(count, 1);
347 }
348
349
350 int32
_ColumnCount() const351 GroupView::_ColumnCount() const
352 {
353 int32 childCount = CountChildren();
354 int32 count;
355 if (fOrientation == B_HORIZONTAL)
356 count = (childCount + fLineCount - 1) / fLineCount;
357 else
358 count = min_c(fLineCount, childCount);
359
360 return max_c(count, 1);
361 }
362
363
364 View*
_ChildAt(int32 column,int32 row) const365 GroupView::_ChildAt(int32 column, int32 row) const
366 {
367 if (fOrientation == B_HORIZONTAL)
368 return ChildAt(column * fLineCount + row);
369 else
370 return ChildAt(row * fLineCount + column);
371 }
372
373
374 // #pragma mark - Glue
375
376
Glue()377 Glue::Glue()
378 : View()
379 {
380 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
381 }
382
383
384 // #pragma mark - Strut
385
386
Strut(float pixelWidth,float pixelHeight)387 Strut::Strut(float pixelWidth, float pixelHeight)
388 : View(),
389 fSize(pixelWidth >= 0 ? pixelWidth - 1 : B_SIZE_UNSET,
390 pixelHeight >= 0 ? pixelHeight - 1 : B_SIZE_UNSET)
391 {
392 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
393 }
394
395
396 BSize
MinSize()397 Strut::MinSize()
398 {
399 return BLayoutUtils::ComposeSize(fSize, BSize(-1, -1));
400 }
401
402
403 BSize
MaxSize()404 Strut::MaxSize()
405 {
406 return BLayoutUtils::ComposeSize(fSize,
407 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
408 }
409
410
411 // #pragma mark - HStrut
412
413
HStrut(float width)414 HStrut::HStrut(float width)
415 : Strut(width, -1)
416 {
417 }
418
419
420 // #pragma mark - VStrut
421
422
VStrut(float height)423 VStrut::VStrut(float height)
424 : Strut(-1, height)
425 {
426 }
427