1/*
2 * Copyright 2006-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2014 Haiku, Inc. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 *
7 * Authors:
8 *		John Scipione, jscipione@gmail.com
9 *		Ingo Weinhold, ingo_weinhold@gmx.de
10 */
11
12#include <LayoutUtils.h>
13
14#include <algorithm>
15#include <typeinfo>
16
17#include <Layout.h>
18#include <View.h>
19
20#include "ViewLayoutItem.h"
21
22
23// // AddSizesFloat
24// float
25// BLayoutUtils::AddSizesFloat(float a, float b)
26// {
27// 	float sum = a + b + 1;
28// 	if (sum >= B_SIZE_UNLIMITED)
29// 		return B_SIZE_UNLIMITED;
30//
31// 	return sum;
32// }
33//
34// // AddSizesFloat
35// float
36// BLayoutUtils::AddSizesFloat(float a, float b, float c)
37// {
38// 	return AddSizesFloat(AddSizesFloat(a, b), c);
39// }
40
41
42// AddSizesInt32
43int32
44BLayoutUtils::AddSizesInt32(int32 a, int32 b)
45{
46	if (a >= B_SIZE_UNLIMITED - b)
47		return B_SIZE_UNLIMITED;
48	return a + b;
49}
50
51
52// AddSizesInt32
53int32
54BLayoutUtils::AddSizesInt32(int32 a, int32 b, int32 c)
55{
56	return AddSizesInt32(AddSizesInt32(a, b), c);
57}
58
59
60// AddDistances
61float
62BLayoutUtils::AddDistances(float a, float b)
63{
64	float sum = a + b + 1;
65	if (sum >= B_SIZE_UNLIMITED)
66		return B_SIZE_UNLIMITED;
67
68	return sum;
69}
70
71
72// AddDistances
73float
74BLayoutUtils::AddDistances(float a, float b, float c)
75{
76	return AddDistances(AddDistances(a, b), c);
77}
78
79
80// // SubtractSizesFloat
81// float
82// BLayoutUtils::SubtractSizesFloat(float a, float b)
83// {
84// 	if (a < b)
85// 		return -1;
86// 	return a - b - 1;
87// }
88
89
90// SubtractSizesInt32
91int32
92BLayoutUtils::SubtractSizesInt32(int32 a, int32 b)
93{
94	if (a < b)
95		return 0;
96	return a - b;
97}
98
99
100// SubtractDistances
101float
102BLayoutUtils::SubtractDistances(float a, float b)
103{
104	if (a < b)
105		return -1;
106	return a - b - 1;
107}
108
109
110// FixSizeConstraints
111void
112BLayoutUtils::FixSizeConstraints(float& min, float& max, float& preferred)
113{
114	if (max < min)
115		max = min;
116	if (preferred < min)
117		preferred = min;
118	else if (preferred > max)
119		preferred = max;
120}
121
122
123// FixSizeConstraints
124void
125BLayoutUtils::FixSizeConstraints(BSize& min, BSize& max, BSize& preferred)
126{
127	FixSizeConstraints(min.width, max.width, preferred.width);
128	FixSizeConstraints(min.height, max.height, preferred.height);
129}
130
131
132// ComposeSize
133BSize
134BLayoutUtils::ComposeSize(BSize size, BSize layoutSize)
135{
136	if (!size.IsWidthSet())
137		size.width = layoutSize.width;
138	if (!size.IsHeightSet())
139		size.height = layoutSize.height;
140
141	return size;
142}
143
144
145// ComposeAlignment
146BAlignment
147BLayoutUtils::ComposeAlignment(BAlignment alignment, BAlignment layoutAlignment)
148{
149	if (!alignment.IsHorizontalSet())
150		alignment.horizontal = layoutAlignment.horizontal;
151	if (!alignment.IsVerticalSet())
152		alignment.vertical = layoutAlignment.vertical;
153
154	return alignment;
155}
156
157
158// AlignInFrame
159// This method restricts the dimensions of the resulting rectangle according
160// to the available size specified by maxSize.
161BRect
162BLayoutUtils::AlignInFrame(BRect frame, BSize maxSize, BAlignment alignment)
163{
164	// align according to the given alignment
165	if (maxSize.width < frame.Width()
166		&& alignment.horizontal != B_ALIGN_USE_FULL_WIDTH) {
167		frame.left += (int)((frame.Width() - maxSize.width)
168			* alignment.RelativeHorizontal());
169		frame.right = frame.left + maxSize.width;
170	}
171	if (maxSize.height < frame.Height()
172		&& alignment.vertical != B_ALIGN_USE_FULL_HEIGHT) {
173		frame.top += (int)((frame.Height() - maxSize.height)
174			* alignment.RelativeVertical());
175		frame.bottom = frame.top + maxSize.height;
176	}
177
178	return frame;
179}
180
181
182// AlignInFrame
183void
184BLayoutUtils::AlignInFrame(BView* view, BRect frame)
185{
186	BSize maxSize = view->MaxSize();
187	BAlignment alignment = view->LayoutAlignment();
188	if (view->HasHeightForWidth()) {
189		// The view has height for width, so we do the horizontal alignment
190		// ourselves and restrict the height max constraint respectively.
191		if (maxSize.width < frame.Width()
192			&& alignment.horizontal != B_ALIGN_USE_FULL_WIDTH) {
193			frame.OffsetBy(floorf((frame.Width() - maxSize.width)
194				* alignment.RelativeHorizontal()), 0);
195			frame.right = frame.left + maxSize.width;
196		}
197		alignment.horizontal = B_ALIGN_USE_FULL_WIDTH;
198		float minHeight;
199		float maxHeight;
200		float preferredHeight;
201		view->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight,
202			&preferredHeight);
203		frame.bottom = frame.top + std::max(frame.Height(), minHeight);
204		maxSize.height = minHeight;
205	}
206	frame = AlignInFrame(frame, maxSize, alignment);
207	view->MoveTo(frame.LeftTop());
208	view->ResizeTo(frame.Size());
209}
210
211
212// AlignOnRect
213// This method, unlike AlignInFrame(), provides the possibility to return
214// a rectangle with dimensions greater than the available size.
215BRect
216BLayoutUtils::AlignOnRect(BRect rect, BSize size, BAlignment alignment)
217{
218	rect.left += (int)((rect.Width() - size.width)
219		* alignment.RelativeHorizontal());
220	rect.top += (int)(((rect.Height() - size.height))
221		* alignment.RelativeVertical());
222	rect.right = rect.left + size.width;
223	rect.bottom = rect.top + size.height;
224
225	return rect;
226}
227
228
229/*!	Offsets a rectangle's location so that it lies fully in a given rectangular
230	frame.
231
232	If the rectangle is too wide/high to fully fit in the frame, its left/top
233	edge is offset to 0. The rect's size always remains unchanged.
234
235	\param rect The rectangle to be moved.
236	\param frameSize The size of the frame the rect shall be moved into. The
237		frame's left-top is (0, 0).
238	\return The modified rect.
239*/
240/*static*/ BRect
241BLayoutUtils::MoveIntoFrame(BRect rect, BSize frameSize)
242{
243	BPoint leftTop(rect.LeftTop());
244
245	// enforce horizontal limits; favor left edge
246	if (rect.right > frameSize.width)
247		leftTop.x -= rect.right - frameSize.width;
248	if (leftTop.x < 0)
249		leftTop.x = 0;
250
251	// enforce vertical limits; favor top edge
252	if (rect.bottom > frameSize.height)
253		leftTop.y -= rect.bottom - frameSize.height;
254	if (leftTop.y < 0)
255		leftTop.y = 0;
256
257	return rect.OffsetToSelf(leftTop);
258}
259
260
261/*static*/ BString
262BLayoutUtils::GetLayoutTreeDump(BView* view)
263{
264	BString result;
265	_GetLayoutTreeDump(view, 0, result);
266	return result;
267}
268
269
270/*static*/ BString
271BLayoutUtils::GetLayoutTreeDump(BLayoutItem* item)
272{
273	BString result;
274	_GetLayoutTreeDump(item, 0, false, result);
275	return result;
276}
277
278
279/*static*/ void
280BLayoutUtils::_GetLayoutTreeDump(BView* view, int level, BString& _output)
281{
282	BString indent;
283	indent.SetTo(' ', level * 2);
284
285	if (view == NULL) {
286		_output << indent << "<null view>\n";
287		return;
288	}
289
290	BRect frame = view->Frame();
291	BSize min = view->MinSize();
292	BSize max = view->MinSize();
293	BSize preferred = view->PreferredSize();
294	_output << BString().SetToFormat(
295		"%sview %p (%s):\n"
296		"%s  frame: (%f, %f, %f, %f)\n"
297		"%s  min:   (%f, %f)\n"
298		"%s  max:   (%f, %f)\n"
299		"%s  pref:  (%f, %f)\n",
300		indent.String(), view, typeid(*view).name(),
301		indent.String(), frame.left, frame.top, frame.right, frame.bottom,
302		indent.String(), min.width, min.height,
303		indent.String(), max.width, max.height,
304		indent.String(), preferred.width, preferred.height);
305
306	if (BLayout* layout = view->GetLayout()) {
307		_GetLayoutTreeDump(layout, level, true, _output);
308		return;
309	}
310
311	int32 count = view->CountChildren();
312	for (int32 i = 0; i < count; i++) {
313		_output << indent << "  ---\n";
314		_GetLayoutTreeDump(view->ChildAt(i), level + 1, _output);
315	}
316}
317
318
319/*static*/ void
320BLayoutUtils::_GetLayoutTreeDump(BLayoutItem* item, int level,
321	bool isViewLayout, BString& _output)
322{
323	if (BViewLayoutItem* viewItem = dynamic_cast<BViewLayoutItem*>(item)) {
324		_GetLayoutTreeDump(viewItem->View(), level, _output);
325		return;
326	}
327
328	BString indent;
329	indent.SetTo(' ', level * 2);
330
331	if (item == NULL) {
332		_output << indent << "<null item>\n";
333		return;
334	}
335
336	BLayout* layout = dynamic_cast<BLayout*>(item);
337	BRect frame = item->Frame();
338	BSize min = item->MinSize();
339	BSize max = item->MinSize();
340	BSize preferred = item->PreferredSize();
341	if (isViewLayout) {
342		_output << indent << BString().SetToFormat("  [layout %p (%s)]\n",
343			layout, typeid(*layout).name());
344	} else {
345		_output << indent << BString().SetToFormat("item %p (%s):\n",
346			item, typeid(*item).name());
347	}
348	_output << BString().SetToFormat(
349		"%s  frame: (%f, %f, %f, %f)\n"
350		"%s  min:   (%f, %f)\n"
351		"%s  max:   (%f, %f)\n"
352		"%s  pref:  (%f, %f)\n",
353		indent.String(), frame.left, frame.top, frame.right, frame.bottom,
354		indent.String(), min.width, min.height,
355		indent.String(), max.width, max.height,
356		indent.String(), preferred.width, preferred.height);
357
358	if (layout == NULL)
359		return;
360
361	int32 count = layout->CountItems();
362	for (int32 i = 0; i < count; i++) {
363		_output << indent << "  ---\n";
364		_GetLayoutTreeDump(layout->ItemAt(i), level + 1, false, _output);
365	}
366}
367