1 /*
2 Copyright (C) 2007 - 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.hpp"
18 #include "gui/widgets/settings.hpp"
19 #include "gui/widgets/window.hpp"
20 #include "gui/core/event/message.hpp"
21 #include "gui/core/log.hpp"
22 #include "gui/core/window_builder/helper.hpp"
23 #include "sdl/rect.hpp"
24
25 namespace gui2
26 {
27
28 /***** ***** ***** Constructor and destructor. ***** ***** *****/
29
widget()30 widget::widget()
31 : id_("")
32 , parent_(nullptr)
33 , x_(-1)
34 , y_(-1)
35 , width_(0)
36 , height_(0)
37 , layout_size_()
38 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
39 , last_best_size_()
40 #endif
41 , linked_group_()
42 , is_dirty_(true)
43 , visible_(visibility::visible)
44 , redraw_action_(redraw_action::full)
45 , clipping_rectangle_()
46 , debug_border_mode_(0)
47 , debug_border_color_(0,0,0,0)
48 {
49 DBG_GUI_LF << "widget create: " << static_cast<void*>(this) << "\n";
50 }
51
widget(const builder_widget & builder)52 widget::widget(const builder_widget& builder)
53 : id_(builder.id)
54 , parent_(nullptr)
55 , x_(-1)
56 , y_(-1)
57 , width_(0)
58 , height_(0)
59 , layout_size_()
60 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
61 , last_best_size_()
62 #endif
63 , linked_group_(builder.linked_group)
64 , is_dirty_(true)
65 , visible_(visibility::visible)
66 , redraw_action_(redraw_action::full)
67 , clipping_rectangle_()
68 , debug_border_mode_(builder.debug_border_mode)
69 , debug_border_color_(builder.debug_border_color)
70 {
71 DBG_GUI_LF << "widget create: " << static_cast<void*>(this) << "\n";
72 }
73
~widget()74 widget::~widget()
75 {
76 DBG_GUI_LF
77 << "widget destroy: " << static_cast<void*>(this) << " (id: " << id_
78 << ")\n";
79
80 widget* p = parent();
81 while(p) {
82 fire(event::NOTIFY_REMOVAL, *p, nullptr);
83 p = p->parent();
84 }
85
86 if(!linked_group_.empty()) {
87 if(window* window = get_window()) {
88 window->remove_linked_widget(linked_group_, this);
89 }
90 }
91 }
92
93 /***** ***** ***** ***** ID functions. ***** ***** ***** *****/
94
set_id(const std::string & id)95 void widget::set_id(const std::string& id)
96 {
97 styled_widget* this_ctrl = dynamic_cast<styled_widget*>(this);
98
99 DBG_GUI_LF
100 << "set id of " << static_cast<void*>(this) << " to '" << id << "' "
101 << "(was '" << id_ << "'). Widget type: "
102 << (this_ctrl ? this_ctrl->get_control_type() : typeid(widget).name()) << "\n";
103
104 id_ = id;
105 }
106
id() const107 const std::string& widget::id() const
108 {
109 return id_;
110 }
111
112 /***** ***** ***** ***** Parent functions ***** ***** ***** *****/
113
get_window()114 window* widget::get_window()
115 {
116 // Go up into the parent tree until we find the top level
117 // parent, we can also be the toplevel so start with
118 // ourselves instead of our parent.
119 widget* result = this;
120 while(result->parent_) {
121 result = result->parent_;
122 }
123
124 // on error dynamic_cast returns nullptr which is what we want.
125 return dynamic_cast<window*>(result);
126 }
127
get_window() const128 const window* widget::get_window() const
129 {
130 // Go up into the parent tree until we find the top level
131 // parent, we can also be the toplevel so start with
132 // ourselves instead of our parent.
133 const widget* result = this;
134 while(result->parent_) {
135 result = result->parent_;
136 }
137
138 // on error dynamic_cast returns nullptr which is what we want.
139 return dynamic_cast<const window*>(result);
140 }
141
get_parent_grid()142 grid* widget::get_parent_grid()
143 {
144 widget* result = parent_;
145 while(result && dynamic_cast<grid*>(result) == nullptr) {
146 result = result->parent_;
147 }
148
149 return result ? dynamic_cast<grid*>(result) : nullptr;
150 }
151
set_parent(widget * parent)152 void widget::set_parent(widget* parent)
153 {
154 parent_ = parent;
155 }
156
parent()157 widget* widget::parent()
158 {
159 return parent_;
160 }
161
162 /***** ***** ***** ***** Size and layout functions. ***** ***** ***** *****/
163
layout_initialize(const bool)164 void widget::layout_initialize(const bool /*full_initialization*/)
165 {
166 assert(visible_ != visibility::invisible);
167 assert(get_window());
168
169 layout_size_ = point();
170 if(!linked_group_.empty()) {
171 get_window()->add_linked_widget(linked_group_, this);
172 }
173 }
174
demand_reduce_width(const unsigned)175 void widget::demand_reduce_width(const unsigned /*maximum_width*/)
176 {
177 /* DO NOTHING */
178 }
179
request_reduce_height(const unsigned)180 void widget::request_reduce_height(const unsigned /*maximum_height*/)
181 {
182 /* DO NOTHING */
183 }
184
demand_reduce_height(const unsigned)185 void widget::demand_reduce_height(const unsigned /*maximum_height*/)
186 {
187 /* DO NOTHING */
188 }
189
get_best_size() const190 point widget::get_best_size() const
191 {
192 assert(visible_ != visibility::invisible);
193
194 point result = layout_size_;
195 if(result == point()) {
196 result = calculate_best_size();
197 //Adjust to linked widget size if linked widget size was already calculated.
198 if(!get_window()->get_need_layout() && !linked_group_.empty())
199 {
200 point linked_size = get_window()->get_linked_size(linked_group_);
201 result.x = std::max(result.x, linked_size.x);
202 result.y = std::max(result.y, linked_size.y);
203 }
204 }
205
206 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
207 last_best_size_ = result;
208 #endif
209
210 return result;
211 }
212
can_wrap() const213 bool widget::can_wrap() const
214 {
215 return false;
216 }
217
set_origin(const point & origin)218 void widget::set_origin(const point& origin)
219 {
220 x_ = origin.x;
221 y_ = origin.y;
222 }
223
set_size(const point & size)224 void widget::set_size(const point& size)
225 {
226 assert(size.x >= 0);
227 assert(size.y >= 0);
228
229 width_ = size.x;
230 height_ = size.y;
231
232 set_is_dirty(true);
233 }
234
place(const point & origin,const point & size)235 void widget::place(const point& origin, const point& size)
236 {
237 assert(size.x >= 0);
238 assert(size.y >= 0);
239
240 x_ = origin.x;
241 y_ = origin.y;
242 width_ = size.x;
243 height_ = size.y;
244
245 #if 0
246 std::cerr
247 << "Id " << id()
248 << " rect " << get_rectangle()
249 << " parent "
250 << (parent ? parent->get_x() : 0)
251 << ','
252 << (parent ? parent->get_y() : 0)
253 << " screen origin " << x_ << ',' << y_
254 << ".\n";
255 #endif
256
257 set_is_dirty(true);
258 }
259
move(const int x_offset,const int y_offset)260 void widget::move(const int x_offset, const int y_offset)
261 {
262 x_ += x_offset;
263 y_ += y_offset;
264 }
265
set_horizontal_alignment(const std::string & alignment)266 void widget::set_horizontal_alignment(const std::string& alignment)
267 {
268 grid* parent_grid = get_parent_grid();
269 if(!parent_grid) {
270 return;
271 }
272
273 parent_grid->set_child_alignment(this, implementation::get_h_align(alignment), grid::HORIZONTAL_MASK);
274
275 // TODO: evaluate necessity
276 //get_window()->invalidate_layout();
277 }
278
set_vertical_alignment(const std::string & alignment)279 void widget::set_vertical_alignment(const std::string& alignment)
280 {
281 grid* parent_grid = get_parent_grid();
282 if(!parent_grid) {
283 return;
284 }
285
286 parent_grid->set_child_alignment(this, implementation::get_v_align(alignment), grid::VERTICAL_MASK);
287
288 // TODO: evaluate necessity
289 //get_window()->invalidate_layout();
290 }
291
layout_children()292 void widget::layout_children()
293 {
294 /* DO NOTHING */
295 }
296
get_origin() const297 point widget::get_origin() const
298 {
299 return point(x_, y_);
300 }
301
get_size() const302 point widget::get_size() const
303 {
304 return point(width_, height_);
305 }
306
get_rectangle() const307 SDL_Rect widget::get_rectangle() const
308 {
309 return create_rect(get_origin(), get_size());
310 }
311
get_x() const312 int widget::get_x() const
313 {
314 return x_;
315 }
316
get_y() const317 int widget::get_y() const
318 {
319 return y_;
320 }
321
get_width() const322 unsigned widget::get_width() const
323 {
324 return width_;
325 }
326
get_height() const327 unsigned widget::get_height() const
328 {
329 return height_;
330 }
331
set_layout_size(const point & size)332 void widget::set_layout_size(const point& size)
333 {
334 layout_size_ = size;
335 }
336
layout_size() const337 const point& widget::layout_size() const
338 {
339 return layout_size_;
340 }
341
set_linked_group(const std::string & linked_group)342 void widget::set_linked_group(const std::string& linked_group)
343 {
344 linked_group_ = linked_group;
345 }
346
347 /***** ***** ***** ***** Drawing functions. ***** ***** ***** *****/
348
calculate_blitting_rectangle(const int x_offset,const int y_offset)349 SDL_Rect widget::calculate_blitting_rectangle(const int x_offset,
350 const int y_offset)
351 {
352 SDL_Rect result = get_rectangle();
353 result.x += x_offset;
354 result.y += y_offset;
355 return result;
356 }
357
calculate_clipping_rectangle(const int x_offset,const int y_offset)358 SDL_Rect widget::calculate_clipping_rectangle(const int x_offset,
359 const int y_offset)
360 {
361 SDL_Rect result = clipping_rectangle_;
362 result.x += x_offset;
363 result.y += y_offset;
364 return result;
365 }
366
draw_background(surface & frame_buffer,int x_offset,int y_offset)367 void widget::draw_background(surface& frame_buffer, int x_offset, int y_offset)
368 {
369 assert(visible_ == visibility::visible);
370
371 if(redraw_action_ == redraw_action::partly) {
372 const SDL_Rect clipping_rectangle
373 = calculate_clipping_rectangle(x_offset, y_offset);
374
375 clip_rect_setter clip(frame_buffer, &clipping_rectangle);
376 draw_debug_border(x_offset, y_offset);
377 impl_draw_background(frame_buffer, x_offset, y_offset);
378 } else {
379 draw_debug_border(x_offset, y_offset);
380 impl_draw_background(frame_buffer, x_offset, y_offset);
381 }
382 }
383
draw_children(surface & frame_buffer,int x_offset,int y_offset)384 void widget::draw_children(surface& frame_buffer, int x_offset, int y_offset)
385 {
386 assert(visible_ == visibility::visible);
387
388 if(redraw_action_ == redraw_action::partly) {
389 const SDL_Rect clipping_rectangle
390 = calculate_clipping_rectangle(x_offset, y_offset);
391
392 clip_rect_setter clip(frame_buffer, &clipping_rectangle);
393 impl_draw_children(frame_buffer, x_offset, y_offset);
394 } else {
395 impl_draw_children(frame_buffer, x_offset, y_offset);
396 }
397 }
398
draw_foreground(surface & frame_buffer,int x_offset,int y_offset)399 void widget::draw_foreground(surface& frame_buffer, int x_offset, int y_offset)
400 {
401 assert(visible_ == visibility::visible);
402
403 if(redraw_action_ == redraw_action::partly) {
404 const SDL_Rect clipping_rectangle
405 = calculate_clipping_rectangle(x_offset, y_offset);
406
407 clip_rect_setter clip(frame_buffer, &clipping_rectangle);
408 impl_draw_foreground(frame_buffer, x_offset, y_offset);
409 } else {
410 impl_draw_foreground(frame_buffer, x_offset, y_offset);
411 }
412 }
413
populate_dirty_list(window & caller,std::vector<widget * > & call_stack)414 void widget::populate_dirty_list(window& caller,
415 std::vector<widget*>& call_stack)
416 {
417 assert(call_stack.empty() || call_stack.back() != this);
418
419 if(visible_ != visibility::visible) {
420 return;
421 }
422
423 if(get_drawing_action() == redraw_action::none) {
424 return;
425 }
426
427 call_stack.push_back(this);
428 if(is_dirty_) {
429 caller.add_to_dirty_list(call_stack);
430 } else {
431 // virtual function which only does something for container items.
432 child_populate_dirty_list(caller, call_stack);
433 }
434 }
435
436 void
child_populate_dirty_list(window &,const std::vector<widget * > &)437 widget::child_populate_dirty_list(window& /*caller*/
438 ,
439 const std::vector<widget*>& /*call_stack*/)
440 {
441 /* DO NOTHING */
442 }
443
get_dirty_rectangle() const444 SDL_Rect widget::get_dirty_rectangle() const
445 {
446 return redraw_action_ == redraw_action::full ? get_rectangle()
447 : clipping_rectangle_;
448 }
449
set_visible_rectangle(const SDL_Rect & rectangle)450 void widget::set_visible_rectangle(const SDL_Rect& rectangle)
451 {
452 clipping_rectangle_ = sdl::intersect_rects(rectangle, get_rectangle());
453
454 if(clipping_rectangle_ == get_rectangle()) {
455 redraw_action_ = redraw_action::full;
456 } else if(clipping_rectangle_ == sdl::empty_rect) {
457 redraw_action_ = redraw_action::none;
458 } else {
459 redraw_action_ = redraw_action::partly;
460 }
461 }
462
set_is_dirty(const bool is_dirty)463 void widget::set_is_dirty(const bool is_dirty)
464 {
465 is_dirty_ = is_dirty;
466 }
467
get_is_dirty() const468 bool widget::get_is_dirty() const
469 {
470 return is_dirty_;
471 }
472
set_visible(const visibility visible)473 void widget::set_visible(const visibility visible)
474 {
475 if(visible == visible_) {
476 return;
477 }
478
479 // Switching to or from invisible should invalidate the layout
480 // if the widget has already been laid out.
481 const bool need_resize = visible_ == visibility::invisible
482 || (visible == visibility::invisible && get_size() != point());
483 visible_ = visible;
484
485 if(need_resize) {
486 if(visible == visibility::visible && new_widgets) {
487 event::message message;
488 fire(event::REQUEST_PLACEMENT, *this, message);
489 } else {
490 window* window = get_window();
491 if(window) {
492 window->invalidate_layout();
493 }
494 }
495 } else {
496 set_is_dirty(true);
497 }
498 }
499
get_visible() const500 widget::visibility widget::get_visible() const
501 {
502 return visible_;
503 }
504
get_drawing_action() const505 widget::redraw_action widget::get_drawing_action() const
506 {
507 return (width_ == 0 || height_ == 0) ? redraw_action::none
508 : redraw_action_;
509 }
510
set_debug_border_mode(const unsigned debug_border_mode)511 void widget::set_debug_border_mode(const unsigned debug_border_mode)
512 {
513 debug_border_mode_ = debug_border_mode;
514 }
515
set_debug_border_color(const color_t debug_border_color)516 void widget::set_debug_border_color(const color_t debug_border_color)
517 {
518 debug_border_color_ = debug_border_color;
519 }
520
draw_debug_border()521 void widget::draw_debug_border()
522 {
523 SDL_Rect r = redraw_action_ == redraw_action::partly ? clipping_rectangle_
524 : get_rectangle();
525
526 switch(debug_border_mode_) {
527 case 0:
528 /* DO NOTHING */
529 break;
530 case 1:
531 sdl::draw_rectangle(r, debug_border_color_);
532 break;
533
534 case 2:
535 sdl::fill_rectangle(r, debug_border_color_);
536 break;
537
538 default:
539 assert(false);
540 }
541 }
542
543 void
draw_debug_border(int x_offset,int y_offset)544 widget::draw_debug_border(int x_offset, int y_offset)
545 {
546 SDL_Rect r = redraw_action_ == redraw_action::partly
547 ? calculate_clipping_rectangle(x_offset, y_offset)
548 : calculate_blitting_rectangle(x_offset, y_offset);
549
550 switch(debug_border_mode_) {
551 case 0:
552 /* DO NOTHING */
553 break;
554
555 case 1:
556 sdl::draw_rectangle(r, debug_border_color_);
557 break;
558
559 case 2:
560 sdl::fill_rectangle(r, debug_border_color_);
561 break;
562
563 default:
564 assert(false);
565 }
566 }
567
568 /***** ***** ***** ***** Query functions ***** ***** ***** *****/
569
find_at(const point & coordinate,const bool must_be_active)570 widget* widget::find_at(const point& coordinate, const bool must_be_active)
571 {
572 return is_at(coordinate, must_be_active) ? this : nullptr;
573 }
574
find_at(const point & coordinate,const bool must_be_active) const575 const widget* widget::find_at(const point& coordinate,
576 const bool must_be_active) const
577 {
578 return is_at(coordinate, must_be_active) ? this : nullptr;
579 }
580
find(const std::string & id,const bool)581 widget* widget::find(const std::string& id, const bool /*must_be_active*/)
582 {
583 return id_ == id ? this : nullptr;
584 }
585
find(const std::string & id,const bool) const586 const widget* widget::find(const std::string& id,
587 const bool /*must_be_active*/) const
588 {
589 return id_ == id ? this : nullptr;
590 }
591
has_widget(const widget & widget) const592 bool widget::has_widget(const widget& widget) const
593 {
594 return &widget == this;
595 }
596
is_at(const point & coordinate) const597 bool widget::is_at(const point& coordinate) const
598 {
599 return is_at(coordinate, true);
600 }
601
recursive_is_visible(const widget * widget,const bool must_be_active) const602 bool widget::recursive_is_visible(const widget* widget, const bool must_be_active) const
603 {
604 while(widget) {
605 if(widget->visible_ == visibility::invisible
606 || (widget->visible_ == visibility::hidden && must_be_active)) {
607 return false;
608 }
609
610 widget = widget->parent_;
611 }
612
613 return true;
614 }
615
is_at(const point & coordinate,const bool must_be_active) const616 bool widget::is_at(const point& coordinate, const bool must_be_active) const
617 {
618 if(!recursive_is_visible(this, must_be_active)) {
619 return false;
620 }
621
622 return sdl::point_in_rect(coordinate, get_rectangle());
623 }
624
625 } // namespace gui2
626