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