1 /* GG is a GUI for OpenGL.
2 Copyright (C) 2003-2008 T. Zachary Laine
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public License
6 as published by the Free Software Foundation; either version 2.1
7 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library; if not, write to the Free
16 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17 02111-1307 USA
18
19 If you do not wish to comply with the terms of the LGPL please
20 contact the author as other terms are available for a fee.
21
22 Zach Laine
23 whatwasthataddress@gmail.com */
24
25 #include <GG/Layout.h>
26
27 #include <GG/ClrConstants.h>
28 #include <GG/DrawUtil.h>
29 #include <GG/TextControl.h>
30 #include <GG/WndEvent.h>
31
32 #include <cassert>
33 #include <cmath>
34
35 using namespace GG;
36
37 namespace {
MinDueToMargin(unsigned int cell_margin,std::size_t num_rows_or_columns,std::size_t row_or_column)38 unsigned int MinDueToMargin(unsigned int cell_margin, std::size_t num_rows_or_columns, std::size_t row_or_column)
39 {
40 return (row_or_column == 0 || row_or_column == num_rows_or_columns - 1) ?
41 static_cast<unsigned int>(std::ceil(cell_margin / 2.0)) :
42 cell_margin;
43 }
44 }
45
46 // RowColParams
RowColParams()47 Layout::RowColParams::RowColParams() :
48 stretch(0),
49 min(0),
50 effective_min(0),
51 current_origin(0),
52 current_width(0)
53 {}
54
55 // WndPosition
WndPosition()56 Layout::WndPosition::WndPosition() :
57 first_row(0),
58 first_column(0),
59 last_row(0),
60 last_column(0),
61 alignment(ALIGN_NONE)
62 {}
63
WndPosition(std::size_t first_row_,std::size_t first_column_,std::size_t last_row_,std::size_t last_column_,Flags<Alignment> alignment_,const Pt & original_ul_,const Pt & original_size_)64 Layout::WndPosition::WndPosition(std::size_t first_row_, std::size_t first_column_,
65 std::size_t last_row_, std::size_t last_column_,
66 Flags<Alignment> alignment_, const Pt& original_ul_, const Pt& original_size_) :
67 first_row(first_row_),
68 first_column(first_column_),
69 last_row(last_row_),
70 last_column(last_column_),
71 alignment(alignment_),
72 original_ul(original_ul_),
73 original_size(original_size_)
74 {}
75
76 // Layout
77 const unsigned int Layout::INVALID_CELL_MARGIN = std::numeric_limits<unsigned int>::max();
78
Layout(X x,Y y,X w,Y h,std::size_t rows,std::size_t columns,unsigned int border_margin,unsigned int cell_margin)79 Layout::Layout(X x, Y y, X w, Y h, std::size_t rows, std::size_t columns,
80 unsigned int border_margin/* = 0*/, unsigned int cell_margin/* = INVALID_CELL_MARGIN*/) :
81 Wnd(x, y, w, h, NO_WND_FLAGS),
82 m_cells(rows, std::vector<std::weak_ptr<Wnd>>(columns)),
83 m_border_margin(border_margin),
84 m_cell_margin(cell_margin == INVALID_CELL_MARGIN ? border_margin : cell_margin),
85 m_row_params(rows),
86 m_column_params(columns),
87 m_ignore_child_resize(false),
88 m_stop_resize_recursion(false),
89 m_render_outline(false),
90 m_outline_color(CLR_MAGENTA)
91 {
92 assert(rows);
93 assert(columns);
94 }
95
MinUsableSize() const96 Pt Layout::MinUsableSize() const
97 { return m_min_usable_size; }
98
Rows() const99 std::size_t Layout::Rows() const
100 { return m_cells.size(); }
101
Columns() const102 std::size_t Layout::Columns() const
103 { return m_cells.empty() ? 0 : m_cells[0].size(); }
104
ChildAlignment(const Wnd * wnd) const105 Flags<Alignment> Layout::ChildAlignment(const Wnd* wnd) const
106 {
107 auto it = m_wnd_positions.find(const_cast<Wnd*>(wnd));
108 if (it == m_wnd_positions.end())
109 throw NoSuchChild("Layout::ChildAlignment() : Alignment of a nonexistent child was requested");
110 return it->second.alignment;
111 }
112
BorderMargin() const113 unsigned int Layout::BorderMargin() const
114 { return m_border_margin; }
115
CellMargin() const116 unsigned int Layout::CellMargin() const
117 { return m_cell_margin; }
118
RowStretch(std::size_t row) const119 double Layout::RowStretch(std::size_t row) const
120 { return m_row_params[row].stretch; }
121
ColumnStretch(std::size_t column) const122 double Layout::ColumnStretch(std::size_t column) const
123 { return m_column_params[column].stretch; }
124
MinimumRowHeight(std::size_t row) const125 Y Layout::MinimumRowHeight(std::size_t row) const
126 { return Y(m_row_params[row].min); }
127
MinimumColumnWidth(std::size_t column) const128 X Layout::MinimumColumnWidth(std::size_t column) const
129 { return X(m_column_params[column].min); }
130
Cells() const131 std::vector<std::vector<const Wnd*>> Layout::Cells() const
132 {
133 std::vector<std::vector<const Wnd*>> retval(m_cells.size());
134 for (std::size_t i = 0; i < m_cells.size(); ++i) {
135 retval[i].resize(m_cells[i].size());
136 for (std::size_t j = 0; j < m_cells[i].size(); ++j) {
137 retval[i][j] = m_cells[i][j].lock().get();
138 }
139 }
140 return retval;
141 }
142
CellRects() const143 std::vector<std::vector<Rect>> Layout::CellRects() const
144 {
145 std::vector<std::vector<Rect>> retval = RelativeCellRects();
146 for (std::vector<Rect>& column : retval) {
147 for (Rect& cell : column) {
148 cell += ClientUpperLeft();
149 }
150 }
151 return retval;
152 }
153
RelativeCellRects() const154 std::vector<std::vector<Rect>> Layout::RelativeCellRects() const
155 {
156 std::vector<std::vector<Rect>> retval(m_cells.size());
157 for (std::size_t i = 0; i < m_cells.size(); ++i) {
158 retval[i].resize(m_cells[i].size());
159 for (std::size_t j = 0; j < m_cells[i].size(); ++j) {
160 Pt ul(X(m_column_params[j].current_origin),
161 Y(m_row_params[i].current_origin));
162 Pt lr = ul + Pt(X(m_column_params[j].current_width),
163 Y(m_row_params[i].current_width));
164 Rect rect(ul, lr);
165 if (j)
166 rect.ul.x += static_cast<int>(m_cell_margin / 2);
167 if (j != m_cells[i].size() - 1)
168 rect.lr.x -= static_cast<int>(m_cell_margin - m_cell_margin / 2);
169 if (i)
170 rect.ul.y += static_cast<int>(m_cell_margin / 2);
171 if (i != m_cells.size() - 1)
172 rect.lr.y -= static_cast<int>(m_cell_margin - m_cell_margin / 2);
173 retval[i][j] = rect;
174 }
175 }
176 return retval;
177 }
178
RenderOutline() const179 bool Layout::RenderOutline() const
180 { return m_render_outline; }
181
OutlineColor() const182 Clr Layout::OutlineColor() const
183 { return m_outline_color; }
184
StartingChildDragDrop(const Wnd * wnd,const Pt & offset)185 void Layout::StartingChildDragDrop(const Wnd* wnd, const Pt& offset)
186 {
187 if (auto&& parent = Parent())
188 parent->StartingChildDragDrop(wnd, offset);
189 }
190
CancellingChildDragDrop(const std::vector<const Wnd * > & wnds)191 void Layout::CancellingChildDragDrop(const std::vector<const Wnd*>& wnds)
192 {
193 if (auto&& parent = Parent())
194 parent->CancellingChildDragDrop(wnds);
195 }
196
ChildrenDraggedAway(const std::vector<Wnd * > & wnds,const Wnd * destination)197 void Layout::ChildrenDraggedAway(const std::vector<Wnd*>& wnds, const Wnd* destination)
198 {
199 if (auto&& parent = Parent())
200 parent->ChildrenDraggedAway(wnds, destination);
201 }
202
SizeMove(const Pt & ul,const Pt & lr)203 void Layout::SizeMove(const Pt& ul, const Pt& lr)
204 { DoLayout(ul, lr); }
205
DoLayout(Pt ul,Pt lr)206 void Layout::DoLayout(Pt ul, Pt lr)
207 {
208 if (m_stop_resize_recursion)
209 return;
210
211 // these hold values used to calculate m_min_usable_size
212 std::vector<unsigned int> row_effective_min_usable_sizes(m_row_params.size());
213 std::vector<unsigned int> column_effective_min_usable_sizes(m_column_params.size());
214
215 // reset effective_min values
216 for (std::size_t i = 0; i < m_row_params.size(); ++i) {
217 unsigned int min_due_to_margin = MinDueToMargin(m_cell_margin, m_row_params.size(), i);
218 m_row_params[i].effective_min = std::max(m_row_params[i].min, min_due_to_margin);
219 row_effective_min_usable_sizes[i] = min_due_to_margin;
220 }
221
222 for (std::size_t i = 0; i < m_column_params.size(); ++i) {
223 unsigned int min_due_to_margin = MinDueToMargin(m_cell_margin, m_column_params.size(), i);
224 m_column_params[i].effective_min = std::max(m_column_params[i].min, min_due_to_margin);
225 column_effective_min_usable_sizes[i] = min_due_to_margin;
226 }
227
228 // adjust effective minimums based on cell contents
229 for (const auto& wnd_position : m_wnd_positions) {
230 Pt margin;
231 if (0 < wnd_position.second.first_row && wnd_position.second.last_row < m_row_params.size())
232 margin.y = Y(m_cell_margin);
233 else if (0 < wnd_position.second.first_row || wnd_position.second.last_row < m_row_params.size())
234 margin.y = Y(static_cast<int>(std::ceil(m_cell_margin / 2.0)));
235 if (0 < wnd_position.second.first_column && wnd_position.second.last_column < m_column_params.size())
236 margin.x = X(m_cell_margin);
237 else if (0 < wnd_position.second.first_column || wnd_position.second.last_column < m_column_params.size())
238 margin.x = X(static_cast<int>(std::ceil(m_cell_margin / 2.0)));
239
240 Pt min_space_needed = wnd_position.first->MinSize() + margin;
241 Pt min_usable_size = wnd_position.first->MinUsableSize() + margin;
242
243 // HACK! This is put here so that TextControl, which is currently GG's
244 // only height-for-width Wnd type, doesn't get vertically squashed
245 // down to 0-height cells. Note that they can still get horizontally
246 // squashed.
247 if (TextControl* text_control = dynamic_cast<TextControl*>(wnd_position.first)) {
248 min_space_needed.y = (text_control->MinUsableSize(Width()) + margin).y;
249 min_usable_size.y = min_space_needed.y;
250 }
251
252 // adjust row minimums
253 double total_stretch = 0.0;
254 for (std::size_t i = wnd_position.second.first_row; i < wnd_position.second.last_row; ++i) {
255 total_stretch += m_row_params[i].stretch;
256 }
257 if (total_stretch) {
258 for (std::size_t i = wnd_position.second.first_row; i < wnd_position.second.last_row; ++i) {
259 m_row_params[i].effective_min = std::max(m_row_params[i].effective_min, static_cast<unsigned int>(Value(min_space_needed.y / total_stretch * m_row_params[i].stretch)));
260 row_effective_min_usable_sizes[i] = std::max(row_effective_min_usable_sizes[i], static_cast<unsigned int>(Value(min_usable_size.y / total_stretch * m_row_params[i].stretch)));
261 }
262 } else { // if all rows have 0.0 stretch, distribute height evenly
263 double per_row_min = Value(min_space_needed.y / static_cast<double>(wnd_position.second.last_row - wnd_position.second.first_row));
264 double per_row_usable_min = Value(min_usable_size.y / static_cast<double>(wnd_position.second.last_row - wnd_position.second.first_row));
265 for (std::size_t i = wnd_position.second.first_row; i < wnd_position.second.last_row; ++i) {
266 m_row_params[i].effective_min = std::max(m_row_params[i].effective_min, static_cast<unsigned int>(per_row_min + 0.5));
267 row_effective_min_usable_sizes[i] = std::max(row_effective_min_usable_sizes[i], static_cast<unsigned int>(per_row_usable_min + 0.5));
268 }
269 }
270
271 // adjust column minimums
272 total_stretch = 0.0;
273 for (std::size_t i = wnd_position.second.first_column; i < wnd_position.second.last_column; ++i) {
274 total_stretch += m_column_params[i].stretch;
275 }
276 if (total_stretch) {
277 for (std::size_t i = wnd_position.second.first_column; i < wnd_position.second.last_column; ++i) {
278 m_column_params[i].effective_min = std::max(m_column_params[i].effective_min, static_cast<unsigned int>(Value(min_space_needed.x / total_stretch * m_column_params[i].stretch)));
279 column_effective_min_usable_sizes[i] = std::max(column_effective_min_usable_sizes[i], static_cast<unsigned int>(Value(min_usable_size.x / total_stretch * m_column_params[i].stretch)));
280 }
281 } else { // if all columns have 0.0 stretch, distribute width evenly
282 double per_column_min = Value(min_space_needed.x / static_cast<double>(wnd_position.second.last_column - wnd_position.second.first_column));
283 double per_column_usable_min = Value(min_usable_size.x / static_cast<double>(wnd_position.second.last_column - wnd_position.second.first_column));
284 for (std::size_t i = wnd_position.second.first_column; i < wnd_position.second.last_column; ++i) {
285 m_column_params[i].effective_min = std::max(m_column_params[i].effective_min, static_cast<unsigned int>(per_column_min + 0.5));
286 column_effective_min_usable_sizes[i] = std::max(column_effective_min_usable_sizes[i], static_cast<unsigned int>(per_column_usable_min + 0.5));
287 }
288 }
289 }
290
291 // determine final effective minimums, preserving stretch ratios
292 double greatest_min_over_stretch_ratio = 0.0;
293 double greatest_usable_min_over_stretch_ratio = 0.0;
294 bool is_zero_total_stretch = true;
295 for (std::size_t i = 0; i < m_row_params.size(); ++i) {
296 if (m_row_params[i].stretch) {
297 is_zero_total_stretch = false;
298 greatest_min_over_stretch_ratio = std::max(greatest_min_over_stretch_ratio, m_row_params[i].effective_min / m_row_params[i].stretch);
299 greatest_usable_min_over_stretch_ratio = std::max(greatest_usable_min_over_stretch_ratio, row_effective_min_usable_sizes[i] / m_row_params[i].stretch);
300 }
301 }
302 for (std::size_t i = 0; i < m_row_params.size(); ++i) {
303 if (is_zero_total_stretch || m_row_params[i].stretch) {
304 m_row_params[i].effective_min = std::max(
305 m_row_params[i].effective_min,
306 static_cast<unsigned int>(m_row_params[i].stretch * greatest_min_over_stretch_ratio));
307 row_effective_min_usable_sizes[i] = std::max(
308 row_effective_min_usable_sizes[i],
309 static_cast<unsigned int>(m_row_params[i].stretch * greatest_usable_min_over_stretch_ratio));
310 }
311 }
312 greatest_min_over_stretch_ratio = 0.0;
313 greatest_usable_min_over_stretch_ratio = 0.0;
314 is_zero_total_stretch = true;
315 for (std::size_t i = 0; i < m_column_params.size(); ++i) {
316 if (m_column_params[i].stretch) {
317 greatest_min_over_stretch_ratio = std::max(greatest_min_over_stretch_ratio, m_column_params[i].effective_min / m_column_params[i].stretch);
318 greatest_usable_min_over_stretch_ratio = std::max(greatest_usable_min_over_stretch_ratio, column_effective_min_usable_sizes[i] / m_column_params[i].stretch);
319 }
320 }
321 for (std::size_t i = 0; i < m_column_params.size(); ++i) {
322 if (is_zero_total_stretch || m_column_params[i].stretch) {
323 m_column_params[i].effective_min = std::max(
324 m_column_params[i].effective_min,
325 static_cast<unsigned int>(m_column_params[i].stretch * greatest_min_over_stretch_ratio));
326 column_effective_min_usable_sizes[i] = std::max(
327 column_effective_min_usable_sizes[i],
328 static_cast<unsigned int>(m_column_params[i].stretch * greatest_usable_min_over_stretch_ratio));
329 }
330 }
331
332 //TODO Determine min usable size before stretching to fit space requested
333 m_min_usable_size.x = X(2 * m_border_margin);
334 for (unsigned int column_size : column_effective_min_usable_sizes) {
335 m_min_usable_size.x += static_cast<int>(column_size);
336 }
337 m_min_usable_size.y = Y(2 * m_border_margin);
338 for (unsigned int row_size : row_effective_min_usable_sizes) {
339 m_min_usable_size.y += static_cast<int>(row_size);
340 }
341
342 bool size_or_min_size_changed = false;
343 Pt new_min_size(TotalMinWidth(), TotalMinHeight());
344 if (new_min_size != MinSize()) {
345 ScopedAssign<bool> assignment(m_stop_resize_recursion, true);
346 SetMinSize(new_min_size);
347 ClampRectWithMinAndMaxSize(ul, lr);
348 size_or_min_size_changed = true;
349 }
350 Pt original_size = Size();
351 Wnd::SizeMove(ul, lr);
352 if (Size() != original_size)
353 size_or_min_size_changed = true;
354
355 // if this is the layout object for some Wnd, propogate the minimum size up to the owning Wnd
356 if (auto&& parent = Parent()) {
357 if (parent->GetLayout().get() == this) {
358 Pt new_parent_min_size = MinSize() + parent->Size() - parent->ClientSize();
359 ScopedAssign<bool> assignment(m_stop_resize_recursion, true);
360 parent->SetMinSize(Pt(new_parent_min_size.x, new_parent_min_size.y));
361 }
362 }
363
364 // determine row and column positions
365 double total_stretch = TotalStretch(m_row_params);
366 int total_stretch_space = Value(Size().y - MinSize().y);
367 double space_per_unit_stretch = total_stretch ? total_stretch_space / total_stretch : 0.0;
368 bool larger_than_min = 0 < total_stretch_space;
369 double remainder = 0.0;
370 int current_origin = m_border_margin;
371 for (std::size_t i = 0; i < m_row_params.size(); ++i) {
372 if (larger_than_min) {
373 if (i < m_row_params.size() - 1) {
374 double raw_width =
375 m_row_params[i].effective_min +
376 (total_stretch ?
377 space_per_unit_stretch * m_row_params[i].stretch :
378 total_stretch_space / static_cast<double>(m_row_params.size()));
379 int int_raw_width = static_cast<int>(raw_width);
380 m_row_params[i].current_width = int_raw_width;
381 remainder += raw_width - int_raw_width;
382 if (1.0 < remainder) {
383 --remainder;
384 ++m_row_params[i].current_width;
385 }
386 } else {
387 m_row_params[i].current_width = Value(Height()) - m_border_margin - current_origin;
388 }
389 } else {
390 m_row_params[i].current_width = m_row_params[i].effective_min;
391 }
392 m_row_params[i].current_origin = current_origin;
393 current_origin += m_row_params[i].current_width;
394 }
395
396 total_stretch = TotalStretch(m_column_params);
397 total_stretch_space = Value(Size().x - MinSize().x);
398 space_per_unit_stretch = total_stretch ? total_stretch_space / total_stretch : 0.0;
399 larger_than_min = 0 < total_stretch_space;
400 remainder = 0.0;
401 current_origin = m_border_margin;
402 for (std::size_t i = 0; i < m_column_params.size(); ++i) {
403 if (larger_than_min) {
404 if (i < m_column_params.size() - 1) {
405 double raw_width =
406 m_column_params[i].effective_min +
407 (total_stretch ?
408 space_per_unit_stretch * m_column_params[i].stretch :
409 total_stretch_space / static_cast<double>(m_column_params.size()));
410 int int_raw_width = static_cast<int>(raw_width);
411 m_column_params[i].current_width = int_raw_width;
412 remainder += raw_width - int_raw_width;
413 if (1.0 < remainder) {
414 --remainder;
415 ++m_column_params[i].current_width;
416 }
417 } else {
418 m_column_params[i].current_width = Value(Width()) - m_border_margin - current_origin;
419 }
420 } else {
421 m_column_params[i].current_width = m_column_params[i].effective_min;
422 }
423 m_column_params[i].current_origin = current_origin;
424 current_origin += m_column_params[i].current_width;
425 }
426
427 if (m_row_params.back().current_origin + m_row_params.back().current_width != Value(Height()) - m_border_margin)
428 throw FailedCalculationCheck("Layout::DoLayout() : calculated row positions do not sum to the height of the layout");
429
430 if (m_column_params.back().current_origin + m_column_params.back().current_width != Value(Width()) - m_border_margin)
431 throw FailedCalculationCheck("Layout::DoLayout() : calculated column positions do not sum to the width of the layout");
432
433 // resize cells and their contents
434 m_ignore_child_resize = true;
435 for (auto& wnd_position : m_wnd_positions) {
436 Pt wnd_ul(X(m_column_params[wnd_position.second.first_column].current_origin),
437 Y(m_row_params[wnd_position.second.first_row].current_origin));
438 Pt wnd_lr(X(m_column_params[wnd_position.second.last_column - 1].current_origin +
439 m_column_params[wnd_position.second.last_column - 1].current_width),
440 Y(m_row_params[wnd_position.second.last_row - 1].current_origin +
441 m_row_params[wnd_position.second.last_row - 1].current_width));
442 Pt ul_margin(X0, Y0);
443 Pt lr_margin(X0, Y0);
444 if (0 < wnd_position.second.first_row)
445 ul_margin.y += static_cast<int>(m_cell_margin / 2);
446 if (0 < wnd_position.second.first_column)
447 ul_margin.x += static_cast<int>(m_cell_margin / 2);
448 if (wnd_position.second.last_row < m_row_params.size())
449 lr_margin.y += static_cast<int>(m_cell_margin / 2.0 + 0.5);
450 if (wnd_position.second.last_column < m_column_params.size())
451 lr_margin.x += static_cast<int>(m_cell_margin / 2.0 + 0.5);
452
453 wnd_ul += ul_margin;
454 wnd_lr -= lr_margin;
455
456 if (wnd_position.second.alignment == ALIGN_NONE) { // expand to fill available space
457 wnd_position.first->SizeMove(wnd_ul, wnd_lr);
458 } else { // align as appropriate
459 Pt available_space = wnd_lr - wnd_ul;
460 Pt min_usable_size = wnd_position.first->MinUsableSize();
461 Pt min_size = wnd_position.first->MinSize();
462
463 // HACK! This is put here so that TextControl, which is currently GG's
464 // only height-for-width Wnd type, doesn't get vertically squashed
465 // down to 0-height cells. Note that they can still get horizontally
466 // squashed.
467 if (TextControl* text_control = dynamic_cast<TextControl*>(wnd_position.first)) {
468 X text_width = (wnd_lr.x - wnd_ul.x);
469 min_usable_size = text_control->MinUsableSize(text_width);
470 min_size = min_usable_size;
471 }
472
473 Pt window_size(std::min(available_space.x, std::max(wnd_position.second.original_size.x, std::max(min_size.x, min_usable_size.x))),
474 std::min(available_space.y, std::max(wnd_position.second.original_size.y, std::max(min_size.y, min_usable_size.y))));
475 Pt resize_ul, resize_lr;
476 if (wnd_position.second.alignment & ALIGN_LEFT) {
477 resize_ul.x = wnd_ul.x;
478 resize_lr.x = resize_ul.x + window_size.x;
479 } else if (wnd_position.second.alignment & ALIGN_CENTER) {
480 resize_ul.x = wnd_ul.x + (available_space.x - window_size.x) / 2;
481 resize_lr.x = resize_ul.x + window_size.x;
482 } else if (wnd_position.second.alignment & ALIGN_RIGHT) {
483 resize_lr.x = wnd_lr.x;
484 resize_ul.x = resize_lr.x - window_size.x;
485 } else {
486 resize_ul.x = wnd_ul.x;
487 resize_lr.x = wnd_lr.x;
488 }
489 if (wnd_position.second.alignment & ALIGN_TOP) {
490 resize_ul.y = wnd_ul.y;
491 resize_lr.y = resize_ul.y + window_size.y;
492 } else if (wnd_position.second.alignment & ALIGN_VCENTER) {
493 resize_ul.y = wnd_ul.y + (available_space.y - window_size.y) / 2;
494 resize_lr.y = resize_ul.y + window_size.y;
495 } else if (wnd_position.second.alignment & ALIGN_BOTTOM) {
496 resize_lr.y = wnd_lr.y;
497 resize_ul.y = resize_lr.y - window_size.y;
498 } else {
499 resize_ul.y = wnd_ul.y;
500 resize_lr.y = wnd_lr.y;
501 }
502 wnd_position.first->SizeMove(resize_ul, resize_lr);
503 }
504 }
505 m_ignore_child_resize = false;
506
507 if (ContainingLayout() && size_or_min_size_changed)
508 ContainingLayout()->ChildSizeOrMinSizeChanged();
509 }
510
Render()511 void Layout::Render()
512 {
513 if (m_render_outline) {
514 Pt ul = UpperLeft(), lr = LowerRight();
515 FlatRectangle(ul, lr, CLR_ZERO, m_outline_color, 1);
516 for (const std::vector<Rect>& columns : CellRects()) {
517 for (const Rect& cell : columns) {
518 FlatRectangle(cell.ul, cell.lr, CLR_ZERO, m_outline_color, 1);
519 }
520 }
521 }
522 }
523
Add(std::shared_ptr<Wnd> wnd,std::size_t row,std::size_t column,Flags<Alignment> alignment)524 void Layout::Add(std::shared_ptr<Wnd> wnd, std::size_t row, std::size_t column, Flags<Alignment> alignment/* = ALIGN_NONE*/)
525 { Add(std::forward<std::shared_ptr<Wnd>>(wnd), row, column, 1, 1, alignment); }
526
Add(std::shared_ptr<Wnd> wnd,std::size_t row,std::size_t column,std::size_t num_rows,std::size_t num_columns,Flags<Alignment> alignment)527 void Layout::Add(std::shared_ptr<Wnd> wnd, std::size_t row, std::size_t column, std::size_t num_rows, std::size_t num_columns,
528 Flags<Alignment> alignment/* = ALIGN_NONE*/)
529 {
530 std::size_t last_row = row + num_rows;
531 std::size_t last_column = column + num_columns;
532 assert(row < last_row);
533 assert(column < last_column);
534 ValidateAlignment(alignment);
535 if (m_cells.size() < last_row || m_cells[0].size() < last_column) {
536 ResizeLayout(std::max(last_row, Rows()), std::max(last_column, Columns()));
537 }
538 for (std::size_t i = row; i < last_row; ++i) {
539 for (std::size_t j = column; j < last_column; ++j) {
540 if (m_cells[i][j].lock())
541 throw AttemptedOverwrite("Layout::Add() : Attempted to add a Wnd to a layout cell that is already occupied");
542 m_cells[i][j] = wnd;
543 }
544 }
545 if (wnd) {
546 m_wnd_positions[wnd.get()] = WndPosition(row, column, last_row, last_column, alignment, wnd->RelativeUpperLeft(), wnd->Size());
547 AttachChild(std::forward<std::shared_ptr<Wnd>>(wnd));
548 }
549 RedoLayout();
550 }
551
Remove(Wnd * wnd)552 void Layout::Remove(Wnd* wnd)
553 {
554 auto it = m_wnd_positions.find(wnd);
555 if (it == m_wnd_positions.end())
556 return;
557
558 const WndPosition& wnd_position = it->second;
559 for (std::size_t i = wnd_position.first_row; i < wnd_position.last_row; ++i) {
560 for (std::size_t j = wnd_position.first_column; j < wnd_position.last_column; ++j) {
561 m_cells[i][j] = std::weak_ptr<Wnd>();
562 }
563 }
564 Pt original_ul = it->second.original_ul;
565 Pt original_size = it->second.original_size;
566 m_wnd_positions.erase(wnd);
567 RedoLayout();
568 wnd->SizeMove(original_ul, original_ul + original_size);
569 DetachChild(wnd);
570 }
571
DetachAndResetChildren()572 void Layout::DetachAndResetChildren()
573 {
574 std::map<Wnd*, WndPosition> wnd_positions = m_wnd_positions;
575 DetachChildren();
576 for (auto& wnd_position : wnd_positions) {
577 wnd_position.first->SizeMove(wnd_position.second.original_ul, wnd_position.second.original_ul + wnd_position.second.original_size);
578 }
579 m_wnd_positions.clear();
580 }
581
ResizeLayout(std::size_t rows,std::size_t columns)582 void Layout::ResizeLayout(std::size_t rows, std::size_t columns)
583 {
584 assert(0 < rows);
585 assert(0 < columns);
586 if (static_cast<std::size_t>(rows) < m_cells.size()) {
587 for (std::size_t i = static_cast<std::size_t>(rows); i < m_cells.size(); ++i) {
588 for (auto& cell : m_cells[i]) {
589 auto locked = cell.lock();
590 cell.reset();
591 DetachChild(locked.get());
592 m_wnd_positions.erase(locked.get());
593 }
594 }
595 }
596 m_cells.resize(rows);
597 for (auto& row : m_cells) {
598 if (static_cast<std::size_t>(columns) < row.size()) {
599 for (std::size_t j = static_cast<std::size_t>(columns); j < row.size(); ++j) {
600 auto locked = row[j].lock();
601 row[j].reset();
602 DetachChild(locked.get());
603 m_wnd_positions.erase(locked.get());
604 }
605 }
606 row.resize(columns);
607 }
608 m_row_params.resize(rows);
609 m_column_params.resize(columns);
610 RedoLayout();
611 }
612
SetChildAlignment(const Wnd * wnd,Flags<Alignment> alignment)613 void Layout::SetChildAlignment(const Wnd* wnd, Flags<Alignment> alignment)
614 {
615 std::map<Wnd*, WndPosition>::iterator it = m_wnd_positions.find(const_cast<Wnd*>(wnd));
616 if (it != m_wnd_positions.end()) {
617 ValidateAlignment(alignment);
618 it->second.alignment = alignment;
619 RedoLayout();
620 }
621 }
622
SetBorderMargin(unsigned int margin)623 void Layout::SetBorderMargin(unsigned int margin)
624 {
625 m_border_margin = margin;
626 RedoLayout();
627 }
628
SetCellMargin(unsigned int margin)629 void Layout::SetCellMargin(unsigned int margin)
630 {
631 m_cell_margin = margin;
632 RedoLayout();
633 }
634
SetRowStretch(std::size_t row,double stretch)635 void Layout::SetRowStretch(std::size_t row, double stretch)
636 {
637 assert(row < m_row_params.size());
638 m_row_params[row].stretch = stretch;
639 RedoLayout();
640 }
641
SetColumnStretch(std::size_t column,double stretch)642 void Layout::SetColumnStretch(std::size_t column, double stretch)
643 {
644 assert(column < m_column_params.size());
645 m_column_params[column].stretch = stretch;
646 RedoLayout();
647 }
648
SetMinimumRowHeight(std::size_t row,Y height)649 void Layout::SetMinimumRowHeight(std::size_t row, Y height)
650 {
651 assert(row < m_row_params.size());
652 m_row_params[row].min = Value(height);
653 RedoLayout();
654 }
655
SetMinimumColumnWidth(std::size_t column,X width)656 void Layout::SetMinimumColumnWidth(std::size_t column, X width)
657 {
658 assert(column < m_column_params.size());
659 m_column_params[column].min = Value(width);
660 RedoLayout();
661 }
662
RenderOutline(bool render_outline)663 void Layout::RenderOutline(bool render_outline)
664 { m_render_outline = render_outline; }
665
SetOutlineColor(Clr color)666 void Layout::SetOutlineColor(Clr color)
667 { m_outline_color = color; }
668
MouseWheel(const Pt & pt,int move,Flags<ModKey> mod_keys)669 void Layout::MouseWheel(const Pt& pt, int move, Flags<ModKey> mod_keys)
670 { ForwardEventToParent(); }
671
KeyPress(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys)672 void Layout::KeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys)
673 { ForwardEventToParent(); }
674
KeyRelease(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys)675 void Layout::KeyRelease(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys)
676 { ForwardEventToParent(); }
677
TotalStretch(const std::vector<RowColParams> & params_vec) const678 double Layout::TotalStretch(const std::vector<RowColParams>& params_vec) const
679 {
680 double retval = 0.0;
681 for (const RowColParams& param : params_vec) {
682 retval += param.stretch;
683 }
684 return retval;
685 }
686
TotalMinWidth() const687 X Layout::TotalMinWidth() const
688 {
689 X retval = X(2 * m_border_margin);
690 for (const RowColParams& column_param : m_column_params) {
691 retval += static_cast<int>(column_param.effective_min);
692 }
693 return retval;
694 }
695
TotalMinHeight() const696 Y Layout::TotalMinHeight() const
697 {
698 Y retval = Y(2 * m_border_margin);
699 for (const RowColParams& row_param : m_row_params) {
700 retval += static_cast<int>(row_param.effective_min);
701 }
702 return retval;
703 }
704
ValidateAlignment(Flags<Alignment> & alignment)705 void Layout::ValidateAlignment(Flags<Alignment>& alignment)
706 {
707 int dup_ct = 0; // duplication count
708 if (alignment & ALIGN_LEFT) ++dup_ct;
709 if (alignment & ALIGN_RIGHT) ++dup_ct;
710 if (alignment & ALIGN_CENTER) ++dup_ct;
711 if (1 < dup_ct) { // when multiples are picked, use ALIGN_CENTER by default
712 alignment &= ~(ALIGN_RIGHT | ALIGN_LEFT);
713 alignment |= ALIGN_CENTER;
714 }
715 dup_ct = 0;
716 if (alignment & ALIGN_TOP) ++dup_ct;
717 if (alignment & ALIGN_BOTTOM) ++dup_ct;
718 if (alignment & ALIGN_VCENTER) ++dup_ct;
719 if (1 < dup_ct) { // when multiples are picked, use ALIGN_VCENTER by default
720 alignment &= ~(ALIGN_TOP | ALIGN_BOTTOM);
721 alignment |= ALIGN_VCENTER;
722 }
723
724 // get rid of any irrelevant bits
725 if (!(alignment & (ALIGN_LEFT | ALIGN_RIGHT | ALIGN_CENTER | ALIGN_TOP | ALIGN_BOTTOM | ALIGN_VCENTER)))
726 alignment = ALIGN_NONE;
727 }
728
RedoLayout()729 void Layout::RedoLayout()
730 {
731 //Bug: This does nothing if the size has not changed. Fixing it to
732 //use Layout::SizeMove breaks all text boxes.
733 Resize(Size());
734 }
735
ChildSizeOrMinSizeChanged()736 void Layout::ChildSizeOrMinSizeChanged()
737 {
738 if (!m_ignore_child_resize)
739 RedoLayout();
740 }
741