1 /*
2 Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16
17 #include "gui/widgets/grid_private.hpp"
18
19 #include "gui/auxiliary/iterator/walker_grid.hpp"
20 #include "gui/core/event/message.hpp"
21 #include "gui/core/log.hpp"
22 #include "gui/core/layout_exception.hpp"
23 #include "gui/widgets/styled_widget.hpp"
24 #include "gui/widgets/window.hpp"
25
26 #include <numeric>
27
28 #define LOG_SCOPE_HEADER "grid [" + id() + "] " + __func__
29 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
30 #define LOG_IMPL_HEADER "grid [" + grid.id() + "] " + __func__ + ':'
31
32 #define LOG_CHILD_SCOPE_HEADER \
33 "grid::child [" + (widget_ ? widget_->id() : "-") + "] " + __func__
34 #define LOG_CHILD_HEADER LOG_CHILD_SCOPE_HEADER + ':'
35
36 namespace gui2
37 {
38
grid(const unsigned rows,const unsigned cols)39 grid::grid(const unsigned rows, const unsigned cols)
40 : rows_(rows)
41 , cols_(cols)
42 , row_height_()
43 , col_width_()
44 , row_grow_factor_(rows)
45 , col_grow_factor_(cols)
46 , children_(rows * cols)
47 {
48 connect_signal<event::REQUEST_PLACEMENT>(
49 std::bind(&grid::request_placement, this,
50 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
51 event::dispatcher::back_pre_child);
52 }
53
~grid()54 grid::~grid()
55 {
56 }
57
add_row(const unsigned count)58 unsigned grid::add_row(const unsigned count)
59 {
60 assert(count);
61
62 // FIXME the warning in set_rows_cols should be killed.
63
64 unsigned result = rows_;
65 set_rows_cols(rows_ + count, cols_);
66 return result;
67 }
68
set_child(widget * widget,const unsigned row,const unsigned col,const unsigned flags,const unsigned border_size)69 void grid::set_child(widget* widget,
70 const unsigned row,
71 const unsigned col,
72 const unsigned flags,
73 const unsigned border_size)
74 {
75 assert(row < rows_ && col < cols_);
76 assert(flags & VERTICAL_MASK);
77 assert(flags & HORIZONTAL_MASK);
78
79 child& cell = get_child(row, col);
80
81 // clear old child if any
82 if(cell.get_widget()) {
83 // free a child when overwriting it
84 WRN_GUI_G << LOG_HEADER << " child '" << cell.id() << "' at cell '"
85 << row << ',' << col << "' will be replaced.\n";
86 }
87
88 // copy data
89 cell.set_flags(flags);
90 cell.set_border_size(border_size);
91 cell.set_widget(widget);
92 if(cell.get_widget()) {
93 // make sure the new child is valid before deferring
94 cell.get_widget()->set_parent(this);
95 }
96 }
97
swap_child(const std::string & id,widget * w,const bool recurse,widget * new_parent)98 std::unique_ptr<widget> grid::swap_child(const std::string& id,
99 widget* w,
100 const bool recurse,
101 widget* new_parent)
102 {
103 assert(w);
104
105 for(auto & child : children_)
106 {
107 if(child.id() != id) {
108
109 if(recurse) {
110 // decent in the nested grids.
111 grid* g = dynamic_cast<grid*>(child.get_widget());
112 if(g) {
113
114 std::unique_ptr<widget> old = g->swap_child(id, w, true);
115 if(old) {
116 return old;
117 }
118 }
119 }
120
121 continue;
122 }
123
124 // Free widget from cell and validate.
125 std::unique_ptr<widget> old = child.free_widget();
126 assert(old);
127
128 old->set_parent(new_parent);
129
130 w->set_parent(this);
131 w->set_visible(old->get_visible());
132
133 child.set_widget(w);
134 return old;
135 }
136
137 return nullptr;
138 }
139
remove_child(const unsigned row,const unsigned col)140 void grid::remove_child(const unsigned row, const unsigned col)
141 {
142 assert(row < rows_ && col < cols_);
143
144 child& cell = get_child(row, col);
145 cell.set_widget(nullptr);
146 }
147
remove_child(const std::string & id,const bool find_all)148 void grid::remove_child(const std::string& id, const bool find_all)
149 {
150 for(auto & child : children_)
151 {
152 if(child.id() == id) {
153 child.set_widget(nullptr);
154
155 if(!find_all) {
156 break;
157 }
158 }
159 }
160 }
161
set_active(const bool active)162 void grid::set_active(const bool active)
163 {
164 for(auto & child : children_)
165 {
166
167 widget* widget = child.get_widget();
168 if(!widget) {
169 continue;
170 }
171
172 grid* g = dynamic_cast<grid*>(widget);
173 if(g) {
174 g->set_active(active);
175 continue;
176 }
177
178 styled_widget* control = dynamic_cast<styled_widget*>(widget);
179 if(control) {
180 control->set_active(active);
181 }
182 }
183 }
184
layout_initialize(const bool full_initialization)185 void grid::layout_initialize(const bool full_initialization)
186 {
187 // Inherited.
188 widget::layout_initialize(full_initialization);
189
190 // Clear child caches.
191 for(auto & child : children_)
192 {
193
194 child.layout_initialize(full_initialization);
195 }
196 }
197
reduce_width(const unsigned maximum_width)198 void grid::reduce_width(const unsigned maximum_width)
199 {
200 /***** ***** ***** ***** INIT ***** ***** ***** *****/
201 log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
202 DBG_GUI_L << LOG_HEADER << " maximum width " << maximum_width << ".\n";
203
204 point size = get_best_size();
205 if(size.x <= static_cast<int>(maximum_width)) {
206 DBG_GUI_L << LOG_HEADER << " Already fits.\n";
207 return;
208 }
209
210 /***** ***** ***** ***** Request resize ***** ***** ***** *****/
211
212 request_reduce_width(maximum_width);
213
214 size = get_best_size();
215 if(size.x <= static_cast<int>(maximum_width)) {
216 DBG_GUI_L << LOG_HEADER << " Resize request honored.\n";
217 return;
218 }
219
220 /***** ***** ***** ***** Demand resize ***** ***** ***** *****/
221
222 /** @todo Implement. */
223
224 /***** ***** ***** ***** Acknowledge failure ***** ***** ***** *****/
225
226 DBG_GUI_L << LOG_HEADER << " Resizing failed.\n";
227
228 throw layout_exception_width_resize_failed();
229 }
230
request_reduce_width(const unsigned maximum_width)231 void grid::request_reduce_width(const unsigned maximum_width)
232 {
233 point size = get_best_size();
234 if(size.x <= static_cast<int>(maximum_width)) {
235 /** @todo this point shouldn't be reached, find out why it does. */
236 return;
237 }
238
239 const unsigned too_wide = size.x - maximum_width;
240 unsigned reduced = 0;
241 for(size_t col = 0; col < cols_; ++col) {
242 if(too_wide - reduced >= col_width_[col]) {
243 DBG_GUI_L << LOG_HEADER << " column " << col
244 << " is too small to be reduced.\n";
245 continue;
246 }
247
248 const unsigned wanted_width = col_width_[col] - (too_wide - reduced);
249 const unsigned width
250 = grid_implementation::column_request_reduce_width(
251 *this, col, wanted_width);
252
253 if(width < col_width_[col]) {
254 unsigned reduction = col_width_[col] - width;
255
256 DBG_GUI_L << LOG_HEADER << " reduced " << reduction
257 << " pixels for column " << col << ".\n";
258
259 size.x -= reduction;
260 reduced += reduction;
261 }
262
263 if(size.x <= static_cast<int>(maximum_width)) {
264 break;
265 }
266 }
267
268 set_layout_size(calculate_best_size());
269 }
270
demand_reduce_width(const unsigned)271 void grid::demand_reduce_width(const unsigned /*maximum_width*/)
272 {
273 /** @todo Implement. */
274 }
275
reduce_height(const unsigned maximum_height)276 void grid::reduce_height(const unsigned maximum_height)
277 {
278 /***** ***** ***** ***** INIT ***** ***** ***** *****/
279 log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
280 DBG_GUI_L << LOG_HEADER << " maximum height " << maximum_height << ".\n";
281
282 point size = get_best_size();
283 if(size.y <= static_cast<int>(maximum_height)) {
284 DBG_GUI_L << LOG_HEADER << " Already fits.\n";
285 return;
286 }
287
288 /***** ***** ***** ***** Request resize ***** ***** ***** *****/
289
290 request_reduce_height(maximum_height);
291
292 size = get_best_size();
293 if(size.y <= static_cast<int>(maximum_height)) {
294 DBG_GUI_L << LOG_HEADER << " Resize request honored.\n";
295 return;
296 }
297
298 /***** ***** ***** ***** Demand resize ***** ***** ***** *****/
299
300 /** @todo Implement. */
301
302 /***** ***** ***** ***** Acknowledge failure ***** ***** ***** *****/
303
304 DBG_GUI_L << LOG_HEADER << " Resizing failed.\n";
305
306 throw layout_exception_height_resize_failed();
307 }
308
request_reduce_height(const unsigned maximum_height)309 void grid::request_reduce_height(const unsigned maximum_height)
310 {
311 point size = get_best_size();
312 if(size.y <= static_cast<int>(maximum_height)) {
313 /** @todo this point shouldn't be reached, find out why it does. */
314 return;
315 }
316
317 const unsigned too_high = size.y - maximum_height;
318 unsigned reduced = 0;
319 for(size_t row = 0; row < rows_; ++row) {
320 unsigned wanted_height = row_height_[row] - (too_high - reduced);
321 /**
322 * @todo Improve this code.
323 *
324 * Now we try every item to be reduced, maybe items need a flag whether
325 * or not to try to reduce and also evaluate whether the force
326 * reduction is still needed.
327 */
328 if(too_high - reduced >= row_height_[row]) {
329 DBG_GUI_L << LOG_HEADER << " row " << row << " height "
330 << row_height_[row] << " want to reduce " << too_high
331 << " is too small to be reduced fully try 1 pixel.\n";
332
333 wanted_height = 1;
334 }
335
336 /* Reducing the height of a widget causes the widget to save its new size
337 in widget::layout_size_. After that, get_best_size() will return that
338 size and not the originally calculated optimal size.
339 Thus, it's perfectly correct that grid::calculate_best_size() that we
340 call later calls get_best_size() for child widgets as if size reduction
341 had never happened. */
342 const unsigned height = grid_implementation::row_request_reduce_height(
343 *this, row, wanted_height);
344
345 if(height < row_height_[row]) {
346 unsigned reduction = row_height_[row] - height;
347
348 DBG_GUI_L << LOG_HEADER << " row " << row << " height "
349 << row_height_[row] << " want to reduce " << too_high
350 << " reduced " << reduction << " pixels.\n";
351
352 size.y -= reduction;
353 reduced += reduction;
354 }
355
356 if(size.y <= static_cast<int>(maximum_height)) {
357 break;
358 }
359 }
360
361 size = calculate_best_size();
362
363 DBG_GUI_L << LOG_HEADER << " Requested maximum " << maximum_height
364 << " resulting height " << size.y << ".\n";
365
366 set_layout_size(size);
367 }
368
demand_reduce_height(const unsigned)369 void grid::demand_reduce_height(const unsigned /*maximum_height*/)
370 {
371 /** @todo Implement. */
372 }
373
request_placement(dispatcher &,const event::ui_event,bool & handled,bool &)374 void grid::request_placement(dispatcher&, const event::ui_event, bool& handled, bool&)
375 {
376 if (get_window()->invalidate_layout_blocked()) {
377 handled = true;
378 return;
379 }
380
381 point size = get_size();
382 point best_size = calculate_best_size();
383 if(size.x >= best_size.x && size.y >= best_size.y) {
384 place(get_origin(), size);
385 handled = true;
386 return;
387 }
388
389 recalculate_best_size();
390
391 if(size.y >= best_size.y) {
392 // We have enough space in the Y direction, but not in the X direction.
393 // Try wrapping the content.
394 request_reduce_width(size.x);
395 best_size = get_best_size();
396
397 if(size.x >= best_size.x && size.y >= best_size.y) {
398 // Wrapping succeeded, we still fit vertically.
399 place(get_origin(), size);
400 handled = true;
401 return;
402 } else {
403 // Wrapping failed, we no longer fit.
404 // Reset the sizes of child widgets.
405 layout_initialize(true);
406 }
407 }
408
409 /*
410 Not enough space.
411 Let the event flow higher up.
412 This is a pre-event handler, so the event flows upwards. */
413 }
414
recalculate_best_size()415 point grid::recalculate_best_size()
416 {
417 point best_size = calculate_best_size();
418 set_layout_size(best_size);
419 return best_size;
420 }
421
calculate_best_size() const422 point grid::calculate_best_size() const
423 {
424 log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
425
426 // Reset the cached values.
427 row_height_.clear();
428 row_height_.resize(rows_, 0);
429 col_width_.clear();
430 col_width_.resize(cols_, 0);
431
432 // First get the sizes for all items.
433 for(unsigned row = 0; row < rows_; ++row) {
434 for(unsigned col = 0; col < cols_; ++col) {
435
436 const point size = get_child(row, col).get_best_size();
437
438 if(size.x > static_cast<int>(col_width_[col])) {
439 col_width_[col] = size.x;
440 }
441
442 if(size.y > static_cast<int>(row_height_[row])) {
443 row_height_[row] = size.y;
444 }
445 }
446 }
447
448 for(unsigned row = 0; row < rows_; ++row) {
449 DBG_GUI_L << LOG_HEADER << " the row_height_ for row " << row
450 << " will be " << row_height_[row] << ".\n";
451 }
452
453 for(unsigned col = 0; col < cols_; ++col) {
454 DBG_GUI_L << LOG_HEADER << " the col_width_ for column " << col
455 << " will be " << col_width_[col] << ".\n";
456 }
457
458 const point result(
459 std::accumulate(col_width_.begin(), col_width_.end(), 0),
460 std::accumulate(row_height_.begin(), row_height_.end(), 0));
461
462 DBG_GUI_L << LOG_HEADER << " returning " << result << ".\n";
463 return result;
464 }
465
can_wrap() const466 bool grid::can_wrap() const
467 {
468 for(const auto & child : children_)
469 {
470 if(child.can_wrap()) {
471 return true;
472 }
473 }
474
475 // Inherited.
476 return widget::can_wrap();
477 }
478
place(const point & origin,const point & size)479 void grid::place(const point& origin, const point& size)
480 {
481 log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
482
483 /***** INIT *****/
484
485 widget::place(origin, size);
486
487 if(!rows_ || !cols_) {
488 return;
489 }
490
491 // call the calculate so the size cache gets updated.
492 const point best_size = calculate_best_size();
493
494 assert(row_height_.size() == rows_);
495 assert(col_width_.size() == cols_);
496 assert(row_grow_factor_.size() == rows_);
497 assert(col_grow_factor_.size() == cols_);
498
499 DBG_GUI_L << LOG_HEADER << " best size " << best_size << " available size "
500 << size << ".\n";
501
502 /***** BEST_SIZE *****/
503
504 if(best_size == size) {
505 layout(origin);
506 return;
507 }
508
509 if(best_size.x > size.x || best_size.y > size.y) {
510 // The assertion below fails quite often so try to give as much information as possible.
511 std::stringstream out;
512 out << " Failed to place a grid, we have " << size << " space but we need " << best_size << " space.";
513 out << " This happened at a grid with the id '" << id() << "'";
514 widget* pw = parent();
515 while(pw != nullptr) {
516 out << " in a '" << typeid(*pw).name() << "' with the id '" << pw->id() << "'";
517 pw = pw->parent();
518 }
519 ERR_GUI_L << LOG_HEADER << out.str() << ".\n";
520
521 return;
522 }
523
524 /***** GROW *****/
525
526 // expand it.
527 if(size.x > best_size.x) {
528 const unsigned w = size.x - best_size.x;
529 unsigned w_size = std::accumulate(
530 col_grow_factor_.begin(), col_grow_factor_.end(), 0);
531
532 DBG_GUI_L << LOG_HEADER << " extra width " << w
533 << " will be divided amount " << w_size << " units in "
534 << cols_ << " columns.\n";
535
536 if(w_size == 0) {
537 // If all sizes are 0 reset them to 1
538 for(auto & val : col_grow_factor_)
539 {
540 val = 1;
541 }
542 w_size = cols_;
543 }
544 // We might have a bit 'extra' if the division doesn't fix exactly
545 // but we ignore that part for now.
546 const unsigned w_normal = w / w_size;
547 for(unsigned i = 0; i < cols_; ++i) {
548 col_width_[i] += w_normal * col_grow_factor_[i];
549 DBG_GUI_L << LOG_HEADER << " column " << i
550 << " with grow factor " << col_grow_factor_[i]
551 << " set width to " << col_width_[i] << ".\n";
552 }
553 }
554
555 if(size.y > best_size.y) {
556 const unsigned h = size.y - best_size.y;
557 unsigned h_size = std::accumulate(
558 row_grow_factor_.begin(), row_grow_factor_.end(), 0);
559 DBG_GUI_L << LOG_HEADER << " extra height " << h
560 << " will be divided amount " << h_size << " units in "
561 << rows_ << " rows.\n";
562
563 if(h_size == 0) {
564 // If all sizes are 0 reset them to 1
565 for(auto & val : row_grow_factor_)
566 {
567 val = 1;
568 }
569 h_size = rows_;
570 }
571 // We might have a bit 'extra' if the division doesn't fix exactly
572 // but we ignore that part for now.
573 const unsigned h_normal = h / h_size;
574 for(unsigned i = 0; i < rows_; ++i) {
575 row_height_[i] += h_normal * row_grow_factor_[i];
576 DBG_GUI_L << LOG_HEADER << " row " << i << " with grow factor "
577 << row_grow_factor_[i] << " set height to "
578 << row_height_[i] << ".\n";
579 }
580 }
581
582 layout(origin);
583 return;
584 }
585
set_origin(const point & origin)586 void grid::set_origin(const point& origin)
587 {
588 const point movement {origin.x - get_x(), origin.y - get_y()};
589
590 // Inherited.
591 widget::set_origin(origin);
592
593 for(auto & child : children_)
594 {
595
596 widget* widget = child.get_widget();
597 assert(widget);
598
599 widget->set_origin(point(widget->get_x() + movement.x, widget->get_y() + movement.y));
600 }
601 }
602
set_visible_rectangle(const SDL_Rect & rectangle)603 void grid::set_visible_rectangle(const SDL_Rect& rectangle)
604 {
605 // Inherited.
606 widget::set_visible_rectangle(rectangle);
607
608 for(auto & child : children_)
609 {
610
611 widget* widget = child.get_widget();
612 assert(widget);
613
614 widget->set_visible_rectangle(rectangle);
615 }
616 }
617
layout_children()618 void grid::layout_children()
619 {
620 for(auto & child : children_)
621 {
622 assert(child.get_widget());
623 child.get_widget()->layout_children();
624 }
625 }
626
child_populate_dirty_list(window & caller,const std::vector<widget * > & call_stack)627 void grid::child_populate_dirty_list(window& caller,
628 const std::vector<widget*>& call_stack)
629 {
630 assert(!call_stack.empty() && call_stack.back() == this);
631
632 for(auto & child : children_)
633 {
634
635 assert(child.get_widget());
636
637 std::vector<widget*> child_call_stack = call_stack;
638 child.get_widget()->populate_dirty_list(caller, child_call_stack);
639 }
640 }
641
find_at(const point & coordinate,const bool must_be_active)642 widget* grid::find_at(const point& coordinate, const bool must_be_active)
643 {
644 return grid_implementation::find_at<widget>(
645 *this, coordinate, must_be_active);
646 }
647
find_at(const point & coordinate,const bool must_be_active) const648 const widget* grid::find_at(const point& coordinate,
649 const bool must_be_active) const
650 {
651 return grid_implementation::find_at<const widget>(
652 *this, coordinate, must_be_active);
653 }
654
find(const std::string & id,const bool must_be_active)655 widget* grid::find(const std::string& id, const bool must_be_active)
656 {
657 return grid_implementation::find<widget>(*this, id, must_be_active);
658 }
659
find(const std::string & id,const bool must_be_active) const660 const widget* grid::find(const std::string& id, const bool must_be_active)
661 const
662 {
663 return grid_implementation::find<const widget>(*this, id, must_be_active);
664 }
665
has_widget(const widget & widget) const666 bool grid::has_widget(const widget& widget) const
667 {
668 if(widget::has_widget(widget)) {
669 return true;
670 }
671
672 for(const auto & child : children_)
673 {
674 if(child.get_widget()->has_widget(widget)) {
675 return true;
676 }
677 }
678 return false;
679 }
680
disable_click_dismiss() const681 bool grid::disable_click_dismiss() const
682 {
683 if(get_visible() != widget::visibility::visible) {
684 return false;
685 }
686
687 for(const auto & child : children_)
688 {
689 const widget* widget = child.get_widget();
690 assert(widget);
691
692 if(widget->disable_click_dismiss()) {
693 return true;
694 }
695 }
696 return false;
697 }
698
create_walker()699 iteration::walker_base* grid::create_walker()
700 {
701 return new gui2::iteration::grid(*this);
702 }
703
set_rows(const unsigned rows)704 void grid::set_rows(const unsigned rows)
705 {
706 if(rows == rows_) {
707 return;
708 }
709
710 set_rows_cols(rows, cols_);
711 }
712
set_cols(const unsigned cols)713 void grid::set_cols(const unsigned cols)
714 {
715 if(cols == cols_) {
716 return;
717 }
718
719 set_rows_cols(rows_, cols);
720 }
721
set_rows_cols(const unsigned rows,const unsigned cols)722 void grid::set_rows_cols(const unsigned rows, const unsigned cols)
723 {
724 if(rows == rows_ && cols == cols_) {
725 return;
726 }
727
728 if(!children_.empty()) {
729 WRN_GUI_G << LOG_HEADER << " resizing a non-empty grid "
730 << " may give unexpected problems.\n";
731 }
732
733 rows_ = rows;
734 cols_ = cols;
735 row_grow_factor_.resize(rows);
736 col_grow_factor_.resize(cols);
737 children_.resize(rows_ * cols_);
738 }
739
get_best_size() const740 point grid::child::get_best_size() const
741 {
742 log_scope2(log_gui_layout, LOG_CHILD_SCOPE_HEADER)
743
744 if(!widget_) {
745 DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << false
746 << " returning " << border_space() << ".\n";
747 return border_space();
748 }
749
750 if(widget_->get_visible() == widget::visibility::invisible) {
751 DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << true
752 << " widget visible " << false << " returning 0,0"
753 << ".\n";
754 return point();
755 }
756
757 const point best_size = widget_->get_best_size() + border_space();
758
759 DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << true
760 << " widget visible " << true << " returning " << best_size
761 << ".\n";
762 return best_size;
763 }
764
place(point origin,point size)765 void grid::child::place(point origin, point size)
766 {
767 assert(get_widget());
768 if(get_widget()->get_visible() == widget::visibility::invisible) {
769 return;
770 }
771
772 if(border_size_) {
773 if(flags_ & BORDER_TOP) {
774 origin.y += border_size_;
775 size.y -= border_size_;
776 }
777 if(flags_ & BORDER_BOTTOM) {
778 size.y -= border_size_;
779 }
780
781 if(flags_ & BORDER_LEFT) {
782 origin.x += border_size_;
783 size.x -= border_size_;
784 }
785 if(flags_ & BORDER_RIGHT) {
786 size.x -= border_size_;
787 }
788 }
789
790 // If size smaller or equal to best size set that size.
791 // No need to check > min size since this is what we got.
792 const point best_size = get_widget()->get_best_size();
793 if(size <= best_size) {
794 DBG_GUI_L << LOG_CHILD_HEADER
795 << " in best size range setting widget to " << origin << " x "
796 << size << ".\n";
797
798 get_widget()->place(origin, size);
799 return;
800 }
801
802 const styled_widget* control = dynamic_cast<const styled_widget*>(get_widget());
803 const point maximum_size = control ? control->get_config_maximum_size()
804 : point();
805
806 if((flags_ & (HORIZONTAL_MASK | VERTICAL_MASK))
807 == (HORIZONTAL_GROW_SEND_TO_CLIENT | VERTICAL_GROW_SEND_TO_CLIENT)) {
808
809 if(maximum_size == point() || size <= maximum_size) {
810
811 DBG_GUI_L << LOG_CHILD_HEADER
812 << " in maximum size range setting widget to " << origin
813 << " x " << size << ".\n";
814
815 get_widget()->place(origin, size);
816 return;
817 }
818 }
819
820 point widget_size = point(std::min(size.x, best_size.x), std::min(size.y, best_size.y));
821 point widget_orig = origin;
822
823 const unsigned v_flag = flags_ & VERTICAL_MASK;
824
825 if(v_flag == VERTICAL_GROW_SEND_TO_CLIENT) {
826 if(maximum_size.y) {
827 widget_size.y = std::min(size.y, maximum_size.y);
828 } else {
829 widget_size.y = size.y;
830 }
831 DBG_GUI_L << LOG_CHILD_HEADER << " vertical growing from "
832 << best_size.y << " to " << widget_size.y << ".\n";
833
834 } else if(v_flag == VERTICAL_ALIGN_TOP) {
835 // Do nothing.
836
837 DBG_GUI_L << LOG_CHILD_HEADER << " vertically aligned at the top.\n";
838
839 } else if(v_flag == VERTICAL_ALIGN_CENTER) {
840
841 widget_orig.y += (size.y - widget_size.y) / 2;
842 DBG_GUI_L << LOG_CHILD_HEADER << " vertically centered.\n";
843
844 } else if(v_flag == VERTICAL_ALIGN_BOTTOM) {
845
846 widget_orig.y += (size.y - widget_size.y);
847 DBG_GUI_L << LOG_CHILD_HEADER << " vertically aligned at the bottom.\n";
848
849 } else {
850 ERR_GUI_L << LOG_CHILD_HEADER << " Invalid vertical alignment '"
851 << v_flag << "' specified.\n";
852 assert(false);
853 }
854
855 const unsigned h_flag = flags_ & HORIZONTAL_MASK;
856
857 if(h_flag == HORIZONTAL_GROW_SEND_TO_CLIENT) {
858 if(maximum_size.x) {
859 widget_size.x = std::min(size.x, maximum_size.x);
860 } else {
861 widget_size.x = size.x;
862 }
863 DBG_GUI_L << LOG_CHILD_HEADER << " horizontal growing from "
864 << best_size.x << " to " << widget_size.x << ".\n";
865
866 } else if(h_flag == HORIZONTAL_ALIGN_LEFT) {
867 // Do nothing.
868 DBG_GUI_L << LOG_CHILD_HEADER << " horizontally aligned at the left.\n";
869
870 } else if(h_flag == HORIZONTAL_ALIGN_CENTER) {
871
872 widget_orig.x += (size.x - widget_size.x) / 2;
873 DBG_GUI_L << LOG_CHILD_HEADER << " horizontally centered.\n";
874
875 } else if(h_flag == HORIZONTAL_ALIGN_RIGHT) {
876
877 widget_orig.x += (size.x - widget_size.x);
878 DBG_GUI_L << LOG_CHILD_HEADER
879 << " horizontally aligned at the right.\n";
880
881 } else {
882 ERR_GUI_L << LOG_CHILD_HEADER << " No horizontal alignment '" << h_flag
883 << "' specified.\n";
884 assert(false);
885 }
886
887 DBG_GUI_L << LOG_CHILD_HEADER << " resize widget to " << widget_orig
888 << " x " << widget_size << ".\n";
889
890 get_widget()->place(widget_orig, widget_size);
891 }
892
layout_initialize(const bool full_initialization)893 void grid::child::layout_initialize(const bool full_initialization)
894 {
895 assert(widget_);
896
897 if(widget_->get_visible() != widget::visibility::invisible) {
898 widget_->layout_initialize(full_initialization);
899 }
900 }
901
id() const902 const std::string& grid::child::id() const
903 {
904 assert(widget_);
905 return widget_->id();
906 }
907
border_space() const908 point grid::child::border_space() const
909 {
910 point result(0, 0);
911
912 if(border_size_) {
913
914 if(flags_ & BORDER_TOP)
915 result.y += border_size_;
916 if(flags_ & BORDER_BOTTOM)
917 result.y += border_size_;
918
919 if(flags_ & BORDER_LEFT)
920 result.x += border_size_;
921 if(flags_ & BORDER_RIGHT)
922 result.x += border_size_;
923 }
924
925 return result;
926 }
927
get_child(widget * w)928 grid::child* grid::get_child(widget* w)
929 {
930 if(!w) {
931 return nullptr;
932 }
933
934 for(auto& child : children_) {
935 if(w == child.get_widget()) {
936 return &child;
937 }
938 }
939
940 return nullptr;
941 }
942
set_child_alignment(widget * widget,unsigned set_flag,unsigned mode_mask)943 void grid::set_child_alignment(widget* widget, unsigned set_flag, unsigned mode_mask)
944 {
945 grid::child* cell = get_child(widget);
946 if(!cell) {
947 return;
948 }
949
950 unsigned flags = cell->get_flags();
951
952 if((flags & mode_mask) == HORIZONTAL_GROW_SEND_TO_CLIENT) {
953 ERR_GUI_G << "Cannot set horizontal alignment (grid cell specifies dynamic growth)" << std::endl;
954 return;
955 }
956
957 if((flags & mode_mask) == VERTICAL_GROW_SEND_TO_CLIENT) {
958 ERR_GUI_G << "Cannot set vertical alignment (grid cell specifies dynamic growth)" << std::endl;
959 return;
960 }
961
962 flags &= ~mode_mask;
963 flags |= set_flag;
964
965 cell->set_flags(flags);
966
967 event::message message;
968 fire(event::REQUEST_PLACEMENT, *this, message);
969 }
970
layout(const point & origin)971 void grid::layout(const point& origin)
972 {
973 point orig = origin;
974 for(unsigned row = 0; row < rows_; ++row) {
975 for(unsigned col = 0; col < cols_; ++col) {
976
977 const point size(col_width_[col], row_height_[row]);
978 DBG_GUI_L << LOG_HEADER << " set widget at " << row << ',' << col
979 << " at origin " << orig << " with size " << size
980 << ".\n";
981
982 if(get_child(row, col).get_widget()) {
983 get_child(row, col).place(orig, size);
984 }
985
986 orig.x += col_width_[col];
987 }
988 orig.y += row_height_[row];
989 orig.x = origin.x;
990 }
991 }
992
impl_draw_children(surface & frame_buffer,int x_offset,int y_offset)993 void grid::impl_draw_children(surface& frame_buffer, int x_offset, int y_offset)
994 {
995 /*
996 * The call to SDL_PumpEvents seems a bit like black magic.
997 * With the call the resizing doesn't seem to lose resize events.
998 * But when added the queue still only contains one resize event and the
999 * internal SDL queue doesn't seem to overflow (rarely more than 50 pending
1000 * events).
1001 * Without the call when resizing larger a black area of remains, this is
1002 * the area not used for resizing the screen, this call `fixes' that.
1003 */
1004
1005 assert(get_visible() == widget::visibility::visible);
1006 set_is_dirty(false);
1007
1008 for(auto & child : children_)
1009 {
1010
1011 widget* widget = child.get_widget();
1012 assert(widget);
1013
1014 if(widget->get_visible() != widget::visibility::visible) {
1015 continue;
1016 }
1017
1018 if(widget->get_drawing_action() == widget::redraw_action::none) {
1019 continue;
1020 }
1021
1022 widget->draw_background(frame_buffer, x_offset, y_offset);
1023 widget->draw_children(frame_buffer, x_offset, y_offset);
1024 widget->draw_foreground(frame_buffer, x_offset, y_offset);
1025 widget->set_is_dirty(false);
1026 }
1027 }
1028
row_request_reduce_height(grid & grid,const unsigned row,const unsigned maximum_height)1029 unsigned grid_implementation::row_request_reduce_height(
1030 grid& grid, const unsigned row, const unsigned maximum_height)
1031 {
1032 // The minimum height required.
1033 unsigned required_height = 0;
1034
1035 for(size_t x = 0; x < grid.cols_; ++x) {
1036 grid::child& cell = grid.get_child(row, x);
1037 cell_request_reduce_height(cell, maximum_height);
1038
1039 const point size(cell.get_best_size());
1040
1041 if(required_height == 0 || static_cast<size_t>(size.y)
1042 > required_height) {
1043
1044 required_height = size.y;
1045 }
1046 }
1047
1048 DBG_GUI_L << LOG_IMPL_HEADER << " maximum row height " << maximum_height
1049 << " returning " << required_height << ".\n";
1050
1051 return required_height;
1052 }
1053
column_request_reduce_width(grid & grid,const unsigned column,const unsigned maximum_width)1054 unsigned grid_implementation::column_request_reduce_width(
1055 grid& grid, const unsigned column, const unsigned maximum_width)
1056 {
1057 // The minimum width required.
1058 unsigned required_width = 0;
1059
1060 for(size_t y = 0; y < grid.rows_; ++y) {
1061 grid::child& cell = grid.get_child(y, column);
1062 cell_request_reduce_width(cell, maximum_width);
1063
1064 const point size(cell.get_best_size());
1065
1066 if(required_width == 0 || static_cast<size_t>(size.x)
1067 > required_width) {
1068
1069 required_width = size.x;
1070 }
1071 }
1072
1073 DBG_GUI_L << LOG_IMPL_HEADER << " maximum column width " << maximum_width
1074 << " returning " << required_width << ".\n";
1075
1076 return required_width;
1077 }
1078
1079 void
cell_request_reduce_height(grid::child & child,const unsigned maximum_height)1080 grid_implementation::cell_request_reduce_height(grid::child& child,
1081 const unsigned maximum_height)
1082 {
1083 assert(child.widget_);
1084
1085 if(child.widget_->get_visible() == widget::visibility::invisible) {
1086 return;
1087 }
1088
1089 child.widget_->request_reduce_height(maximum_height
1090 - child.border_space().y);
1091 }
1092
1093 void
cell_request_reduce_width(grid::child & child,const unsigned maximum_width)1094 grid_implementation::cell_request_reduce_width(grid::child& child,
1095 const unsigned maximum_width)
1096 {
1097 assert(child.widget_);
1098
1099 if(child.widget_->get_visible() == widget::visibility::invisible) {
1100 return;
1101 }
1102
1103 child.widget_->request_reduce_width(maximum_width - child.border_space().x);
1104 }
1105
set_single_child(grid & grid,widget * widget)1106 void set_single_child(grid& grid, widget* widget)
1107 {
1108 grid.set_rows_cols(1, 1);
1109 grid.set_child(widget,
1110 0,
1111 0,
1112 grid::HORIZONTAL_GROW_SEND_TO_CLIENT
1113 | grid::VERTICAL_GROW_SEND_TO_CLIENT,
1114 0);
1115 }
1116
1117 } // namespace gui2
1118
1119
1120 /*WIKI
1121 * @page = GUILayout
1122 *
1123 * {{Autogenerated}}
1124 *
1125 * = Abstract =
1126 *
1127 * In the widget library the placement and sizes of elements is determined by
1128 * a grid. Therefore most widgets have no fixed size.
1129 *
1130 *
1131 * = Theory =
1132 *
1133 * We have two examples for the addon dialog, the first example the lower
1134 * buttons are in one grid, that means if the remove button gets wider
1135 * (due to translations) the connect button (4.1 - 2.2) will be aligned
1136 * to the left of the remove button. In the second example the connect
1137 * button will be partial underneath the remove button.
1138 *
1139 * A grid exists of x rows and y columns for all rows the number of columns
1140 * needs to be the same, there is no column (nor row) span. If spanning is
1141 * required place a nested grid to do so. In the examples every row has 1 column
1142 * but rows 3, 4 (and in the second 5) have a nested grid to add more elements
1143 * per row.
1144 *
1145 * In the grid every cell needs to have a widget, if no widget is wanted place
1146 * the special widget ''spacer''. This is a non-visible item which normally
1147 * shouldn't have a size. It is possible to give a spacer a size as well but
1148 * that is discussed elsewhere.
1149 *
1150 * Every row and column has a ''grow_factor'', since all columns in a grid are
1151 * aligned only the columns in the first row need to define their grow factor.
1152 * The grow factor is used to determine with the extra size available in a
1153 * dialog. The algorithm determines the extra size work like this:
1154 *
1155 * * determine the extra size
1156 * * determine the sum of the grow factors
1157 * * if this sum is 0 set the grow factor for every item to 1 and sum to sum of items.
1158 * * divide the extra size with the sum of grow factors
1159 * * for every item multiply the grow factor with the division value
1160 *
1161 * eg
1162 * extra size 100
1163 * grow factors 1, 1, 2, 1
1164 * sum 5
1165 * division 100 / 5 = 20
1166 * extra sizes 20, 20, 40, 20
1167 *
1168 * Since we force the factors to 1 if all zero it's not possible to have non
1169 * growing cells. This can be solved by adding an extra cell with a spacer and a
1170 * grow factor of 1. This is used for the buttons in the examples.
1171 *
1172 * Every cell has a ''border_size'' and ''border'' the ''border_size'' is the
1173 * number of pixels in the cell which aren't available for the widget. This is
1174 * used to make sure the items in different cells aren't put side to side. With
1175 * ''border'' it can be determined which sides get the border. So a border is
1176 * either 0 or ''border_size''.
1177 *
1178 * If the widget doesn't grow when there's more space available the alignment
1179 * determines where in the cell the widget is placed.
1180 *
1181 * == Examples ==
1182 *
1183 * |---------------------------------------|
1184 * | 1.1 |
1185 * |---------------------------------------|
1186 * | 2.1 |
1187 * |---------------------------------------|
1188 * | |-----------------------------------| |
1189 * | | 3.1 - 1.1 | 3.1 - 1.2 | |
1190 * | |-----------------------------------| |
1191 * |---------------------------------------|
1192 * | |-----------------------------------| |
1193 * | | 4.1 - 1.1 | 4.1 - 1.2 | 4.1 - 1.3 | |
1194 * | |-----------------------------------| |
1195 * | | 4.1 - 2.1 | 4.1 - 2.2 | 4.1 - 2.3 | |
1196 * | |-----------------------------------| |
1197 * |---------------------------------------|
1198 *
1199 *
1200 * 1.1 label : title
1201 * 2.1 label : description
1202 * 3.1 - 1.1 label : server
1203 * 3.1 - 1.2 text box : server to connect to
1204 * 4.1 - 1.1 spacer
1205 * 4.1 - 1.2 spacer
1206 * 4.1 - 1.3 button : remove addon
1207 * 4.1 - 2.1 spacer
1208 * 4.1 - 2.2 button : connect
1209 * 4.1 - 2.3 button : cancel
1210 *
1211 *
1212 * |---------------------------------------|
1213 * | 1.1 |
1214 * |---------------------------------------|
1215 * | 2.1 |
1216 * |---------------------------------------|
1217 * | |-----------------------------------| |
1218 * | | 3.1 - 1.1 | 3.1 - 1.2 | |
1219 * | |-----------------------------------| |
1220 * |---------------------------------------|
1221 * | |-----------------------------------| |
1222 * | | 4.1 - 1.1 | 4.1 - 1.2 | |
1223 * | |-----------------------------------| |
1224 * |---------------------------------------|
1225 * | |-----------------------------------| |
1226 * | | 5.1 - 1.1 | 5.1 - 1.2 | 5.1 - 2.3 | |
1227 * | |-----------------------------------| |
1228 * |---------------------------------------|
1229 *
1230 *
1231 * 1.1 label : title
1232 * 2.1 label : description
1233 * 3.1 - 1.1 label : server
1234 * 3.1 - 1.2 text box : server to connect to
1235 * 4.1 - 1.1 spacer
1236 * 4.1 - 1.2 button : remove addon
1237 * 5.1 - 1.1 spacer
1238 * 5.1 - 1.2 button : connect
1239 * 5.1 - 1.3 button : cancel
1240 *
1241 * = Praxis =
1242 *
1243 * This is the code needed to create the skeleton for the structure the extra
1244 * flags are omitted.
1245 *
1246 * [grid]
1247 * [row]
1248 * [column]
1249 * [label]
1250 * # 1.1
1251 * [/label]
1252 * [/column]
1253 * [/row]
1254 * [row]
1255 * [column]
1256 * [label]
1257 * # 2.1
1258 * [/label]
1259 * [/column]
1260 * [/row]
1261 * [row]
1262 * [column]
1263 * [grid]
1264 * [row]
1265 * [column]
1266 * [label]
1267 * # 3.1 - 1.1
1268 * [/label]
1269 * [/column]
1270 * [column]
1271 * [text_box]
1272 * # 3.1 - 1.2
1273 * [/text_box]
1274 * [/column]
1275 * [/row]
1276 * [/grid]
1277 * [/column]
1278 * [/row]
1279 * [row]
1280 * [column]
1281 * [grid]
1282 * [row]
1283 * [column]
1284 * [spacer]
1285 * # 4.1 - 1.1
1286 * [/spacer]
1287 * [/column]
1288 * [column]
1289 * [spacer]
1290 * # 4.1 - 1.2
1291 * [/spacer]
1292 * [/column]
1293 * [column]
1294 * [button]
1295 * # 4.1 - 1.3
1296 * [/button]
1297 * [/column]
1298 * [/row]
1299 * [row]
1300 * [column]
1301 * [spacer]
1302 * # 4.1 - 2.1
1303 * [/spacer]
1304 * [/column]
1305 * [column]
1306 * [button]
1307 * # 4.1 - 2.2
1308 * [/button]
1309 * [/column]
1310 * [column]
1311 * [button]
1312 * # 4.1 - 2.3
1313 * [/button]
1314 * [/column]
1315 * [/row]
1316 * [/grid]
1317 * [/column]
1318 * [/row]
1319 * [/grid]
1320 *
1321 *
1322 * [[Category: WML Reference]]
1323 * [[Category: GUI WML Reference]]
1324 */
1325