xref: /haiku/src/tests/kits/interface/layout/widget_layout_test/GroupView.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
22 	LayoutInfo()
23 		: min(0),
24 		  max(B_SIZE_UNLIMITED),
25 		  preferred(0)
26 	{
27 	}
28 
29 	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 
39 	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 
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 
69 GroupView::~GroupView()
70 {
71 	delete fColumnInfos;
72 	delete fRowInfos;
73 }
74 
75 
76 void
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
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
100 GroupView::MinSize()
101 {
102 	_ValidateMinMax();
103 	return _AddInsetsAndSpacing(BSize(fMinWidth - 1, fMinHeight - 1));
104 }
105 
106 
107 BSize
108 GroupView::MaxSize()
109 {
110 	_ValidateMinMax();
111 	return _AddInsetsAndSpacing(BSize(fMaxWidth - 1, fMaxHeight - 1));
112 }
113 
114 
115 BSize
116 GroupView::PreferredSize()
117 {
118 	_ValidateMinMax();
119 	return _AddInsetsAndSpacing(BSize(fPreferredWidth - 1,
120 		fPreferredHeight - 1));
121 }
122 
123 
124 BAlignment
125 GroupView::Alignment()
126 {
127 	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
128 }
129 
130 
131 void
132 GroupView::InvalidateLayout()
133 {
134 	fMinMaxValid = false;
135 	View::InvalidateLayout();
136 }
137 
138 
139 void
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
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
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
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
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
337 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
351 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*
365 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 
377 Glue::Glue()
378 	: View()
379 {
380 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
381 }
382 
383 
384 // #pragma mark -  Strut
385 
386 
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
397 Strut::MinSize()
398 {
399 	return BLayoutUtils::ComposeSize(fSize, BSize(-1, -1));
400 }
401 
402 
403 BSize
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 
414 HStrut::HStrut(float width)
415 	: Strut(width, -1)
416 {
417 }
418 
419 
420 // #pragma mark - VStrut
421 
422 
423 VStrut::VStrut(float height)
424 	: Strut(-1, height)
425 {
426 }
427