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