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