1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /***************************************************************************
3  *            layout.cc
4  *
5  *  Sat Mar 21 15:12:36 CET 2015
6  *  Copyright 2015 Bent Bisballe Nyeng
7  *  deva@aasimon.org
8  ****************************************************************************/
9 
10 /*
11  *  This file is part of DrumGizmo.
12  *
13  *  DrumGizmo is free software; you can redistribute it and/or modify
14  *  it under the terms of the GNU Lesser General Public License as published by
15  *  the Free Software Foundation; either version 3 of the License, or
16  *  (at your option) any later version.
17  *
18  *  DrumGizmo is distributed in the hope that it will be useful,
19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  *  GNU Lesser General Public License for more details.
22  *
23  *  You should have received a copy of the GNU Lesser General Public License
24  *  along with DrumGizmo; if not, write to the Free Software
25  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
26  */
27 #include "layout.h"
28 
29 #include "widget.h"
30 
31 #include <algorithm>
32 
33 namespace GUI
34 {
35 
LayoutItem()36 LayoutItem::LayoutItem()
37 	: parent(nullptr)
38 {
39 }
40 
~LayoutItem()41 LayoutItem::~LayoutItem()
42 {
43 	setLayoutParent(nullptr); // Will disconnect from layout if any.
44 }
45 
setLayoutParent(Layout * p)46 void LayoutItem::setLayoutParent(Layout* p)
47 {
48 	if(this->parent)
49 	{
50 		this->parent->removeItem(this);
51 	}
52 
53 	this->parent = p;
54 }
55 
Layout(LayoutItem * parent)56 Layout::Layout(LayoutItem* parent) : parent(parent)
57 {
58 	auto widget = dynamic_cast<Widget*>(parent);
59 	if(widget)
60 	{
61 		CONNECT(widget, sizeChangeNotifier, this, &Layout::sizeChanged);
62 	}
63 }
64 
addItem(LayoutItem * item)65 void Layout::addItem(LayoutItem* item)
66 {
67 	items.push_back(item);
68 	item->setLayoutParent(this);
69 	layout();
70 }
71 
removeItem(LayoutItem * item)72 void Layout::removeItem(LayoutItem* item)
73 {
74 	auto new_end = std::remove(items.begin(), items.end(), item);
75 	items.erase(new_end, items.end());
76 
77 	layout();
78 }
79 
sizeChanged(int width,int height)80 void Layout::sizeChanged(int width, int height)
81 {
82 	layout();
83 }
84 
85 //
86 // BoxLayout
87 //
88 
BoxLayout(LayoutItem * parent)89 BoxLayout::BoxLayout(LayoutItem* parent) : Layout(parent)
90 {
91 }
92 
setResizeChildren(bool resizeChildren)93 void BoxLayout::setResizeChildren(bool resizeChildren)
94 {
95 	this->resizeChildren = resizeChildren;
96 	layout();
97 }
98 
setSpacing(size_t spacing)99 void BoxLayout::setSpacing(size_t spacing)
100 {
101 	this->spacing = spacing;
102 	layout();
103 }
104 
105 //
106 // VBoxLayout
107 //
108 
VBoxLayout(LayoutItem * parent)109 VBoxLayout::VBoxLayout(LayoutItem* parent)
110 	: BoxLayout(parent)
111 	, align(HAlignment::center)
112 {
113 }
114 
layout()115 void VBoxLayout::layout()
116 {
117 	size_t y = 0;
118 	size_t w = parent->width();
119 	// size_t h = parent->height() / items.size();
120 
121 	LayoutItemList::iterator i = items.begin();
122 	while(i != items.end())
123 	{
124 		LayoutItem* item = *i;
125 
126 		if(resizeChildren)
127 		{
128 			auto num_items = items.size();
129 			auto empty_space = (num_items - 1) * spacing;
130 			auto available_space = parent->height();
131 
132 			if(available_space >= empty_space)
133 			{
134 				auto item_height = (available_space - empty_space) / num_items;
135 				item->resize(w, item_height);
136 			}
137 			else
138 			{
139 				// TODO: Should this case be handled differently?
140 				item->resize(w, 0);
141 			}
142 		}
143 
144 		size_t x = 0;
145 		switch(align)
146 		{
147 		case HAlignment::left:
148 			x = 0;
149 			break;
150 		case HAlignment::center:
151 			x = (w / 2) - (item->width() / 2);
152 			break;
153 		case HAlignment::right:
154 			x = w - item->width();
155 			break;
156 		}
157 
158 		item->move(x, y);
159 		y += item->height() + spacing;
160 		++i;
161 	}
162 }
163 
setHAlignment(HAlignment alignment)164 void VBoxLayout::setHAlignment(HAlignment alignment)
165 {
166 	align = alignment;
167 }
168 
169 //
170 // HBoxLayout
171 //
172 
HBoxLayout(LayoutItem * parent)173 HBoxLayout::HBoxLayout(LayoutItem* parent)
174 	: BoxLayout(parent)
175 	, align(VAlignment::center)
176 {
177 }
178 
layout()179 void HBoxLayout::layout()
180 {
181 	if(items.empty())
182 	{
183 		return;
184 	}
185 
186 	//	size_t w = parent->width() / items.size();
187 	size_t h = parent->height();
188 	size_t x = 0;
189 
190 	LayoutItemList::iterator i = items.begin();
191 	while(i != items.end())
192 	{
193 		LayoutItem* item = *i;
194 		if(resizeChildren)
195 		{
196 			auto num_items = items.size();
197 			auto empty_space = (num_items - 1) * spacing;
198 			auto available_space = parent->width();
199 
200 			if(available_space >= empty_space)
201 			{
202 				auto item_width = (available_space - empty_space) / num_items;
203 				item->resize(item_width, h);
204 			}
205 			else
206 			{
207 				// TODO: Should this case be handled differently?
208 				item->resize(0, h);
209 			}
210 
211 			item->move(x, 0);
212 		}
213 		else
214 		{
215 			size_t y = 0;
216 			switch(align)
217 			{
218 			case VAlignment::top:
219 				y = 0;
220 				break;
221 			case VAlignment::center:
222 				y = (h / 2) - (item->height() / 2);
223 				break;
224 			case VAlignment::bottom:
225 				y = h - item->height();
226 				break;
227 			}
228 
229 			int diff = 0; // w - item->width();
230 			item->move(x + diff / 2, y);
231 		}
232 		x += item->width() + spacing;
233 		++i;
234 	}
235 }
236 
setVAlignment(VAlignment alignment)237 void HBoxLayout::setVAlignment(VAlignment alignment)
238 {
239 	align = alignment;
240 }
241 
242 //
243 // GridLayout
244 //
245 
GridLayout(LayoutItem * parent,std::size_t number_of_columns,std::size_t number_of_rows)246 GridLayout::GridLayout(LayoutItem* parent, std::size_t number_of_columns,
247                        std::size_t number_of_rows)
248 	: BoxLayout(parent)
249 	, number_of_columns(number_of_columns)
250 	, number_of_rows(number_of_rows)
251 {
252 }
253 
removeItem(LayoutItem * item)254 void GridLayout::removeItem(LayoutItem* item)
255 {
256 	// manually remove from grid_ranges as remove_if doesn't work on an
257 	// unordered_map.
258 	auto it = grid_ranges.begin();
259 	while(it != grid_ranges.end())
260 	{
261 		if(it->first == item)
262 		{
263 			it = grid_ranges.erase(it);
264 		}
265 		else
266 		{
267 			++it;
268 		}
269 	}
270 
271 	Layout::removeItem(item);
272 }
273 
layout()274 void GridLayout::layout()
275 {
276 	if(grid_ranges.empty())
277 	{
278 		return;
279 	}
280 
281 	// Calculate cell sizes
282 	auto cell_size = calculateCellSize();
283 
284 	for(auto const& pair : grid_ranges)
285 	{
286 		auto& item = *pair.first;
287 		auto const& range = pair.second;
288 
289 		moveAndResize(item, range, cell_size);
290 	}
291 }
292 
setPosition(LayoutItem * item,GridRange const & range)293 void GridLayout::setPosition(LayoutItem* item, GridRange const& range)
294 {
295 	grid_ranges[item] = range;
296 }
297 
lastUsedRow(int column) const298 int GridLayout::lastUsedRow(int column) const
299 {
300 	int last_row = -1;
301 
302 	for (auto const& grid_range : grid_ranges)
303 	{
304 		auto const& range = grid_range.second;
305 		if (column >= range.column_begin && column < range.column_end)
306 		{
307 			last_row = std::max(last_row, range.row_end - 1);
308 		}
309 	}
310 
311 	return last_row;
312 }
313 
lastUsedColumn(int row) const314 int GridLayout::lastUsedColumn(int row) const
315 {
316 	int last_column = -1;
317 
318 	for (auto const& grid_range : grid_ranges)
319 	{
320 		auto const& range = grid_range.second;
321 		if (row >= range.row_begin && row < range.row_end)
322 		{
323 			last_column = std::max(last_column, range.column_end - 1);
324 		}
325 	}
326 
327 	return last_column;
328 
329 }
330 
calculateCellSize() const331 auto GridLayout::calculateCellSize() const -> CellSize
332 {
333 	auto empty_width = (number_of_columns - 1) * spacing;
334 	auto available_width = parent->width();
335 	auto empty_height = (number_of_rows - 1) * spacing;
336 	auto available_height = parent->height();
337 
338 	CellSize cell_size;
339 	if(available_width > empty_width && available_height > empty_height)
340 	{
341 		cell_size.width = (available_width - empty_width) / number_of_columns;
342 		cell_size.height = (available_height - empty_height) / number_of_rows;
343 	}
344 	else
345 	{
346 		cell_size.width = 0;
347 		cell_size.height = 0;
348 	}
349 
350 	return cell_size;
351 }
352 
moveAndResize(LayoutItem & item,GridRange const & range,CellSize cell_size) const353 void GridLayout::moveAndResize(
354 	LayoutItem& item, GridRange const& range, CellSize cell_size) const
355 {
356 	std::size_t x = range.column_begin * (cell_size.width + spacing);
357 	std::size_t y = range.row_begin * (cell_size.height + spacing);
358 
359 	std::size_t column_count = (range.column_end - range.column_begin);
360 	std::size_t row_count = (range.row_end - range.row_begin);
361 	std::size_t width = column_count * (cell_size.width + spacing) - spacing;
362 	std::size_t height = row_count * (cell_size.height + spacing) - spacing;
363 
364 	if(resizeChildren)
365 	{
366 		item.move(x, y);
367 
368 		if(cell_size.width * cell_size.height != 0)
369 		{
370 			item.resize(width, height);
371 		}
372 		else
373 		{
374 			item.resize(0, 0);
375 		}
376 	}
377 	else
378 	{
379 		auto x_new = (item.width() > width) ? x : x + (width - item.width()) / 2;
380 		auto y_new = (item.height() > height) ? y : y + (height - item.height()) / 2;
381 
382 		item.move(x_new, y_new);
383 	}
384 }
385 
386 } // GUI::
387