1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 /**
16  * @file
17  * Routines to set up the display, scroll and zoom the map.
18  */
19 
20 #include "arrow.hpp"
21 #include "cursor.hpp"
22 #include "display.hpp"
23 #include "fake_unit_manager.hpp"
24 #include "font/sdl_ttf.hpp"
25 #include "font/text.hpp"
26 #include "preferences/game.hpp"
27 #include "gettext.hpp"
28 #include "halo.hpp"
29 #include "hotkey/command_executor.hpp"
30 #include "language.hpp"
31 #include "log.hpp"
32 #include "font/marked-up_text.hpp"
33 #include "map/map.hpp"
34 #include "map/label.hpp"
35 #include "minimap.hpp"
36 #include "overlay.hpp"
37 #include "play_controller.hpp" //note: this can probably be refactored out
38 #include "reports.hpp"
39 #include "resources.hpp"
40 #include "color.hpp"
41 #include "synced_context.hpp"
42 #include "team.hpp"
43 #include "terrain/builder.hpp"
44 #include "time_of_day.hpp"
45 #include "tooltips.hpp"
46 #include "tod_manager.hpp"
47 #include "units/unit.hpp"
48 #include "units/animation_component.hpp"
49 #include "units/drawer.hpp"
50 #include "whiteboard/manager.hpp"
51 #include "show_dialog.hpp"
52 #include "gui/dialogs/loading_screen.hpp"
53 
54 #include <SDL2/SDL_image.h>
55 
56 #include <array>
57 #include <cmath>
58 #include <iomanip>
59 #include <utility>
60 
61 #ifdef _WIN32
62 #include <windows.h>
63 #endif
64 
65 // Includes for bug #17573
66 
67 static lg::log_domain log_display("display");
68 #define ERR_DP LOG_STREAM(err, log_display)
69 #define LOG_DP LOG_STREAM(info, log_display)
70 #define DBG_DP LOG_STREAM(debug, log_display)
71 
72 // These are macros instead of proper constants so that they auto-update if the game config is reloaded.
73 #define zoom_levels      (game_config::zoom_levels)
74 #define final_zoom_index (static_cast<int>(zoom_levels.size()) - 1)
75 #define DefaultZoom      (game_config::tile_size)
76 #define SmallZoom        (DefaultZoom / 2)
77 #define MinZoom          (zoom_levels.front())
78 #define MaxZoom          (zoom_levels.back())
79 
80 namespace {
81 	bool benchmark = false;
82 
83 	bool debug_foreground = false;
84 
85 	int prevLabel = 0;
86 }
87 
88 unsigned int display::zoom_ = DefaultZoom;
89 unsigned int display::last_zoom_ = SmallZoom;
90 
parse_team_overlays()91 void display::parse_team_overlays()
92 {
93 	const team& curr_team = dc_->teams()[playing_team()];
94 	const team& prev_team = playing_team() == 0
95 		? dc_->teams().back()
96 		: dc_->get_team(playing_team());
97 	for (const game_display::overlay_map::value_type i : *overlays_) {
98 		const overlay& ov = i.second;
99 		if (!ov.team_name.empty() &&
100 			((ov.team_name.find(curr_team.team_name()) + 1) != 0) !=
101 			((ov.team_name.find(prev_team.team_name()) + 1) != 0))
102 		{
103 			invalidate(i.first);
104 		}
105 	}
106 }
107 
108 
add_overlay(const map_location & loc,const std::string & img,const std::string & halo,const std::string & team_name,const std::string & item_id,bool visible_under_fog)109 void display::add_overlay(const map_location& loc, const std::string& img, const std::string& halo, const std::string& team_name, const std::string& item_id, bool visible_under_fog)
110 {
111 	if (halo_man_) {
112 		halo::handle halo_handle;
113 		if(halo != "") {
114 			halo_handle = halo_man_->add(get_location_x(loc) + hex_size() / 2,
115 				get_location_y(loc) + hex_size() / 2, halo, loc);
116 		}
117 
118 		overlays_->emplace(loc, overlay(img, halo, halo_handle, team_name, item_id, visible_under_fog));
119 	}
120 }
121 
remove_overlay(const map_location & loc)122 void display::remove_overlay(const map_location& loc)
123 {
124 	/* This code no longer needed because of RAII in halo::handles
125 	if (halo_man_) {
126 		typedef overlay_map::const_iterator Itor;
127 		std::pair<Itor,Itor> itors = overlays_->equal_range(loc);
128 		while(itors.first != itors.second) {
129 			halo_man_->remove(itors.first->second.halo_handle);
130 			++itors.first;
131 		}
132 	}
133 	*/
134 
135 	overlays_->erase(loc);
136 }
137 
remove_single_overlay(const map_location & loc,const std::string & toDelete)138 void display::remove_single_overlay(const map_location& loc, const std::string& toDelete)
139 {
140 	//Iterate through the values with key of loc
141 	typedef overlay_map::iterator Itor;
142 	overlay_map::iterator iteratorCopy;
143 	std::pair<Itor,Itor> itors = overlays_->equal_range(loc);
144 	while(itors.first != itors.second) {
145 		//If image or halo of overlay struct matches toDelete, remove the overlay
146 		if(itors.first->second.image == toDelete || itors.first->second.halo == toDelete || itors.first->second.id == toDelete) {
147 			iteratorCopy = itors.first;
148 			++itors.first;
149 			//Not needed because of RAII --> halo_man_->remove(iteratorCopy->second.halo_handle);
150 			overlays_->erase(iteratorCopy);
151 		}
152 		else {
153 			++itors.first;
154 		}
155 	}
156 }
157 
display(const display_context * dc,std::weak_ptr<wb::manager> wb,reports & reports_object,const config & theme_cfg,const config & level,bool auto_join)158 display::display(const display_context * dc, std::weak_ptr<wb::manager> wb, reports & reports_object, const config& theme_cfg, const config& level, bool auto_join) :
159 	video2::draw_layering(auto_join),
160 	dc_(dc),
161 	halo_man_(new halo::manager(*this)),
162 	wb_(wb),
163 	exclusive_unit_draw_requests_(),
164 	screen_(CVideo::get_singleton()),
165 	currentTeam_(0),
166 	dont_show_all_(false),
167 	xpos_(0),
168 	ypos_(0),
169 	view_locked_(false),
170 	theme_(theme_cfg, screen_.screen_area()),
171 	zoom_index_(0),
172 	fake_unit_man_(new fake_unit_manager(*this)),
173 	builder_(new terrain_builder(level, (dc_ ? &dc_->map() : nullptr), theme_.border().tile_image, theme_.border().show_border)),
174 	minimap_(nullptr),
175 	minimap_location_(sdl::empty_rect),
176 	redrawMinimap_(false),
177 	redraw_background_(true),
178 	invalidateAll_(true),
179 	grid_(false),
180 	diagnostic_label_(0),
181 	panelsDrawn_(false),
182 	turbo_speed_(2),
183 	turbo_(false),
184 	invalidateGameStatus_(true),
185 	map_labels_(new map_labels(0)),
186 	reports_object_(&reports_object),
187 	scroll_event_("scrolled"),
188 	complete_redraw_event_("completely_redrawn"),
189 	frametimes_(50),
190 	fps_counter_(),
191 	fps_start_(),
192 	fps_actual_(),
193 	reportRects_(),
194 	reportSurfaces_(),
195 	reports_(),
196 	menu_buttons_(),
197 	action_buttons_(),
198 	invalidated_(),
199 	mouseover_hex_overlay_(nullptr),
200 	tod_hex_mask1(nullptr),
201 	tod_hex_mask2(nullptr),
202 	fog_images_(),
203 	shroud_images_(),
204 	selectedHex_(),
205 	mouseoverHex_(),
206 	keys_(),
207 	animate_map_(true),
208 	animate_water_(true),
209 	flags_(),
210 	activeTeam_(0),
211 	drawing_buffer_(),
212 	map_screenshot_(false),
213 	reach_map_(),
214 	reach_map_old_(),
215 	reach_map_changed_(true),
216 	overlays_(nullptr),
217 	fps_handle_(0),
218 	invalidated_hexes_(0),
219 	drawn_hexes_(0),
220 	idle_anim_(preferences::idle_anim()),
221 	idle_anim_rate_(1.0),
222 	map_screenshot_surf_(nullptr),
223 	redraw_observers_(),
224 	draw_coordinates_(false),
225 	draw_terrain_codes_(false),
226 	draw_num_of_bitmaps_(false),
227 	arrows_map_(),
228 	color_adjust_(),
229 	dirty_()
230 {
231 	//The following assertion fails when starting a campaign
232 	assert(singleton_ == nullptr);
233 	singleton_ = this;
234 
235 	resources::fake_units = fake_unit_man_.get();
236 
237 	blindfold_ctr_ = 0;
238 
239 	read(level.child_or_empty("display"));
240 
241 	if(screen_.non_interactive()
242 		&& (screen_.getSurface() != nullptr
243 		&& screen_.faked())) {
244 		screen_.lock_updates(true);
245 	}
246 
247 	fill_images_list(game_config::fog_prefix, fog_images_);
248 	fill_images_list(game_config::shroud_prefix, shroud_images_);
249 
250 	set_idle_anim_rate(preferences::idle_anim_rate());
251 
252 	zoom_index_ = std::distance(zoom_levels.begin(), std::find(zoom_levels.begin(), zoom_levels.end(), zoom_));
253 
254 	image::set_zoom(zoom_);
255 
256 	init_flags();
257 
258 	if(!menu_buttons_.empty() || !action_buttons_.empty()) {
259 		create_buttons();
260 	}
261 
262 #ifdef _WIN32
263 	// Increase timer resolution to prevent delays getting much longer than they should.
264 	timeBeginPeriod(1u);
265 #endif
266 }
267 
~display()268 display::~display()
269 {
270 #ifdef _WIN32
271 	timeEndPeriod(1u);
272 #endif
273 
274 	singleton_ = nullptr;
275 	resources::fake_units = nullptr;
276 }
277 
set_theme(config theme_cfg)278 void display::set_theme(config theme_cfg) {
279 	theme_ = theme(theme_cfg, screen_.screen_area());
280 	builder_->set_draw_border(theme_.border().show_border);
281 	menu_buttons_.clear();
282 	action_buttons_.clear();
283 	create_buttons();
284 	invalidate_theme();
285 	rebuild_all();
286 	redraw_everything();
287 }
288 
init_flags()289 void display::init_flags() {
290 
291 	flags_.clear();
292 	if (!dc_) return;
293 	flags_.resize(dc_->teams().size());
294 
295 	std::vector<std::string> side_colors;
296 	side_colors.reserve(dc_->teams().size());
297 
298 	for(const team& t : dc_->teams()) {
299 		std::string side_color = t.color();
300 		side_colors.push_back(side_color);
301 		init_flags_for_side_internal(t.side() - 1, side_color);
302 	}
303 	image::set_team_colors(&side_colors);
304 }
305 
reinit_flags_for_side(size_t side)306 void display::reinit_flags_for_side(size_t side)
307 {
308 	if (!dc_ || side >= dc_->teams().size()) {
309 		ERR_DP << "Cannot rebuild flags for inexistent or unconfigured side " << side << '\n';
310 		return;
311 	}
312 
313 	init_flags_for_side_internal(side, dc_->teams()[side].color());
314 }
315 
init_flags_for_side_internal(size_t n,const std::string & side_color)316 void display::init_flags_for_side_internal(size_t n, const std::string& side_color)
317 {
318 	assert(dc_ != nullptr);
319 	assert(n < dc_->teams().size());
320 	assert(n < flags_.size());
321 
322 	std::string flag = dc_->teams()[n].flag();
323 	std::string old_rgb = game_config::flag_rgb;
324 	std::string new_rgb = side_color;
325 
326 	if(flag.empty()) {
327 		flag = game_config::images::flag;
328 	}
329 
330 	LOG_DP << "Adding flag for team " << n << " from animation " << flag << "\n";
331 
332 	// Must recolor flag image
333 	animated<image::locator> temp_anim;
334 
335 	std::vector<std::string> items = utils::square_parenthetical_split(flag);
336 
337 	for(const std::string& item : items) {
338 		const std::vector<std::string>& sub_items = utils::split(item, ':');
339 		std::string str = item;
340 		int time = 100;
341 
342 		if(sub_items.size() > 1) {
343 			str = sub_items.front();
344 			try {
345 				time = std::max<int>(1, std::stoi(sub_items.back()));
346 			} catch(const std::invalid_argument&) {
347 				ERR_DP << "Invalid time value found when constructing flag for side " << n << ": " << sub_items.back() << "\n";
348 			}
349 		}
350 
351 		std::stringstream temp;
352 		temp << str << "~RC(" << old_rgb << ">"<< new_rgb << ")";
353 		image::locator flag_image(temp.str());
354 		temp_anim.add_frame(time, flag_image);
355 	}
356 
357 	animated<image::locator>& f = flags_[n];
358 	f = temp_anim;
359 	auto time = f.get_end_time();
360 	if (time > 0) {
361 		f.start_animation(randomness::rng::default_instance().get_random_int(0, time-1), true);
362 	}
363 	else {
364 		// this can happen if both flag and game_config::images::flag are empty.
365 		ERR_DP << "missing flag for team" << n << "\n";
366 	}
367 }
368 
get_flag(const map_location & loc)369 surface display::get_flag(const map_location& loc)
370 {
371 	if(!get_map().is_village(loc)) {
372 		return surface(nullptr);
373 	}
374 
375 	for (const team& t : dc_->teams()) {
376 		if (t.owns_village(loc) && (!fogged(loc) || !dc_->get_team(viewing_side()).is_enemy(t.side())))
377 		{
378 			auto& flag = flags_[t.side() - 1];
379 			flag.update_last_draw_time();
380 			const image::locator &image_flag = animate_map_ ?
381 				flag.get_current_frame() : flag.get_first_frame();
382 			return image::get_image(image_flag, image::TOD_COLORED);
383 		}
384 	}
385 
386 	return surface(nullptr);
387 }
388 
set_team(size_t teamindex,bool show_everything)389 void display::set_team(size_t teamindex, bool show_everything)
390 {
391 	assert(teamindex < dc_->teams().size());
392 	currentTeam_ = teamindex;
393 	if (!show_everything)
394 	{
395 		labels().set_team(&dc_->teams()[teamindex]);
396 		dont_show_all_ = true;
397 	}
398 	else
399 	{
400 		labels().set_team(nullptr);
401 		dont_show_all_ = false;
402 	}
403 	labels().recalculate_labels();
404 	if(std::shared_ptr<wb::manager> w = wb_.lock())
405 		w->on_viewer_change(teamindex);
406 }
407 
set_playing_team(size_t teamindex)408 void display::set_playing_team(size_t teamindex)
409 {
410 	assert(teamindex < dc_->teams().size());
411 	activeTeam_ = teamindex;
412 	invalidate_game_status();
413 }
414 
add_exclusive_draw(const map_location & loc,unit & unit)415 bool display::add_exclusive_draw(const map_location& loc, unit& unit)
416 {
417 	if (loc.valid() && exclusive_unit_draw_requests_.find(loc) == exclusive_unit_draw_requests_.end())
418 	{
419 		exclusive_unit_draw_requests_[loc] = unit.id();
420 		return true;
421 	}
422 	else
423 	{
424 		return false;
425 	}
426 }
427 
remove_exclusive_draw(const map_location & loc)428 std::string display::remove_exclusive_draw(const map_location& loc)
429 {
430 	std::string id = "";
431 	if(loc.valid())
432 	{
433 		id = exclusive_unit_draw_requests_[loc];
434 		//id will be set to the default "" string by the [] operator if the map doesn't have anything for that loc.
435 		exclusive_unit_draw_requests_.erase(loc);
436 	}
437 	return id;
438 }
439 
get_time_of_day(const map_location &) const440 const time_of_day & display::get_time_of_day(const map_location& /*loc*/) const
441 {
442 	static time_of_day tod;
443 	return tod;
444 }
445 
update_tod(const time_of_day * tod_override)446 void display::update_tod(const time_of_day* tod_override)
447 {
448 	const time_of_day* tod = tod_override;
449 	if(tod == nullptr) {
450 		tod = &get_time_of_day();
451 	}
452 
453 	const tod_color col = color_adjust_ + tod->color;
454 	image::set_color_adjustment(col.r, col.g, col.b);
455 }
456 
adjust_color_overlay(int r,int g,int b)457 void display::adjust_color_overlay(int r, int g, int b)
458 {
459 	color_adjust_ = tod_color(r, g, b);
460 	update_tod();
461 }
462 
fill_images_list(const std::string & prefix,std::vector<std::string> & images)463 void display::fill_images_list(const std::string& prefix, std::vector<std::string>& images)
464 {
465 	if(prefix == ""){
466 		return;
467 	}
468 
469 	// search prefix.png, prefix1.png, prefix2.png ...
470 	for(int i=0; ; ++i){
471 		std::ostringstream s;
472 		s << prefix;
473 		if(i != 0)
474 			s << i;
475 		s << ".png";
476 		if(image::exists(s.str()))
477 			images.push_back(s.str());
478 		else if(i>0)
479 			break;
480 	}
481 	if (images.empty())
482 		images.emplace_back();
483 }
484 
get_variant(const std::vector<std::string> & variants,const map_location & loc)485 const std::string& display::get_variant(const std::vector<std::string>& variants, const map_location &loc)
486 {
487 	//TODO use better noise function
488 	return variants[std::abs(loc.x + loc.y) % variants.size()];
489 }
490 
rebuild_all()491 void display::rebuild_all()
492 {
493 	builder_->rebuild_all();
494 }
495 
reload_map()496 void display::reload_map()
497 {
498 	redraw_background_ = true;
499 	builder_->reload_map();
500 }
501 
change_display_context(const display_context * dc)502 void display::change_display_context(const display_context * dc)
503 {
504 	dc_ = dc;
505 	builder_->change_map(&dc_->map()); //TODO: Should display_context own and initialize the builder object?
506 }
507 
reset_halo_manager()508 void display::reset_halo_manager()
509 {
510 	halo_man_.reset(new halo::manager(*this));
511 }
512 
reset_halo_manager(halo::manager & halo_man)513 void display::reset_halo_manager(halo::manager & halo_man)
514 {
515 	halo_man_.reset(&halo_man);
516 }
517 
blindfold(bool value)518 void display::blindfold(bool value)
519 {
520 	if(value == true)
521 		++blindfold_ctr_;
522 	else
523 		--blindfold_ctr_;
524 }
525 
is_blindfolded() const526 bool display::is_blindfolded() const
527 {
528 	return blindfold_ctr_ > 0;
529 }
530 
531 
max_map_area() const532 const SDL_Rect& display::max_map_area() const
533 {
534 	static SDL_Rect max_area {0, 0, 0, 0};
535 
536 	// hex_size() is always a multiple of 4
537 	// and hex_width() a multiple of 3,
538 	// so there shouldn't be off-by-one-errors
539 	// due to rounding.
540 	// To display a hex fully on screen,
541 	// a little bit extra space is needed.
542 	// Also added the border two times.
543 	max_area.w  = static_cast<int>((get_map().w() + 2 * theme_.border().size + 1.0/3.0) * hex_width());
544 	max_area.h = static_cast<int>((get_map().h() + 2 * theme_.border().size + 0.5) * hex_size());
545 
546 	return max_area;
547 }
548 
map_area() const549 const SDL_Rect& display::map_area() const
550 {
551 	static SDL_Rect max_area;
552 	max_area = max_map_area();
553 
554 	// if it's for map_screenshot, maximize and don't recenter
555 	if (map_screenshot_) {
556 		return max_area;
557 	}
558 
559 	static SDL_Rect res;
560 	res = map_outside_area();
561 
562 	if(max_area.w < res.w) {
563 		// map is smaller, center
564 		res.x += (res.w - max_area.w)/2;
565 		res.w = max_area.w;
566 	}
567 
568 	if(max_area.h < res.h) {
569 		// map is smaller, center
570 		res.y += (res.h - max_area.h)/2;
571 		res.h = max_area.h;
572 	}
573 
574 	return res;
575 }
576 
outside_area(const SDL_Rect & area,const int x,const int y)577 bool display::outside_area(const SDL_Rect& area, const int x, const int y)
578 {
579 	const int x_thresh = hex_size();
580 	const int y_thresh = hex_size();
581 	return (x < area.x || x > area.x + area.w - x_thresh ||
582 		y < area.y || y > area.y + area.h - y_thresh);
583 }
584 
585 // This function uses the screen as reference
hex_clicked_on(int xclick,int yclick) const586 const map_location display::hex_clicked_on(int xclick, int yclick) const
587 {
588 	const SDL_Rect& rect = map_area();
589 	if(sdl::point_in_rect(xclick,yclick,rect) == false) {
590 		return map_location();
591 	}
592 
593 	xclick -= rect.x;
594 	yclick -= rect.y;
595 
596 	return pixel_position_to_hex(xpos_ + xclick, ypos_ + yclick);
597 }
598 
599 
600 // This function uses the rect of map_area as reference
pixel_position_to_hex(int x,int y) const601 const map_location display::pixel_position_to_hex(int x, int y) const
602 {
603 	// adjust for the border
604 	x -= static_cast<int>(theme_.border().size * hex_width());
605 	y -= static_cast<int>(theme_.border().size * hex_size());
606 	// The editor can modify the border and this will result in a negative y
607 	// value. Instead of adding extra cases we just shift the hex. Since the
608 	// editor doesn't use the direction this is no problem.
609 	const int offset = y < 0 ? 1 : 0;
610 	if(offset) {
611 		x += hex_width();
612 		y += hex_size();
613 	}
614 	const int s = hex_size();
615 	const int tesselation_x_size = hex_width() * 2;
616 	const int tesselation_y_size = s;
617 	const int x_base = x / tesselation_x_size * 2;
618 	const int x_mod  = x % tesselation_x_size;
619 	const int y_base = y / tesselation_y_size;
620 	const int y_mod  = y % tesselation_y_size;
621 
622 	int x_modifier = 0;
623 	int y_modifier = 0;
624 
625 	if (y_mod < tesselation_y_size / 2) {
626 		if ((x_mod * 2 + y_mod) < (s / 2)) {
627 			x_modifier = -1;
628 			y_modifier = -1;
629 		} else if ((x_mod * 2 - y_mod) < (s * 3 / 2)) {
630 			x_modifier = 0;
631 			y_modifier = 0;
632 		} else {
633 			x_modifier = 1;
634 			y_modifier = -1;
635 		}
636 
637 	} else {
638 		if ((x_mod * 2 - (y_mod - s / 2)) < 0) {
639 			x_modifier = -1;
640 			y_modifier = 0;
641 		} else if ((x_mod * 2 + (y_mod - s / 2)) < s * 2) {
642 			x_modifier = 0;
643 			y_modifier = 0;
644 		} else {
645 			x_modifier = 1;
646 			y_modifier = 0;
647 		}
648 	}
649 
650 	return map_location(x_base + x_modifier - offset, y_base + y_modifier - offset);
651 }
652 
operator ++()653 display::rect_of_hexes::iterator& display::rect_of_hexes::iterator::operator++()
654 {
655 	if (loc_.y < rect_.bottom[loc_.x & 1])
656 		++loc_.y;
657 	else {
658 		++loc_.x;
659 		loc_.y = rect_.top[loc_.x & 1];
660 	}
661 
662 	return *this;
663 }
664 
665 // begin is top left, and end is after bottom right
begin() const666 display::rect_of_hexes::iterator display::rect_of_hexes::begin() const
667 {
668 	return iterator(map_location(left, top[left & 1]), *this);
669 }
end() const670 display::rect_of_hexes::iterator display::rect_of_hexes::end() const
671 {
672 	return iterator(map_location(right+1, top[(right+1) & 1]), *this);
673 }
674 
hexes_under_rect(const SDL_Rect & r) const675 const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const
676 {
677 	rect_of_hexes res;
678 
679 	if (r.w<=0 || r.h<=0) {
680 		// empty rect, return dummy values giving begin=end
681 		res.left = 0;
682 		res.right = -1; // end is right+1
683 		res.top[0] = 0;
684 		res.top[1] = 0;
685 		res.bottom[0] = 0;
686 		res.bottom[1] = 0;
687 		return res;
688 	}
689 
690 	SDL_Rect map_rect = map_area();
691 	// translate rect coordinates from screen-based to map_area-based
692 	int x = xpos_ - map_rect.x + r.x;
693 	int y = ypos_ - map_rect.y + r.y;
694 	// we use the "double" type to avoid important rounding error (size of an hex!)
695 	// we will also need to use std::floor to avoid bad rounding at border (negative values)
696 	double tile_width = hex_width();
697 	double tile_size = hex_size();
698 	double border = theme_.border().size;
699 	// we minus "0.(3)", for horizontal imbrication.
700 	// reason is: two adjacent hexes each overlap 1/4 of their width, so for
701 	// grid calculation 3/4 of tile width is used, which by default gives
702 	// 18/54=0.(3). Note that, while tile_width is zoom dependent, 0.(3) is not.
703 	res.left = static_cast<int>(std::floor(-border + x / tile_width - 0.3333333));
704 	// we remove 1 pixel of the rectangle dimensions
705 	// (the rounded division take one pixel more than needed)
706 	res.right = static_cast<int>(std::floor(-border + (x + r.w-1) / tile_width));
707 
708 	// for odd x, we must shift up one half-hex. Since x will vary along the edge,
709 	// we store here the y values for even and odd x, respectively
710 	res.top[0] = static_cast<int>(std::floor(-border + y / tile_size));
711 	res.top[1] = static_cast<int>(std::floor(-border + y / tile_size - 0.5));
712 	res.bottom[0] = static_cast<int>(std::floor(-border + (y + r.h-1) / tile_size));
713 	res.bottom[1] = static_cast<int>(std::floor(-border + (y + r.h-1) / tile_size - 0.5));
714 
715 	// TODO: in some rare cases (1/16), a corner of the big rect is on a tile
716 	// (the 72x72 rectangle containing the hex) but not on the hex itself
717 	// Can maybe be optimized by using pixel_position_to_hex
718 
719 	return res;
720 }
721 
team_valid() const722 bool display::team_valid() const
723 {
724 	return currentTeam_ < dc_->teams().size();
725 }
726 
shrouded(const map_location & loc) const727 bool display::shrouded(const map_location& loc) const
728 {
729 	return is_blindfolded() || (dont_show_all_ && dc_->teams()[currentTeam_].shrouded(loc));
730 }
731 
fogged(const map_location & loc) const732 bool display::fogged(const map_location& loc) const
733 {
734 	return is_blindfolded() || (dont_show_all_ && dc_->teams()[currentTeam_].fogged(loc));
735 }
736 
get_location_x(const map_location & loc) const737 int display::get_location_x(const map_location& loc) const
738 {
739 	return static_cast<int>(map_area().x + (loc.x + theme_.border().size) * hex_width() - xpos_);
740 }
741 
get_location_y(const map_location & loc) const742 int display::get_location_y(const map_location& loc) const
743 {
744 	return static_cast<int>(map_area().y + (loc.y + theme_.border().size) * zoom_ - ypos_ + (is_odd(loc.x) ? zoom_/2 : 0));
745 }
746 
minimap_location_on(int x,int y)747 map_location display::minimap_location_on(int x, int y)
748 {
749 	//TODO: don't return location for this,
750 	// instead directly scroll to the clicked pixel position
751 
752 	if (!sdl::point_in_rect(x, y, minimap_area())) {
753 		return map_location();
754 	}
755 
756 	// we transform the coordinates from minimap to the full map image
757 	// probably more adjustments to do (border, minimap shift...)
758 	// but the mouse and human capacity to evaluate the rectangle center
759 	// is not pixel precise.
760 	int px = (x - minimap_location_.x) * get_map().w()*hex_width() / std::max (minimap_location_.w, 1);
761 	int py = (y - minimap_location_.y) * get_map().h()*hex_size() / std::max(minimap_location_.h, 1);
762 
763 	map_location loc = pixel_position_to_hex(px, py);
764 	if (loc.x < 0)
765 		loc.x = 0;
766 	else if (loc.x >= get_map().w())
767 		loc.x = get_map().w() - 1;
768 
769 	if (loc.y < 0)
770 		loc.y = 0;
771 	else if (loc.y >= get_map().h())
772 		loc.y = get_map().h() - 1;
773 
774 	return loc;
775 }
776 
screenshot(bool map_screenshot)777 surface display::screenshot(bool map_screenshot)
778 {
779 	if (!map_screenshot) {
780 		return screen_.getSurface().clone();
781 	} else {
782 		if (get_map().empty()) {
783 			ERR_DP << "No map loaded, cannot create a map screenshot.\n";
784 			return nullptr;
785 		}
786 
787 		SDL_Rect area = max_map_area();
788 		map_screenshot_surf_ = surface(area.w, area.h);
789 
790 		if (map_screenshot_surf_ == nullptr) {
791 			// Memory problem ?
792 			ERR_DP << "Could not create screenshot surface, try zooming out.\n";
793 			return nullptr;
794 		}
795 
796 		// back up the current map view position and move to top-left
797 		int old_xpos = xpos_;
798 		int old_ypos = ypos_;
799 		xpos_ = 0;
800 		ypos_ = 0;
801 
802 		// we reroute render output to the screenshot surface and invalidate all
803 		map_screenshot_= true;
804 		invalidateAll_ = true;
805 		DBG_DP << "draw() with map_screenshot\n";
806 		draw(true,true);
807 
808 		// restore normal rendering
809 		map_screenshot_= false;
810 		xpos_ = old_xpos;
811 		ypos_ = old_ypos;
812 
813 		// Clear map_screenshot_surf_ and return a new surface that contains the same data
814 		surface surf(std::move(map_screenshot_surf_));
815 		return surf;
816 	}
817 }
818 
find_action_button(const std::string & id)819 std::shared_ptr<gui::button> display::find_action_button(const std::string& id)
820 {
821 	for (size_t i = 0; i < action_buttons_.size(); ++i) {
822 		if(action_buttons_[i]->id() == id) {
823 			return action_buttons_[i];
824 		}
825 	}
826 	return nullptr;
827 }
828 
find_menu_button(const std::string & id)829 std::shared_ptr<gui::button> display::find_menu_button(const std::string& id)
830 {
831 	for (size_t i = 0; i < menu_buttons_.size(); ++i) {
832 		if(menu_buttons_[i]->id() == id) {
833 			return menu_buttons_[i];
834 		}
835 	}
836 	return nullptr;
837 }
838 
layout_buttons()839 void display::layout_buttons()
840 {
841 	DBG_DP << "positioning menu buttons...\n";
842 	for(const auto& menu : theme_.menus()) {
843 		std::shared_ptr<gui::button> b = find_menu_button(menu.get_id());
844 		if(b) {
845 			const SDL_Rect& loc = menu.location(screen_.screen_area());
846 			b->set_location(loc);
847 			b->set_measurements(0,0);
848 			b->set_label(menu.title());
849 			b->set_image(menu.image());
850 		}
851 	}
852 
853 	DBG_DP << "positioning action buttons...\n";
854 	for(const auto& action : theme_.actions()) {
855 		std::shared_ptr<gui::button> b = find_action_button(action.get_id());
856 		if(b) {
857 			const SDL_Rect& loc = action.location(screen_.screen_area());
858 			b->set_location(loc);
859 			b->set_measurements(0,0);
860 			b->set_label(action.title());
861 			b->set_image(action.image());
862 		}
863 	}
864 }
865 
create_buttons()866 void display::create_buttons()
867 {
868 	std::vector<std::shared_ptr<gui::button>> menu_work;
869 	std::vector<std::shared_ptr<gui::button>> action_work;
870 
871 	DBG_DP << "creating menu buttons...\n";
872 	for(const auto& menu : theme_.menus()) {
873 		if (!menu.is_button()) continue;
874 
875 		std::shared_ptr<gui::button> b(new gui::button(screen_, menu.title(), gui::button::TYPE_PRESS, menu.image(),
876 				gui::button::DEFAULT_SPACE, false, menu.overlay()));
877 		DBG_DP << "drawing button " << menu.get_id() << "\n";
878 		b->join_same(this);
879 		b->set_id(menu.get_id());
880 		if (!menu.tooltip().empty()){
881 			b->set_tooltip_string(menu.tooltip());
882 		}
883 
884 		std::shared_ptr<gui::button> b_prev = find_menu_button(b->id());
885 		if(b_prev) {
886 			b->enable(b_prev->enabled());
887 		}
888 
889 		menu_work.push_back(b);
890 	}
891 
892 	DBG_DP << "creating action buttons...\n";
893 	for(const auto& action : theme_.actions()) {
894 		std::shared_ptr<gui::button> b(new gui::button(screen_, action.title(), string_to_button_type(action.type()), action.image(),
895 				gui::button::DEFAULT_SPACE, false, action.overlay()));
896 
897 		DBG_DP << "drawing button " << action.get_id() << "\n";
898 		b->set_id(action.get_id());
899 		b->join_same(this);
900 		if (!action.tooltip(0).empty()){
901 			b->set_tooltip_string(action.tooltip(0));
902 		}
903 
904 		std::shared_ptr<gui::button> b_prev = find_action_button(b->id());
905 		if(b_prev) {
906 			b->enable(b_prev->enabled());
907 			if (b_prev->get_type() == gui::button::TYPE_CHECK) {
908 				b->set_check(b_prev->checked());
909 			}
910 		}
911 
912 		action_work.push_back(b);
913 	}
914 
915 
916 	menu_buttons_.clear();
917 	menu_buttons_.assign(menu_work.begin(), menu_work.end());
918 	action_buttons_.clear();
919 	action_buttons_.assign(action_work.begin(), action_work.end());
920 
921 	layout_buttons();
922 	DBG_DP << "buttons created\n";
923 }
924 
render_buttons()925 void display::render_buttons()
926 {
927 	for (std::shared_ptr<gui::button> btn : menu_buttons_) {
928 		btn->set_dirty(true);
929 		btn->draw();
930 	}
931 
932 	for (std::shared_ptr<gui::button> btn : action_buttons_) {
933 		btn->set_dirty(true);
934 		btn->draw();
935 	}
936 }
937 
938 
string_to_button_type(std::string type)939 gui::button::TYPE display::string_to_button_type(std::string type)
940 {
941 	gui::button::TYPE res = gui::button::TYPE_PRESS;
942 	if (type == "checkbox") { res = gui::button::TYPE_CHECK; }
943 	else if (type == "image") { res = gui::button::TYPE_IMAGE; }
944 	else if (type == "radiobox") { res = gui::button::TYPE_RADIO; }
945 	else if (type == "turbo") { res = gui::button::TYPE_TURBO; }
946 	return res;
947 }
948 
get_direction(size_t n)949 static const std::string& get_direction(size_t n)
950 {
951 	static const std::array<std::string, 6> dirs {{ "-n", "-ne", "-se", "-s", "-sw", "-nw" }};
952 	return dirs[n >= dirs.size() ? 0 : n];
953 }
954 
get_fog_shroud_images(const map_location & loc,image::TYPE image_type)955 std::vector<surface> display::get_fog_shroud_images(const map_location& loc, image::TYPE image_type)
956 {
957 	std::vector<std::string> names;
958 
959 	adjacent_loc_array_t adjacent;
960 	get_adjacent_tiles(loc, adjacent.data());
961 
962 	enum visibility {FOG=0, SHROUD=1, CLEAR=2};
963 	visibility tiles[6];
964 
965 	const std::string* image_prefix[] =
966 		{ &game_config::fog_prefix, &game_config::shroud_prefix};
967 
968 	for(int i = 0; i < 6; ++i) {
969 		if(shrouded(adjacent[i])) {
970 			tiles[i] = SHROUD;
971 		} else if(!fogged(loc) && fogged(adjacent[i])) {
972 			tiles[i] = FOG;
973 		} else {
974 			tiles[i] = CLEAR;
975 		}
976 	}
977 
978 	for(int v = FOG; v != CLEAR; ++v) {
979 		// Find somewhere that doesn't have overlap to use as a starting point
980 		int start;
981 		for(start = 0; start != 6; ++start) {
982 			if(tiles[start] != v) {
983 				break;
984 			}
985 		}
986 
987 		if(start == 6) {
988 			// Completely surrounded by fog or shroud. This might have
989 			// a special graphic.
990 			const std::string name = *image_prefix[v] + "-all.png";
991 			if ( image::exists(name) ) {
992 				names.push_back(name);
993 				// Proceed to the next visibility (fog -> shroud -> clear).
994 				continue;
995 			}
996 			// No special graphic found. We'll just combine some other images
997 			// and hope it works out.
998 			start = 0;
999 		}
1000 
1001 		// Find all the directions overlap occurs from
1002 		for (int i = (start+1)%6, cap1 = 0;  i != start && cap1 != 6;  ++cap1) {
1003 			if(tiles[i] == v) {
1004 				std::ostringstream stream;
1005 				std::string name;
1006 				stream << *image_prefix[v];
1007 
1008 				for (int cap2 = 0;  v == tiles[i] && cap2 != 6;  i = (i+1)%6, ++cap2) {
1009 					stream << get_direction(i);
1010 
1011 					if(!image::exists(stream.str() + ".png")) {
1012 						// If we don't have any surface at all,
1013 						// then move onto the next overlapped area
1014 						if(name.empty()) {
1015 							i = (i+1)%6;
1016 						}
1017 						break;
1018 					} else {
1019 						name = stream.str();
1020 					}
1021 				}
1022 
1023 				if(!name.empty()) {
1024 					names.push_back(name + ".png");
1025 				}
1026 			} else {
1027 				i = (i+1)%6;
1028 			}
1029 		}
1030 	}
1031 
1032 	// now get the surfaces
1033 	std::vector<surface> res;
1034 
1035 	for (std::string& name : names) {
1036 		surface surf(image::get_image(name, image_type));
1037 		if (surf)
1038 			res.push_back(std::move(surf));
1039 	}
1040 
1041 	return res;
1042 }
1043 
get_terrain_images(const map_location & loc,const std::string & timeid,TERRAIN_TYPE terrain_type)1044 void display::get_terrain_images(const map_location &loc,
1045 	const std::string& timeid,
1046 	TERRAIN_TYPE terrain_type)
1047 {
1048 	terrain_image_vector_.clear();
1049 
1050 	terrain_builder::TERRAIN_TYPE builder_terrain_type =
1051 	      (terrain_type == FOREGROUND ?
1052 		  terrain_builder::FOREGROUND : terrain_builder::BACKGROUND);
1053 
1054 	const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc,
1055 			timeid, builder_terrain_type);
1056 
1057 	image::light_string lt;
1058 
1059 	const time_of_day& tod = get_time_of_day(loc);
1060 
1061 	//get all the light transitions
1062 	adjacent_loc_array_t adjs;
1063 	std::array<const time_of_day*, 6> atods;
1064 	get_adjacent_tiles(loc, adjs.data());
1065 	for(size_t d = 0; d < adjs.size(); ++d){
1066 		atods[d] = &get_time_of_day(adjs[d]);
1067 	}
1068 
1069 	for(int d=0; d<6; ++d){
1070 		/* concave
1071 		  _____
1072 		 /     \
1073 		/ atod1 \_____
1074 		\ !tod  /     \
1075 		 \_____/ atod2 \
1076 		 /  \__\ !tod  /
1077 		/       \_____/
1078 		\  tod  /
1079 		 \_____/*/
1080 
1081 		const time_of_day& atod1 = *atods[d];
1082 		const time_of_day& atod2 = *atods[(d + 1) % 6];
1083 
1084 		if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color)
1085 			continue;
1086 
1087 		if(lt.empty()) {
1088 			//color the full hex before adding transitions
1089 			tod_color col = tod.color + color_adjust_;
1090 			lt = image::get_light_string(0, col.r, col.g, col.b);
1091 		}
1092 
1093 		// add the directional transitions
1094 		tod_color acol = atod1.color + color_adjust_;
1095 		lt += image::get_light_string(d + 1, acol.r, acol.g, acol.b);
1096 	}
1097 	for(int d=0; d<6; ++d){
1098 		/* convex 1
1099 		  _____
1100 		 /     \
1101 		/ atod1 \_____
1102 		\ !tod  /     \
1103 		 \_____/ atod2 \
1104 		 /  \__\  tod  /
1105 		/       \_____/
1106 		\  tod  /
1107 		 \_____/*/
1108 
1109 		const time_of_day& atod1 = *atods[d];
1110 		const time_of_day& atod2 = *atods[(d + 1) % 6];
1111 
1112 		if(atod1.color == tod.color || atod1.color == atod2.color)
1113 			continue;
1114 
1115 		if(lt.empty()) {
1116 			//color the full hex before adding transitions
1117 			tod_color col = tod.color + color_adjust_;
1118 			lt = image::get_light_string(0, col.r, col.g, col.b);
1119 		}
1120 
1121 		// add the directional transitions
1122 		tod_color acol = atod1.color + color_adjust_;
1123 		lt += image::get_light_string(d + 7, acol.r, acol.g, acol.b);
1124 	}
1125 	for(int d=0; d<6; ++d){
1126 		/* convex 2
1127 		  _____
1128 		 /     \
1129 		/ atod1 \_____
1130 		\  tod  /     \
1131 		 \_____/ atod2 \
1132 		 /  \__\ !tod  /
1133 		/       \_____/
1134 		\  tod  /
1135 		 \_____/*/
1136 
1137 		const time_of_day& atod1 = *atods[d];
1138 		const time_of_day& atod2 = *atods[(d + 1) % 6];
1139 
1140 		if(atod2.color == tod.color || atod1.color == atod2.color)
1141 			continue;
1142 
1143 		if(lt.empty()) {
1144 			//color the full hex before adding transitions
1145 			tod_color col = tod.color + color_adjust_;
1146 			lt = image::get_light_string(0, col.r, col.g, col.b);
1147 		}
1148 
1149 		// add the directional transitions
1150 		tod_color acol = atod2.color + color_adjust_;
1151 		lt += image::get_light_string(d + 13, acol.r, acol.g, acol.b);
1152 	}
1153 
1154 	if(lt.empty()){
1155 		tod_color col = tod.color + color_adjust_;
1156 		if(!col.is_zero()){
1157 			// no real lightmap needed but still color the hex
1158 			lt = image::get_light_string(-1, col.r, col.g, col.b);
1159 		}
1160 	}
1161 
1162 	if(terrains != nullptr) {
1163 		// Cache the offmap name.
1164 		// Since it is themeable it can change,
1165 		// so don't make it static.
1166 		const std::string off_map_name = "terrain/" + theme_.border().tile_image;
1167 		for(const auto& terrain : *terrains) {
1168 			const image::locator &image = animate_map_ ?
1169 				terrain.get_current_frame() : terrain.get_first_frame();
1170 
1171 			// We prevent ToD coloring and brightening of off-map tiles,
1172 			// We need to test for the tile to be rendered and
1173 			// not the location, since the transitions are rendered
1174 			// over the offmap-terrain and these need a ToD coloring.
1175 			surface surf;
1176 			const bool off_map = (image.get_filename() == off_map_name || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos);
1177 
1178 			if(off_map) {
1179 				surf = image::get_image(image, image::SCALED_TO_HEX);
1180 			} else if(lt.empty()) {
1181 				surf = image::get_image(image, image::SCALED_TO_HEX);
1182 			} else {
1183 				surf = image::get_lighted_image(image, lt, image::SCALED_TO_HEX);
1184 			}
1185 
1186 			if (surf) {
1187 				terrain_image_vector_.push_back(std::move(surf));
1188 			}
1189 		}
1190 	}
1191 }
1192 
drawing_buffer_add(const drawing_layer layer,const map_location & loc,int x,int y,const surface & surf,const SDL_Rect & clip)1193 void display::drawing_buffer_add(const drawing_layer layer,
1194 		const map_location& loc, int x, int y, const surface& surf,
1195 		const SDL_Rect &clip)
1196 {
1197 	drawing_buffer_.emplace_back(layer, loc, x, y, surf, clip);
1198 }
1199 
drawing_buffer_add(const drawing_layer layer,const map_location & loc,int x,int y,const std::vector<surface> & surf,const SDL_Rect & clip)1200 void display::drawing_buffer_add(const drawing_layer layer,
1201 		const map_location& loc, int x, int y,
1202 		const std::vector<surface> &surf,
1203 		const SDL_Rect &clip)
1204 {
1205 	drawing_buffer_.emplace_back(layer, loc, x, y, surf, clip);
1206 }
1207 
1208 // FIXME: temporary method. Group splitting should be made
1209 // public into the definition of drawing_layer
1210 //
1211 // The drawing is done per layer_group, the range per group is [low, high).
1212 const std::array<display::drawing_layer, 4> display::drawing_buffer_key::layer_groups {{
1213 	LAYER_TERRAIN_BG,
1214 	LAYER_UNIT_FIRST,
1215 	LAYER_UNIT_MOVE_DEFAULT,
1216 	// Make sure the movement doesn't show above fog and reachmap.
1217 	LAYER_REACHMAP
1218 }};
1219 
1220 enum {
1221 	// you may adjust the following when needed:
1222 
1223 	// maximum border. 3 should be safe even if a larger border is in use somewhere
1224 	MAX_BORDER           = 3,
1225 
1226 	// store x, y, and layer in one 32 bit integer
1227 	// 4 most significant bits == layer group   => 16
1228 	BITS_FOR_LAYER_GROUP = 4,
1229 
1230 	// 10 second most significant bits == y     => 1024
1231 	BITS_FOR_Y           = 10,
1232 
1233 	// 1 third most significant bit == x parity => 2
1234 	BITS_FOR_X_PARITY    = 1,
1235 
1236 	// 8 fourth most significant bits == layer   => 256
1237 	BITS_FOR_LAYER       = 8,
1238 
1239 	// 9 least significant bits == x / 2        => 512 (really 1024 for x)
1240 	BITS_FOR_X_OVER_2    = 9
1241 };
1242 
drawing_buffer_key(const map_location & loc,drawing_layer layer)1243 inline display::drawing_buffer_key::drawing_buffer_key(const map_location &loc, drawing_layer layer)
1244 	: key_(0)
1245 {
1246 	// Start with the index of last group entry...
1247 	unsigned int group_i = layer_groups.size() - 1;
1248 
1249 	// ...and works backwards until the group containing the specified layer is found.
1250 	while(layer < layer_groups[group_i]) {
1251 		--group_i;
1252 	}
1253 
1254 	enum {
1255 		SHIFT_LAYER          = BITS_FOR_X_OVER_2,
1256 		SHIFT_X_PARITY       = BITS_FOR_LAYER + SHIFT_LAYER,
1257 		SHIFT_Y              = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
1258 		SHIFT_LAYER_GROUP    = BITS_FOR_Y + SHIFT_Y
1259 	};
1260 	static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key_) * 8, "Bit field too small");
1261 
1262 	// the parity of x must be more significant than the layer but less significant than y.
1263 	// Thus basically every row is split in two: First the row containing all the odd x
1264 	// then the row containing all the even x. Since thus the least significant bit of x is
1265 	// not required for x ordering anymore it can be shifted out to the right.
1266 	const unsigned int x_parity = static_cast<unsigned int>(loc.x) & 1;
1267 	key_  = (group_i << SHIFT_LAYER_GROUP) | (static_cast<unsigned int>(loc.y + MAX_BORDER) << SHIFT_Y);
1268 	key_ |= (x_parity << SHIFT_X_PARITY);
1269 	key_ |= (static_cast<unsigned int>(layer) << SHIFT_LAYER) | static_cast<unsigned int>(loc.x + MAX_BORDER) / 2;
1270 }
1271 
drawing_buffer_commit()1272 void display::drawing_buffer_commit()
1273 {
1274 	// std::list::sort() is a stable sort
1275 	drawing_buffer_.sort();
1276 
1277 	SDL_Rect clip_rect = map_area();
1278 	surface& screen = get_screen_surface();
1279 	clip_rect_setter set_clip_rect(screen, &clip_rect);
1280 
1281 	/*
1282 	 * Info regarding the rendering algorithm.
1283 	 *
1284 	 * In order to render a hex properly it needs to be rendered per row. On
1285 	 * this row several layers need to be drawn at the same time. Mainly the
1286 	 * unit and the background terrain. This is needed since both can spill
1287 	 * in the next hex. The foreground terrain needs to be drawn before to
1288 	 * avoid decapitation a unit.
1289 	 *
1290 	 * This ended in the following priority order:
1291 	 * layergroup > location > layer > 'blit_helper' > surface
1292 	 */
1293 
1294 	for (const blit_helper &blit : drawing_buffer_) {
1295 		for (const surface& surf : blit.surf()) {
1296 			// Note that dstrect can be changed by sdl_blit
1297 			// and so a new instance should be initialized
1298 			// to pass to each call to sdl_blit.
1299 			SDL_Rect dstrect {blit.x(), blit.y(), 0, 0};
1300 			SDL_Rect srcrect = blit.clip();
1301 			SDL_Rect *srcrectArg = (srcrect.x | srcrect.y | srcrect.w | srcrect.h)
1302 				? &srcrect : nullptr;
1303 			sdl_blit(surf, srcrectArg, screen, &dstrect);
1304 			//NOTE: the screen part should already be marked as 'to update'
1305 		}
1306 	}
1307 	drawing_buffer_clear();
1308 }
1309 
drawing_buffer_clear()1310 void display::drawing_buffer_clear()
1311 {
1312 	drawing_buffer_.clear();
1313 }
1314 
toggle_benchmark()1315 void display::toggle_benchmark()
1316 {
1317 	benchmark = !benchmark;
1318 }
1319 
toggle_debug_foreground()1320 void display::toggle_debug_foreground()
1321 {
1322 	debug_foreground = !debug_foreground;
1323 }
1324 
flip()1325 void display::flip()
1326 {
1327 	if(video().faked()) {
1328 		return;
1329 	}
1330 
1331 	surface& frameBuffer = video().getSurface();
1332 
1333 	font::draw_floating_labels(frameBuffer);
1334 	events::raise_volatile_draw_event();
1335 
1336 	video().flip();
1337 
1338 	events::raise_volatile_undraw_event();
1339 	font::undraw_floating_labels(frameBuffer);
1340 }
1341 
1342 // frametime is in milliseconds
calculate_fps(unsigned frametime)1343 static unsigned calculate_fps(unsigned frametime)
1344 {
1345 	return frametime != 0u ? 1000u / frametime : 999u;
1346 }
1347 
update_display()1348 void display::update_display()
1349 {
1350 	if (screen_.update_locked()) {
1351 		return;
1352 	}
1353 
1354 	if(preferences::show_fps() || benchmark) {
1355 		static int frames = 0;
1356 		++frames;
1357 		const int sample_freq = 10;
1358 		if(frames == sample_freq) {
1359 			const auto minmax_it = std::minmax_element(frametimes_.begin(), frametimes_.end());
1360 			const unsigned render_avg = std::accumulate(frametimes_.begin(), frametimes_.end(), 0) / frametimes_.size();
1361 			const int avg_fps = calculate_fps(render_avg);
1362 			const int max_fps = calculate_fps(*minmax_it.first);
1363 			const int min_fps = calculate_fps(*minmax_it.second);
1364 			frames = 0;
1365 
1366 			if(fps_handle_ != 0) {
1367 				font::remove_floating_label(fps_handle_);
1368 				fps_handle_ = 0;
1369 			}
1370 			std::ostringstream stream;
1371 			stream << "<tt>      min/avg/max/act</tt>\n";
1372 			stream << "<tt>FPS:  " << std::setfill(' ') << std::setw(3) << min_fps << '/'<< std::setw(3) << avg_fps << '/' << std::setw(3) << max_fps << '/' << std::setw(3) << fps_actual_ << "</tt>\n";
1373 			stream << "<tt>Time: " << std::setfill(' ') << std::setw(3) << *minmax_it.first << '/' << std::setw(3) << render_avg << '/' << std::setw(3) << *minmax_it.second << " ms</tt>\n";
1374 			if (game_config::debug) {
1375 				stream << "\nhex: " << drawn_hexes_*1.0/sample_freq;
1376 				if (drawn_hexes_ != invalidated_hexes_)
1377 					stream << " (" << (invalidated_hexes_-drawn_hexes_)*1.0/sample_freq << ")";
1378 			}
1379 			drawn_hexes_ = 0;
1380 			invalidated_hexes_ = 0;
1381 
1382 			font::floating_label flabel(stream.str());
1383 			flabel.set_font_size(12);
1384 			flabel.set_color(benchmark ? font::BAD_COLOR : font::NORMAL_COLOR);
1385 			flabel.set_position(10, 100);
1386 			flabel.set_alignment(font::LEFT_ALIGN);
1387 
1388 			fps_handle_ = font::add_floating_label(flabel);
1389 		}
1390 	} else if(fps_handle_ != 0) {
1391 		font::remove_floating_label(fps_handle_);
1392 		fps_handle_ = 0;
1393 		drawn_hexes_ = 0;
1394 		invalidated_hexes_ = 0;
1395 	}
1396 
1397 	flip();
1398 }
draw_panel(CVideo & video,const theme::panel & panel,std::vector<std::shared_ptr<gui::button>> &)1399 static void draw_panel(CVideo &video, const theme::panel& panel, std::vector<std::shared_ptr<gui::button>>& /*buttons*/)
1400 {
1401 	//log_scope("draw panel");
1402 	DBG_DP << "drawing panel " << panel.get_id() << "\n";
1403 
1404 	surface surf(image::get_image(panel.image()));
1405 
1406 	const SDL_Rect screen = video.screen_area();
1407 	SDL_Rect& loc = panel.location(screen);
1408 
1409 	DBG_DP << "panel location: x=" << loc.x << ", y=" << loc.y
1410 			<< ", w=" << loc.w << ", h=" << loc.h << "\n";
1411 
1412 	if(surf) {
1413 		if(surf->w != loc.w || surf->h != loc.h) {
1414 			surf = tile_surface(surf,loc.w,loc.h);
1415 		}
1416 		video.blit_surface(loc.x, loc.y, surf);
1417 	}
1418 }
1419 
draw_label(CVideo & video,surface target,const theme::label & label)1420 static void draw_label(CVideo& video, surface target, const theme::label& label)
1421 {
1422 	//log_scope("draw label");
1423 
1424 	const color_t& RGB = label.font_rgb();
1425 
1426 	std::string c_start="<";
1427 	std::string c_sep=",";
1428 	std::string c_end=">";
1429 	std::stringstream color;
1430 	color<< c_start << RGB.r << c_sep << RGB.g << c_sep << RGB.b << c_end;
1431 	std::string text = label.text();
1432 
1433 	if(label.font_rgb_set()) {
1434 		color<<text;
1435 		text = color.str();
1436 	}
1437 	const std::string& icon = label.icon();
1438 	SDL_Rect& loc = label.location(video.screen_area());
1439 
1440 	if(icon.empty() == false) {
1441 		surface surf(image::get_image(icon));
1442 		if(surf) {
1443 			if(surf->w > loc.w || surf->h > loc.h) {
1444 				surf = scale_surface(surf,loc.w,loc.h);
1445 			}
1446 
1447 			sdl_blit(surf,nullptr,target,&loc);
1448 		}
1449 
1450 		if(text.empty() == false) {
1451 			tooltips::add_tooltip(loc,text);
1452 		}
1453 	} else if(text.empty() == false) {
1454 		font::draw_text(&video,loc,label.font_size(),font::NORMAL_COLOR,text,loc.x,loc.y);
1455 	}
1456 
1457 }
1458 
draw_all_panels()1459 void display::draw_all_panels()
1460 {
1461 	surface& screen(screen_.getSurface());
1462 
1463 	/*
1464 	 * The minimap is also a panel, force it to update its contents.
1465 	 * This is required when the size of the minimap has been modified.
1466 	 */
1467 	recalculate_minimap();
1468 
1469 	for(const auto& panel : theme_.panels()) {
1470 		draw_panel(video(), panel, menu_buttons_);
1471 	}
1472 
1473 	for(const auto& label : theme_.labels()) {
1474 		draw_label(video(), screen, label);
1475 	}
1476 
1477 	render_buttons();
1478 }
1479 
draw_background(surface screen,const SDL_Rect & area,const std::string & image)1480 static void draw_background(surface screen, const SDL_Rect& area, const std::string& image)
1481 {
1482 	// No background image, just fill in black.
1483 	if(image.empty()) {
1484 		sdl::fill_rectangle(area, color_t(0, 0, 0));
1485 		return;
1486 	}
1487 
1488 	const surface background(image::get_image(image));
1489 	if(!background) {
1490 		return;
1491 	}
1492 
1493 	const unsigned int width = background->w;
1494 	const unsigned int height = background->h;
1495 
1496 	const unsigned int w_count = static_cast<int>(std::ceil(static_cast<double>(area.w) / static_cast<double>(width)));
1497 	const unsigned int h_count = static_cast<int>(std::ceil(static_cast<double>(area.h) / static_cast<double>(height)));
1498 
1499 	for(unsigned int w = 0, w_off = area.x; w < w_count; ++w, w_off += width) {
1500 		for(unsigned int h = 0, h_off = area.y; h < h_count; ++h, h_off += height) {
1501 			SDL_Rect clip = sdl::create_rect(w_off, h_off, 0, 0);
1502 			sdl_blit(background, nullptr, screen, &clip);
1503 		}
1504 	}
1505 }
1506 
draw_text_in_hex(const map_location & loc,const drawing_layer layer,const std::string & text,size_t font_size,color_t color,double x_in_hex,double y_in_hex)1507 void display::draw_text_in_hex(const map_location& loc,
1508 		const drawing_layer layer, const std::string& text,
1509 		size_t font_size, color_t color, double x_in_hex, double y_in_hex)
1510 {
1511 	if (text.empty()) return;
1512 
1513 	const size_t font_sz = static_cast<size_t>(font_size * get_zoom_factor());
1514 
1515 	surface text_surf = font::get_rendered_text(text, font_sz, color);
1516 	surface back_surf = font::get_rendered_text(text, font_sz, font::BLACK_COLOR);
1517 	const int x = get_location_x(loc) - text_surf->w/2
1518 	              + static_cast<int>(x_in_hex* hex_size());
1519 	const int y = get_location_y(loc) - text_surf->h/2
1520 	              + static_cast<int>(y_in_hex* hex_size());
1521 	for (int dy=-1; dy <= 1; ++dy) {
1522 		for (int dx=-1; dx <= 1; ++dx) {
1523 			if (dx!=0 || dy!=0) {
1524 				drawing_buffer_add(layer, loc, x + dx, y + dy, back_surf);
1525 			}
1526 		}
1527 	}
1528 	drawing_buffer_add(layer, loc, x, y, text_surf);
1529 }
1530 
1531 //TODO: convert this to use sdl::ttexture
render_image(int x,int y,const display::drawing_layer drawing_layer,const map_location & loc,surface image,bool hreverse,bool greyscale,fixed_t alpha,color_t blendto,double blend_ratio,double submerged,bool vreverse)1532 void display::render_image(int x, int y, const display::drawing_layer drawing_layer,
1533 		const map_location& loc, surface image,
1534 		bool hreverse, bool greyscale, fixed_t alpha,
1535 		color_t blendto, double blend_ratio, double submerged, bool vreverse)
1536 {
1537 	if (image==nullptr)
1538 		return;
1539 
1540 	SDL_Rect image_rect {x, y, image->w, image->h};
1541 	SDL_Rect clip_rect = map_area();
1542 	if (!sdl::rects_overlap(image_rect, clip_rect))
1543 		return;
1544 
1545 	surface surf(image);
1546 
1547 	if(hreverse) {
1548 		surf = image::reverse_image(surf);
1549 	}
1550 	if(vreverse) {
1551 		surf = flop_surface(surf);
1552 	}
1553 
1554 	if(greyscale) {
1555 		surf = greyscale_image(surf);
1556 	}
1557 
1558 	if(blend_ratio != 0) {
1559 		surf = blend_surface(surf, blend_ratio, blendto);
1560 	}
1561 	if(alpha > ftofxp(1.0)) {
1562 		surf = brighten_image(surf, alpha);
1563 	//} else if(alpha != 1.0 && blendto != 0) {
1564 	//	surf.assign(blend_surface(surf,1.0-alpha,blendto));
1565 	} else if(alpha != ftofxp(1.0)) {
1566 		surf = surf.clone();
1567 		adjust_surface_alpha(surf, alpha);
1568 	}
1569 
1570 	if(surf == nullptr) {
1571 		ERR_DP << "surface lost..." << std::endl;
1572 		return;
1573 	}
1574 
1575 	if(submerged > 0.0) {
1576 		// divide the surface into 2 parts
1577 		const int submerge_height = std::max<int>(0, surf->h*(1.0-submerged));
1578 		const int depth = surf->h - submerge_height;
1579 		SDL_Rect srcrect {0, 0, surf->w, submerge_height};
1580 		drawing_buffer_add(drawing_layer, loc, x, y, surf, srcrect);
1581 
1582 		if(submerge_height != surf->h) {
1583 			//the lower part will be transparent
1584 			float alpha_base = 0.3f; // 30% alpha at surface of water
1585 			float alpha_delta = 0.015f; // lose 1.5% per pixel depth
1586 			alpha_delta *= zoom_ / DefaultZoom; // adjust with zoom
1587 			surf = submerge_alpha(surf, depth, alpha_base, alpha_delta);
1588 
1589 			srcrect.y = submerge_height;
1590 			srcrect.h = surf->h-submerge_height;
1591 			y += submerge_height;
1592 
1593 			drawing_buffer_add(drawing_layer, loc, x, y, surf, srcrect);
1594 		}
1595 	} else {
1596 		// simple blit
1597 		drawing_buffer_add(drawing_layer, loc, x, y, surf);
1598 	}
1599 }
select_hex(map_location hex)1600 void display::select_hex(map_location hex)
1601 {
1602 	invalidate(selectedHex_);
1603 	selectedHex_ = hex;
1604 	invalidate(selectedHex_);
1605 	recalculate_minimap();
1606 }
1607 
highlight_hex(map_location hex)1608 void display::highlight_hex(map_location hex)
1609 {
1610 	invalidate(mouseoverHex_);
1611 	mouseoverHex_ = hex;
1612 	invalidate(mouseoverHex_);
1613 }
1614 
set_diagnostic(const std::string & msg)1615 void display::set_diagnostic(const std::string& msg)
1616 {
1617 	if(diagnostic_label_ != 0) {
1618 		font::remove_floating_label(diagnostic_label_);
1619 		diagnostic_label_ = 0;
1620 	}
1621 
1622 	if(!msg.empty()) {
1623 		font::floating_label flabel(msg);
1624 		flabel.set_font_size(font::SIZE_PLUS);
1625 		flabel.set_color(font::YELLOW_COLOR);
1626 		flabel.set_position(300, 50);
1627 		flabel.set_clip_rect(map_outside_area());
1628 
1629 		diagnostic_label_ = font::add_floating_label(flabel);
1630 	}
1631 }
1632 
draw_init()1633 void display::draw_init()
1634 {
1635 	if (get_map().empty()) {
1636 		return;
1637 	}
1638 
1639 	if(benchmark) {
1640 		invalidateAll_ = true;
1641 	}
1642 
1643 	if(!panelsDrawn_) {
1644 		draw_all_panels();
1645 		panelsDrawn_ = true;
1646 	}
1647 
1648 	if(redraw_background_) {
1649 		// Full redraw of the background
1650 		const SDL_Rect clip_rect = map_outside_area();
1651 		const surface& screen = get_screen_surface();
1652 		clip_rect_setter set_clip_rect(screen, &clip_rect);
1653 		SDL_FillRect(screen, &clip_rect, 0x00000000);
1654 		draw_background(screen, clip_rect, theme_.border().background_image);
1655 		redraw_background_ = false;
1656 
1657 		// Force a full map redraw
1658 		invalidateAll_ = true;
1659 	}
1660 
1661 	if(invalidateAll_) {
1662 		DBG_DP << "draw() with invalidateAll\n";
1663 
1664 		// toggle invalidateAll_ first to allow regular invalidations
1665 		invalidateAll_ = false;
1666 		invalidate_locations_in_rect(map_area());
1667 
1668 		redrawMinimap_ = true;
1669 	}
1670 }
1671 
draw_wrap(bool update,bool force)1672 void display::draw_wrap(bool update, bool force)
1673 {
1674 	static int time_between_draws = preferences::draw_delay();
1675 	if(time_between_draws < 0) {
1676 		time_between_draws = 1000 / screen_.current_refresh_rate();
1677 	}
1678 
1679 	if(redrawMinimap_) {
1680 		redrawMinimap_ = false;
1681 		draw_minimap();
1682 	}
1683 
1684 	if(update) {
1685 		update_display();
1686 
1687 		frametimes_.push_back(SDL_GetTicks() - last_frame_finished_);
1688 		fps_counter_++;
1689 		using std::chrono::duration_cast;
1690 		using std::chrono::seconds;
1691 		using std::chrono::steady_clock;
1692 		const seconds current_second = duration_cast<seconds>(steady_clock::now().time_since_epoch());
1693 		if(current_second != fps_start_) {
1694 			fps_start_ = current_second;
1695 			fps_actual_ = fps_counter_;
1696 			fps_counter_ = 0;
1697 		}
1698 		int longest_frame = *std::max_element(frametimes_.begin(), frametimes_.end());
1699 		int wait_time = time_between_draws - longest_frame;
1700 
1701 		if(!force && !benchmark && wait_time > 0) {
1702 			// If it's not time yet to draw, delay until it is
1703 			SDL_Delay(wait_time);
1704 		}
1705 
1706 		last_frame_finished_ = SDL_GetTicks();
1707 	}
1708 }
1709 
action_pressed()1710 const theme::action* display::action_pressed()
1711 {
1712 	for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
1713 		if((*i)->pressed()) {
1714 			const size_t index = std::distance(action_buttons_.begin(), i);
1715 			if(index >= theme_.actions().size()) {
1716 				assert(false);
1717 				return nullptr;
1718 			}
1719 			return &theme_.actions()[index];
1720 		}
1721 	}
1722 
1723 	return nullptr;
1724 }
1725 
menu_pressed()1726 const theme::menu* display::menu_pressed()
1727 {
1728 	for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) {
1729 		if((*i)->pressed()) {
1730 			const size_t index = std::distance(menu_buttons_.begin(), i);
1731 			if(index >= theme_.menus().size()) {
1732 				assert(false);
1733 				return nullptr;
1734 			}
1735 			return theme_.get_menu_item((*i)->id());
1736 		}
1737 	}
1738 
1739 	return nullptr;
1740 }
1741 
enable_menu(const std::string & item,bool enable)1742 void display::enable_menu(const std::string& item, bool enable)
1743 {
1744 	for(auto menu = theme_.menus().begin(); menu != theme_.menus().end(); ++menu) {
1745 
1746 		const auto hasitem = std::find_if(menu->items().begin(), menu->items().end(),
1747 			[&item](const config& c) { return c["id"].str() == item; }
1748 		);
1749 
1750 		if(hasitem != menu->items().end()) {
1751 			const size_t index = std::distance(theme_.menus().begin(), menu);
1752 			if(index >= menu_buttons_.size()) {
1753 				continue;
1754 			}
1755 			menu_buttons_[index]->enable(enable);
1756 		}
1757 	}
1758 }
1759 
announce(const std::string & message,const color_t & color,const announce_options & options)1760 void display::announce(const std::string& message, const color_t& color, const announce_options& options)
1761 {
1762 	if(options.discard_previous) {
1763 		font::remove_floating_label(prevLabel);
1764 	}
1765 	font::floating_label flabel(message);
1766 	flabel.set_font_size(font::SIZE_XLARGE);
1767 	flabel.set_color(color);
1768 	flabel.set_position(map_outside_area().x + map_outside_area().w/2,
1769 		map_outside_area().y + map_outside_area().h/3);
1770 	flabel.set_lifetime(options.lifetime);
1771 	flabel.set_clip_rect(map_outside_area());
1772 
1773 	prevLabel = font::add_floating_label(flabel);
1774 }
1775 
draw_minimap()1776 void display::draw_minimap()
1777 {
1778 	const SDL_Rect& area = minimap_area();
1779 
1780 	if(area.w == 0 || area.h == 0) {
1781 		return;
1782 	}
1783 
1784 	if(minimap_ == nullptr || minimap_->w > area.w || minimap_->h > area.h) {
1785 		minimap_ = image::getMinimap(area.w, area.h, get_map(),
1786 			dc_->teams().empty() ? nullptr : &dc_->teams()[currentTeam_],
1787 			(selectedHex_.valid() && !is_blindfolded()) ? &reach_map_ : nullptr);
1788 		if(minimap_ == nullptr) {
1789 			return;
1790 		}
1791 	}
1792 
1793 	const surface& screen(screen_.getSurface());
1794 	clip_rect_setter clip_setter(screen, &area);
1795 
1796 	color_t back_color {31,31,23,SDL_ALPHA_OPAQUE};
1797 	draw_centered_on_background(minimap_, area, back_color, screen);
1798 
1799 	//update the minimap location for mouse and units functions
1800 	minimap_location_.x = area.x + (area.w - minimap_->w) / 2;
1801 	minimap_location_.y = area.y + (area.h - minimap_->h) / 2;
1802 	minimap_location_.w = minimap_->w;
1803 	minimap_location_.h = minimap_->h;
1804 
1805 	draw_minimap_units();
1806 
1807 	// calculate the visible portion of the map:
1808 	// scaling between minimap and full map images
1809 	double xscaling = 1.0*minimap_->w / (get_map().w()*hex_width());
1810 	double yscaling = 1.0*minimap_->h / (get_map().h()*hex_size());
1811 
1812 	// we need to shift with the border size
1813 	// and the 0.25 from the minimap balanced drawing
1814 	// and the possible difference between real map and outside off-map
1815 	SDL_Rect map_rect = map_area();
1816 	SDL_Rect map_out_rect = map_outside_area();
1817 	double border = theme_.border().size;
1818 	double shift_x = - border*hex_width() - (map_out_rect.w - map_rect.w) / 2;
1819 	double shift_y = - (border+0.25)*hex_size() - (map_out_rect.h - map_rect.h) / 2;
1820 
1821 	int view_x = static_cast<int>((xpos_ + shift_x) * xscaling);
1822 	int view_y = static_cast<int>((ypos_ + shift_y) * yscaling);
1823 	int view_w = static_cast<int>(map_out_rect.w * xscaling);
1824 	int view_h = static_cast<int>(map_out_rect.h * yscaling);
1825 
1826 	SDL_Rect outline_rect {
1827 		minimap_location_.x + view_x - 1,
1828 		minimap_location_.y + view_y - 1,
1829 		view_w + 2,
1830 		view_h + 2
1831 	};
1832 
1833 	// SDL 2.0.10's render batching changes result in the
1834 	// surface's clipping rectangle being overridden even if
1835 	// no render clipping rectangle set operaton was queued,
1836 	// so let's not use the render API to draw the rectangle.
1837 
1838 	const SDL_Rect outline_parts[] = {
1839 		// top
1840 		{ outline_rect.x,                      outline_rect.y,                  outline_rect.w, 1              },
1841 		// bottom
1842 		{ outline_rect.x,                      outline_rect.y + outline_rect.h, outline_rect.w, 1              },
1843 		// left
1844 		{ outline_rect.x,                      outline_rect.y,                  1,              outline_rect.h },
1845 		// right
1846 		{ outline_rect.x + outline_rect.w - 1, outline_rect.y,                  1,              outline_rect.h },
1847 	};
1848 
1849 	for(const auto& r : outline_parts) {
1850 		SDL_FillRect(screen_.getSurface(), &r, 0x00FFFFFF);
1851 	}
1852 }
1853 
draw_minimap_units()1854 void display::draw_minimap_units()
1855 {
1856 	if (!preferences::minimap_draw_units() || is_blindfolded()) return;
1857 
1858 	double xscaling = 1.0 * minimap_location_.w / get_map().w();
1859 	double yscaling = 1.0 * minimap_location_.h / get_map().h();
1860 
1861 	for(const auto& u : dc_->units()) {
1862 		if (fogged(u.get_location()) ||
1863 		    (dc_->teams()[currentTeam_].is_enemy(u.side()) &&
1864 		     u.invisible(u.get_location(), *dc_)) ||
1865 			 u.get_hidden()) {
1866 			continue;
1867 		}
1868 
1869 		int side = u.side();
1870 		color_t col = team::get_minimap_color(side);
1871 
1872 		if (!preferences::minimap_movement_coding()) {
1873 
1874 			if (dc_->teams()[currentTeam_].is_enemy(side)) {
1875 				col = game_config::color_info(preferences::enemy_color()).rep();
1876 			} else {
1877 
1878 				if (currentTeam_ +1 == static_cast<unsigned>(side)) {
1879 
1880 					if (u.movement_left() == u.total_movement())
1881 						col = game_config::color_info(preferences::unmoved_color()).rep();
1882 					else if (u.movement_left() == 0)
1883 						col = game_config::color_info(preferences::moved_color()).rep();
1884 					else
1885 						col = game_config::color_info(preferences::partial_color()).rep();
1886 
1887 				} else
1888 					col = game_config::color_info(preferences::allied_color()).rep();
1889 			}
1890 		}
1891 
1892 		double u_x = u.get_location().x * xscaling;
1893 		double u_y = (u.get_location().y + (is_odd(u.get_location().x) ? 1 : -1)/4.0) * yscaling;
1894 		// use 4/3 to compensate the horizontal hexes imbrication
1895 		double u_w = 4.0 / 3.0 * xscaling;
1896 		double u_h = yscaling;
1897 
1898 		SDL_Rect r {
1899 				  minimap_location_.x + round_double(u_x)
1900 				, minimap_location_.y + round_double(u_y)
1901 				, round_double(u_w)
1902 				, round_double(u_h)
1903 		};
1904 
1905 		// SDL 2.0.10's render batching changes result in the
1906 		// surface's clipping rectangle being overridden even if
1907 		// no render clipping rectangle set operaton was queued,
1908 		// so let's not use the render API to draw the rectangle.
1909 
1910 		SDL_FillRect(screen_.getSurface(), &r, col.to_argb_bytes());
1911 	}
1912 }
1913 
scroll(int xmove,int ymove,bool force)1914 bool display::scroll(int xmove, int ymove, bool force)
1915 {
1916 	if(view_locked_ && !force) {
1917 		return false;
1918 	}
1919 
1920 	// No move offset, do nothing.
1921 	if(xmove == 0 && ymove == 0) {
1922 		return false;
1923 	}
1924 
1925 	int new_x = xpos_ + xmove;
1926 	int new_y = ypos_ + ymove;
1927 
1928 	bounds_check_position(new_x, new_y);
1929 
1930 	// Camera position doesn't change, exit.
1931 	if(xpos_ == new_x && ypos_ == new_y) {
1932 		return false;
1933 	}
1934 
1935 	const int diff_x = xpos_ - new_x;
1936 	const int diff_y = ypos_ - new_y;
1937 
1938 	xpos_ = new_x;
1939 	ypos_ = new_y;
1940 
1941 	/* Adjust floating label positions. This only affects labels whose position is anchored
1942 	 * to the map instead of the screen. In order to do that, we want to adjust their drawing
1943 	 * coordinates in the opposite direction of the screen scroll.
1944 	 *
1945 	 * The check a few lines up prevents any scrolling from happening if the camera position
1946 	 * doesn't change. Without that, the label still scroll even when the map edge is reached.
1947 	 * If that's removed, the following formula should work instead:
1948 	 *
1949 	 * const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
1950 	 */
1951 	font::scroll_floating_labels(diff_x, diff_y);
1952 
1953 	labels().recalculate_shroud();
1954 
1955 	//
1956 	// NOTE: the next three blocks can be removed once we switch to accelerated rendering.
1957 	//
1958 
1959 	if(!screen_.update_locked()) {
1960 		surface& screen(screen_.getSurface());
1961 
1962 		SDL_Rect dstrect = map_area();
1963 		dstrect.x += diff_x;
1964 		dstrect.y += diff_y;
1965 		dstrect = sdl::intersect_rects(dstrect, map_area());
1966 
1967 		SDL_Rect srcrect = dstrect;
1968 		srcrect.x -= diff_x;
1969 		srcrect.y -= diff_y;
1970 
1971 		// This is a workaround for a SDL2 bug when blitting on overlapping surfaces. The bug
1972 		// only strikes during scrolling, but will then duplicate textures across the entire map.
1973 		surface screen_copy = screen.clone();
1974 
1975 		SDL_SetSurfaceBlendMode(screen_copy, SDL_BLENDMODE_NONE);
1976 		SDL_BlitSurface(screen_copy, &srcrect, screen, &dstrect);
1977 	}
1978 
1979 	if(diff_y != 0) {
1980 		SDL_Rect r = map_area();
1981 
1982 		if(diff_y < 0) {
1983 			r.y = r.y + r.h + diff_y;
1984 		}
1985 
1986 		r.h = std::abs(diff_y);
1987 		invalidate_locations_in_rect(r);
1988 	}
1989 
1990 	if(diff_x != 0) {
1991 		SDL_Rect r = map_area();
1992 
1993 		if(diff_x < 0) {
1994 			r.x = r.x + r.w + diff_x;
1995 		}
1996 
1997 		r.w = std::abs(diff_x);
1998 		invalidate_locations_in_rect(r);
1999 	}
2000 
2001 	scroll_event_.notify_observers();
2002 
2003 	redrawMinimap_ = true;
2004 
2005 	return true;
2006 }
2007 
zoom_at_max()2008 bool display::zoom_at_max()
2009 {
2010 	return zoom_ == MaxZoom;
2011 }
2012 
zoom_at_min()2013 bool display::zoom_at_min()
2014 {
2015 	return zoom_ == MinZoom;
2016 }
2017 
set_zoom(bool increase)2018 bool display::set_zoom(bool increase)
2019 {
2020 	// Ensure we don't try to access nonexistent vector indices.
2021 	zoom_index_ = utils::clamp(increase ? zoom_index_ + 1 : zoom_index_ - 1, 0, final_zoom_index);
2022 
2023 	// No validation check is needed in the next step since we've already set the index here and
2024 	// know the new zoom value is indeed valid.
2025 	return set_zoom(zoom_levels[zoom_index_], false);
2026 }
2027 
set_zoom(unsigned int amount,const bool validate_value_and_set_index)2028 bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_index)
2029 {
2030 	unsigned int new_zoom = utils::clamp(amount, MinZoom, MaxZoom);
2031 
2032 	LOG_DP << "new_zoom = " << new_zoom << std::endl;
2033 
2034 	if(new_zoom == zoom_) {
2035 		return false;
2036 	}
2037 
2038 	// Confirm this is indeed a valid zoom level.
2039 	if(validate_value_and_set_index) {
2040 		auto iter = std::lower_bound(zoom_levels.begin(), zoom_levels.end(), new_zoom);
2041 
2042 		if(iter == zoom_levels.end()) {
2043 			// This should never happen, since the value was already clamped earlier
2044 			return false;
2045 		} else if(iter != zoom_levels.begin()) {
2046 			float diff = *iter - *(iter - 1);
2047 			float lower = (new_zoom - *(iter - 1)) / diff;
2048 			float upper = (*iter - new_zoom) / diff;
2049 			if(lower < upper) {
2050 				// It's actually closer to the previous element.
2051 				iter--;
2052 			}
2053 		}
2054 
2055 		new_zoom = *iter;
2056 		zoom_index_ = std::distance(zoom_levels.begin(), iter);
2057 	}
2058 
2059 	const SDL_Rect& outside_area = map_outside_area();
2060 	const SDL_Rect& area = map_area();
2061 
2062 	//Turn the zoom factor to a double in order to avoid rounding errors.
2063 	double zoom_factor = static_cast<double>(new_zoom) / static_cast<double>(zoom_);
2064 
2065 	// INVARIANT: xpos_ + area.w == xend where xend is as in bounds_check_position()
2066 	//
2067 	// xpos_: Position of the leftmost visible map pixel of the viewport, in pixels.
2068 	// Affected by the current zoom: this->zoom_ pixels to the hex.
2069 	//
2070 	// xpos_ + area.w/2: Position of the center of the viewport, in pixels.
2071 	//
2072 	// (xpos_ + area.w/2) * new_zoom/zoom_: Position of the center of the
2073 	// viewport, as it would be under new_zoom.
2074 	//
2075 	// (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
2076 	// leftmost visible map pixel, as it would be under new_zoom.
2077 	xpos_ = round_double(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2));
2078 	ypos_ = round_double(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2));
2079 	xpos_ -= (outside_area.w - area.w) / 2;
2080 	ypos_ -= (outside_area.h - area.h) / 2;
2081 
2082 	zoom_ = new_zoom;
2083 	bounds_check_position(xpos_, ypos_);
2084 	if(zoom_ != DefaultZoom) {
2085 		last_zoom_ = zoom_;
2086 	}
2087 
2088 	image::set_zoom(zoom_);
2089 
2090 	labels().recalculate_labels();
2091 	redraw_background_ = true;
2092 	invalidate_all();
2093 
2094 	// Forces a redraw after zooming.
2095 	// This prevents some graphic glitches from occurring.
2096 	draw();
2097 
2098 	return true;
2099 }
2100 
set_default_zoom()2101 void display::set_default_zoom()
2102 {
2103 	if (zoom_ != DefaultZoom) {
2104 		last_zoom_ = zoom_;
2105 		set_zoom(DefaultZoom);
2106 	} else {
2107 		// When we are already at the default zoom,
2108 		// switch to the last zoom used
2109 		set_zoom(last_zoom_);
2110 	}
2111 }
2112 
tile_fully_on_screen(const map_location & loc) const2113 bool display::tile_fully_on_screen(const map_location& loc) const
2114 {
2115 	int x = get_location_x(loc);
2116 	int y = get_location_y(loc);
2117 	return !outside_area(map_area(), x, y);
2118 }
2119 
tile_nearly_on_screen(const map_location & loc) const2120 bool display::tile_nearly_on_screen(const map_location& loc) const
2121 {
2122 	int x = get_location_x(loc);
2123 	int y = get_location_y(loc);
2124 	const SDL_Rect &area = map_area();
2125 	int hw = hex_width(), hs = hex_size();
2126 	return x + hs >= area.x - hw && x < area.x + area.w + hw &&
2127 	       y + hs >= area.y - hs && y < area.y + area.h + hs;
2128 }
2129 
scroll_to_xy(int screenxpos,int screenypos,SCROLL_TYPE scroll_type,bool force)2130 void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force)
2131 {
2132 	if(!force && (view_locked_ || !preferences::scroll_to_action())) return;
2133 	if(screen_.update_locked()) {
2134 		return;
2135 	}
2136 	const SDL_Rect area = map_area();
2137 	const int xmove_expected = screenxpos - (area.x + area.w/2);
2138 	const int ymove_expected = screenypos - (area.y + area.h/2);
2139 
2140 	int xpos = xpos_ + xmove_expected;
2141 	int ypos = ypos_ + ymove_expected;
2142 	bounds_check_position(xpos, ypos);
2143 	int xmove = xpos - xpos_;
2144 	int ymove = ypos - ypos_;
2145 
2146 	if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || preferences::scroll_speed() > 99) {
2147 		scroll(xmove,ymove,true);
2148 		draw();
2149 		return;
2150 	}
2151 
2152 	// Doing an animated scroll, with acceleration etc.
2153 
2154 	int x_old = 0;
2155 	int y_old = 0;
2156 
2157 	const double dist_total = hypot(xmove, ymove);
2158 	double dist_moved = 0.0;
2159 
2160 	int t_prev = SDL_GetTicks();
2161 
2162 	double velocity = 0.0;
2163 	while (dist_moved < dist_total) {
2164 		events::pump();
2165 
2166 		int t = SDL_GetTicks();
2167 		double dt = (t - t_prev) / 1000.0;
2168 		if (dt > 0.200) {
2169 			// Do not skip too many frames on slow PCs
2170 			dt = 0.200;
2171 		}
2172 		t_prev = t;
2173 
2174 		const double accel_time = 0.3 / turbo_speed(); // seconds until full speed is reached
2175 		const double decel_time = 0.4 / turbo_speed(); // seconds from full speed to stop
2176 
2177 		double velocity_max = preferences::scroll_speed() * 60.0;
2178 		velocity_max *= turbo_speed();
2179 		double accel = velocity_max / accel_time;
2180 		double decel = velocity_max / decel_time;
2181 
2182 		// If we started to decelerate now, where would we stop?
2183 		double stop_time = velocity / decel;
2184 		double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time;
2185 		if (dist_stop > dist_total || velocity > velocity_max) {
2186 			velocity -= decel * dt;
2187 			if (velocity < 1.0) velocity = 1.0;
2188 		} else {
2189 			velocity += accel * dt;
2190 			if (velocity > velocity_max) velocity = velocity_max;
2191 		}
2192 
2193 		dist_moved += velocity * dt;
2194 		if (dist_moved > dist_total) dist_moved = dist_total;
2195 
2196 		int x_new = round_double(xmove * dist_moved / dist_total);
2197 		int y_new = round_double(ymove * dist_moved / dist_total);
2198 
2199 		int dx = x_new - x_old;
2200 		int dy = y_new - y_old;
2201 
2202 		scroll(dx,dy,true);
2203 		x_old += dx;
2204 		y_old += dy;
2205 		draw();
2206 	}
2207 }
2208 
scroll_to_tile(const map_location & loc,SCROLL_TYPE scroll_type,bool check_fogged,bool force)2209 void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, bool check_fogged, bool force)
2210 {
2211 	if(get_map().on_board(loc) == false) {
2212 		ERR_DP << "Tile at " << loc << " isn't on the map, can't scroll to the tile." << std::endl;
2213 		return;
2214 	}
2215 
2216 	std::vector<map_location> locs;
2217 	locs.push_back(loc);
2218 	scroll_to_tiles(locs, scroll_type, check_fogged,false,0.0,force);
2219 }
2220 
scroll_to_tiles(map_location loc1,map_location loc2,SCROLL_TYPE scroll_type,bool check_fogged,double add_spacing,bool force)2221 void display::scroll_to_tiles(map_location loc1, map_location loc2,
2222                               SCROLL_TYPE scroll_type, bool check_fogged,
2223 			      double add_spacing, bool force)
2224 {
2225 	std::vector<map_location> locs;
2226 	locs.push_back(loc1);
2227 	locs.push_back(loc2);
2228 	scroll_to_tiles(locs, scroll_type, check_fogged, false, add_spacing,force);
2229 }
2230 
scroll_to_tiles(const std::vector<map_location>::const_iterator & begin,const std::vector<map_location>::const_iterator & end,SCROLL_TYPE scroll_type,bool check_fogged,bool only_if_possible,double add_spacing,bool force)2231 void display::scroll_to_tiles(const std::vector<map_location>::const_iterator & begin,
2232                               const std::vector<map_location>::const_iterator & end,
2233                               SCROLL_TYPE scroll_type, bool check_fogged,
2234                               bool only_if_possible, double add_spacing, bool force)
2235 {
2236 	// basically we calculate the min/max coordinates we want to have on-screen
2237 	int minx = 0;
2238 	int maxx = 0;
2239 	int miny = 0;
2240 	int maxy = 0;
2241 	bool valid = false;
2242 
2243 	for(std::vector<map_location>::const_iterator itor = begin; itor != end ; ++itor) {
2244 		if(get_map().on_board(*itor) == false) continue;
2245 		if(check_fogged && fogged(*itor)) continue;
2246 
2247 		int x = get_location_x(*itor);
2248 		int y = get_location_y(*itor);
2249 
2250 		if (!valid) {
2251 			minx = x;
2252 			maxx = x;
2253 			miny = y;
2254 			maxy = y;
2255 			valid = true;
2256 		} else {
2257 			int minx_new = std::min<int>(minx,x);
2258 			int miny_new = std::min<int>(miny,y);
2259 			int maxx_new = std::max<int>(maxx,x);
2260 			int maxy_new = std::max<int>(maxy,y);
2261 			SDL_Rect r = map_area();
2262 			r.x = minx_new;
2263 			r.y = miny_new;
2264 			if(outside_area(r, maxx_new, maxy_new)) {
2265 				// we cannot fit all locations to the screen
2266 				if (only_if_possible) return;
2267 				break;
2268 			}
2269 			minx = minx_new;
2270 			miny = miny_new;
2271 			maxx = maxx_new;
2272 			maxy = maxy_new;
2273 		}
2274 	}
2275 	//if everything is fogged or the location list is empty
2276 	if(!valid) return;
2277 
2278 	if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2279 		SDL_Rect r = map_area();
2280 		int spacing = round_double(add_spacing*hex_size());
2281 		r.x += spacing;
2282 		r.y += spacing;
2283 		r.w -= 2*spacing;
2284 		r.h -= 2*spacing;
2285 		if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) {
2286 			return;
2287 		}
2288 	}
2289 
2290 	// let's do "normal" rectangle math from now on
2291 	SDL_Rect locs_bbox;
2292 	locs_bbox.x = minx;
2293 	locs_bbox.y = miny;
2294 	locs_bbox.w = maxx - minx + hex_size();
2295 	locs_bbox.h = maxy - miny + hex_size();
2296 
2297 	// target the center
2298 	int target_x = locs_bbox.x + locs_bbox.w/2;
2299 	int target_y = locs_bbox.y + locs_bbox.h/2;
2300 
2301 	if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2302 		// when doing an ONSCREEN scroll we do not center the target unless needed
2303 		SDL_Rect r = map_area();
2304 		int map_center_x = r.x + r.w/2;
2305 		int map_center_y = r.y + r.h/2;
2306 
2307 		int h = r.h;
2308 		int w = r.w;
2309 
2310 		// we do not want to be only inside the screen rect, but center a bit more
2311 		double inside_frac = 0.5; // 0.0 = always center the target, 1.0 = scroll the minimum distance
2312 		w = static_cast<int>(w * inside_frac);
2313 		h = static_cast<int>(h * inside_frac);
2314 
2315 		// shrink the rectangle by the size of the locations rectangle we found
2316 		// such that the new task to fit a point into a rectangle instead of rectangle into rectangle
2317 		w -= locs_bbox.w;
2318 		h -= locs_bbox.h;
2319 
2320 		if (w < 1) w = 1;
2321 		if (h < 1) h = 1;
2322 
2323 		r.x = target_x - w/2;
2324 		r.y = target_y - h/2;
2325 		r.w = w;
2326 		r.h = h;
2327 
2328 		// now any point within r is a possible target to scroll to
2329 		// we take the one with the minimum distance to map_center
2330 		// which will always be at the border of r
2331 
2332 		if (map_center_x < r.x) {
2333 			target_x = r.x;
2334 			target_y = map_center_y;
2335 			if (target_y < r.y) target_y = r.y;
2336 			if (target_y > r.y+r.h-1) target_y = r.y+r.h-1;
2337 		} else if (map_center_x > r.x+r.w-1) {
2338 			target_x = r.x+r.w-1;
2339 			target_y = map_center_y;
2340 			if (target_y < r.y) target_y = r.y;
2341 			if (target_y >= r.y+r.h) target_y = r.y+r.h-1;
2342 		} else if (map_center_y < r.y) {
2343 			target_y = r.y;
2344 			target_x = map_center_x;
2345 			if (target_x < r.x) target_x = r.x;
2346 			if (target_x > r.x+r.w-1) target_x = r.x+r.w-1;
2347 		} else if (map_center_y > r.y+r.h-1) {
2348 			target_y = r.y+r.h-1;
2349 			target_x = map_center_x;
2350 			if (target_x < r.x) target_x = r.x;
2351 			if (target_x > r.x+r.w-1) target_x = r.x+r.w-1;
2352 		} else {
2353 			ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all..." << std::endl;
2354 			// keep the target at the center
2355 		}
2356 	}
2357 
2358 	scroll_to_xy(target_x, target_y,scroll_type,force);
2359 }
2360 
2361 
bounds_check_position()2362 void display::bounds_check_position()
2363 {
2364 	const unsigned int orig_zoom = zoom_;
2365 
2366 	if(zoom_ < MinZoom) {
2367 		zoom_ = MinZoom;
2368 	}
2369 
2370 	if(zoom_ > MaxZoom) {
2371 		zoom_ = MaxZoom;
2372 	}
2373 
2374 	bounds_check_position(xpos_, ypos_);
2375 
2376 	if(zoom_ != orig_zoom) {
2377 		image::set_zoom(zoom_);
2378 	}
2379 }
2380 
bounds_check_position(int & xpos,int & ypos) const2381 void display::bounds_check_position(int& xpos, int& ypos) const
2382 {
2383 	const int tile_width = hex_width();
2384 
2385 	// Adjust for the border 2 times
2386 	const int xend = static_cast<int>(tile_width * (get_map().w() + 2 * theme_.border().size) + tile_width/3);
2387 	const int yend = static_cast<int>(zoom_ * (get_map().h() + 2 * theme_.border().size) + zoom_/2);
2388 
2389 	if(xpos > xend - map_area().w) {
2390 		xpos = xend - map_area().w;
2391 	}
2392 
2393 	if(ypos > yend - map_area().h) {
2394 		ypos = yend - map_area().h;
2395 	}
2396 
2397 	if(xpos < 0) {
2398 		xpos = 0;
2399 	}
2400 
2401 	if(ypos < 0) {
2402 		ypos = 0;
2403 	}
2404 }
2405 
turbo_speed() const2406 double display::turbo_speed() const
2407 {
2408 	bool res = turbo_;
2409 	if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT]) {
2410 		res = !res;
2411 	}
2412 
2413 	res |= screen_.faked();
2414 	if (res)
2415 		return turbo_speed_;
2416 	else
2417 		return 1.0;
2418 }
2419 
set_idle_anim_rate(int rate)2420 void display::set_idle_anim_rate(int rate)
2421 {
2422 	idle_anim_rate_ = std::pow(2.0, -rate/10.0);
2423 }
2424 
redraw_everything()2425 void display::redraw_everything()
2426 {
2427 	if(screen_.update_locked())
2428 		return;
2429 
2430 	invalidateGameStatus_ = true;
2431 
2432 	reportRects_.clear();
2433 	reportSurfaces_.clear();
2434 	reports_.clear();
2435 
2436 	bounds_check_position();
2437 
2438 	tooltips::clear_tooltips();
2439 
2440 	theme_.set_resolution(screen_.screen_area());
2441 
2442 	if(!menu_buttons_.empty() || !action_buttons_.empty()) {
2443 		create_buttons();
2444 	}
2445 
2446 	if(resources::controller) {
2447 		hotkey::command_executor* command_executor = resources::controller->get_hotkey_command_executor();
2448 		if(command_executor != nullptr)	{
2449 			// This function adds button overlays,
2450 			// it needs to be run after recreating the buttons.
2451 			command_executor->set_button_state();
2452 		}
2453 	}
2454 
2455 	panelsDrawn_ = false;
2456 	if (!gui::in_dialog()) {
2457 		labels().recalculate_labels();
2458 	}
2459 
2460 	redraw_background_ = true;
2461 
2462 	for(std::function<void(display&)> f : redraw_observers_) {
2463 		f(*this);
2464 	}
2465 
2466 	int ticks1 = SDL_GetTicks();
2467 	invalidate_all();
2468 	int ticks2 = SDL_GetTicks();
2469 	draw(true,true);
2470 	int ticks3 = SDL_GetTicks();
2471 	LOG_DP << "invalidate and draw: " << (ticks3 - ticks2) << " and " << (ticks2 - ticks1) << "\n";
2472 
2473 	complete_redraw_event_.notify_observers();
2474 }
2475 
add_redraw_observer(std::function<void (display &)> f)2476 void display::add_redraw_observer(std::function<void(display&)> f)
2477 {
2478 	redraw_observers_.push_back(f);
2479 }
2480 
clear_redraw_observers()2481 void display::clear_redraw_observers()
2482 {
2483 	redraw_observers_.clear();
2484 }
2485 
draw()2486 void display::draw() {
2487 	draw(true, false);
2488 }
2489 
draw(bool update)2490 void display::draw(bool update) {
2491 	draw(update, false);
2492 }
2493 
2494 
draw(bool update,bool force)2495 void display::draw(bool update,bool force) {
2496 //	log_scope("display::draw");
2497 
2498 	if (screen_.update_locked()) {
2499 		return;
2500 	}
2501 
2502 	if (dirty_) {
2503 		flip_locker flip_lock(screen_);
2504 		dirty_ = false;
2505 		redraw_everything();
2506 		return;
2507 	}
2508 
2509 	// Trigger cache rebuild when preference gets changed
2510 	if (animate_water_ != preferences::animate_water()) {
2511 		animate_water_ = preferences::animate_water();
2512 		builder_->rebuild_cache_all();
2513 	}
2514 
2515 	set_scontext_unsynced leave_synced_context;
2516 
2517 	draw_init();
2518 	pre_draw();
2519 	// invalidate all that needs to be invalidated
2520 	invalidate_animations();
2521 
2522 	if(!get_map().empty()) {
2523 		//int simulate_delay = 0;
2524 
2525 		/*
2526 		 * draw_invalidated() also invalidates the halos, so also needs to be
2527 		 * ran if invalidated_.empty() == true.
2528 		 */
2529 		if(!invalidated_.empty() || preferences::show_haloes()) {
2530 			draw_invalidated();
2531 			invalidated_.clear();
2532 		}
2533 		drawing_buffer_commit();
2534 		post_commit();
2535 		draw_sidebar();
2536 
2537 		// Simulate slow PC:
2538 		//SDL_Delay(2*simulate_delay + rand() % 20);
2539 	}
2540 	draw_wrap(update, force);
2541 	post_draw();
2542 }
2543 
labels()2544 map_labels& display::labels()
2545 {
2546 	return *map_labels_;
2547 }
2548 
labels() const2549 const map_labels& display::labels() const
2550 {
2551 	return *map_labels_;
2552 }
2553 
get_clip_rect()2554 const SDL_Rect& display::get_clip_rect()
2555 {
2556 	return map_area();
2557 }
2558 
draw_invalidated()2559 void display::draw_invalidated() {
2560 //	log_scope("display::draw_invalidated");
2561 	SDL_Rect clip_rect = get_clip_rect();
2562 	surface& screen = get_screen_surface();
2563 	clip_rect_setter set_clip_rect(screen, &clip_rect);
2564 	for (const map_location& loc : invalidated_) {
2565 		int xpos = get_location_x(loc);
2566 		int ypos = get_location_y(loc);
2567 
2568 		//const bool on_map = get_map().on_board(loc);
2569 		SDL_Rect hex_rect = sdl::create_rect(xpos, ypos, zoom_, zoom_);
2570 		if(!sdl::rects_overlap(hex_rect,clip_rect)) {
2571 			continue;
2572 		}
2573 		draw_hex(loc);
2574 		drawn_hexes_+=1;
2575 	}
2576 	invalidated_hexes_ += invalidated_.size();
2577 
2578 	if (dc_->teams().empty())
2579 	{
2580 		// The unit drawer can't function without teams
2581 		return;
2582 	}
2583 
2584 	unit_drawer drawer = unit_drawer(*this);
2585 
2586 	for (const map_location& loc : invalidated_) {
2587 		unit_map::const_iterator u_it = dc_->units().find(loc);
2588 		exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc);
2589 		if (u_it != dc_->units().end()
2590 				&& (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id()))
2591 			drawer.redraw_unit(*u_it);
2592 	}
2593 
2594 }
2595 
draw_hex(const map_location & loc)2596 void display::draw_hex(const map_location& loc) {
2597 	int xpos = get_location_x(loc);
2598 	int ypos = get_location_y(loc);
2599 	image::TYPE image_type = get_image_type(loc);
2600 	const bool on_map = get_map().on_board(loc);
2601 	const time_of_day& tod = get_time_of_day(loc);
2602 
2603 	int num_images_fg = 0;
2604 	int num_images_bg = 0;
2605 
2606 	if(!shrouded(loc)) {
2607 		// unshrouded terrain (the normal case)
2608 		get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
2609 		drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, terrain_image_vector_);
2610 		num_images_bg = terrain_image_vector_.size();
2611 
2612 		get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
2613 		drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, terrain_image_vector_);
2614 		num_images_fg = terrain_image_vector_.size();
2615 
2616 		// Draw the grid, if that's been enabled
2617 		if(grid_) {
2618 			static const image::locator grid_top(game_config::images::grid_top);
2619 			drawing_buffer_add(LAYER_GRID_TOP, loc, xpos, ypos,
2620 				image::get_image(grid_top, image::TOD_COLORED));
2621 			static const image::locator grid_bottom(game_config::images::grid_bottom);
2622 			drawing_buffer_add(LAYER_GRID_BOTTOM, loc, xpos, ypos,
2623 				image::get_image(grid_bottom, image::TOD_COLORED));
2624 		}
2625 	}
2626 
2627 	if(!shrouded(loc)) {
2628 		typedef overlay_map::const_iterator Itor;
2629 		std::pair<Itor,Itor> overlays = overlays_->equal_range(loc);
2630 		const bool have_overlays = overlays.first != overlays.second;
2631 
2632 		if(have_overlays) {
2633 			tod_color tod_col = tod.color + color_adjust_;
2634 			image::light_string lt = image::get_light_string(-1, tod_col.r, tod_col.g, tod_col.b);
2635 
2636 			for( ; overlays.first != overlays.second; ++overlays.first) {
2637 				if ((overlays.first->second.team_name.empty() ||
2638 						overlays.first->second.team_name.find(dc_->teams()[viewing_team()].team_name()) != std::string::npos)
2639 						&& !(fogged(loc) && !overlays.first->second.visible_in_fog))
2640 				{
2641 
2642 					const std::string image = overlays.first->second.image;
2643 					const surface surf = image.find("~NO_TOD_SHIFT()") == std::string::npos ?
2644 						image::get_lighted_image(image, lt, image::SCALED_TO_HEX) : image::get_image(image, image::SCALED_TO_HEX);
2645 					drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, surf);
2646 				}
2647 			}
2648 		}
2649 	}
2650 
2651 	if(!shrouded(loc)) {
2652 		// village-control flags.
2653 		drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, get_flag(loc));
2654 	}
2655 
2656 	// Draw the time-of-day mask on top of the terrain in the hex.
2657 	// tod may differ from tod if hex is illuminated.
2658 	const std::string& tod_hex_mask = tod.image_mask;
2659 	if(tod_hex_mask1 != nullptr || tod_hex_mask2 != nullptr) {
2660 		drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask1);
2661 		drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask2);
2662 	} else if(!tod_hex_mask.empty()) {
2663 		drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos,
2664 			image::get_image(tod_hex_mask,image::SCALED_TO_HEX));
2665 	}
2666 
2667 	// Paint mouseover overlays
2668 	if(loc == mouseoverHex_ && (on_map || (in_editor() && get_map().on_board_with_border(loc)))
2669 			&& mouseover_hex_overlay_ != nullptr) {
2670 		drawing_buffer_add(LAYER_MOUSEOVER_OVERLAY, loc, xpos, ypos, mouseover_hex_overlay_);
2671 	}
2672 
2673 	// Paint arrows
2674 	arrows_map_t::const_iterator arrows_in_hex = arrows_map_.find(loc);
2675 	if(arrows_in_hex != arrows_map_.end()) {
2676 		for (arrow* const a : arrows_in_hex->second) {
2677 			a->draw_hex(loc);
2678 		}
2679 	}
2680 
2681 	// Apply shroud, fog and linger overlay
2682 
2683 	if(shrouded(loc)) {
2684 		// We apply void also on off-map tiles
2685 		// to shroud the half-hexes too
2686 		const std::string& shroud_image = get_variant(shroud_images_, loc);
2687 		drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos,
2688 			image::get_image(shroud_image, image_type));
2689 	} else if(fogged(loc)) {
2690 		const std::string& fog_image = get_variant(fog_images_, loc);
2691 		drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos,
2692 			image::get_image(fog_image, image_type));
2693 	}
2694 
2695 	if(!shrouded(loc)) {
2696 		drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos, get_fog_shroud_images(loc, image_type));
2697 	}
2698 
2699 	if (on_map) {
2700 		if (draw_coordinates_) {
2701 			int off_x = xpos + hex_size()/2;
2702 			int off_y = ypos + hex_size()/2;
2703 			surface text = font::get_rendered_text(lexical_cast<std::string>(loc), font::SIZE_SMALL, font::NORMAL_COLOR);
2704 			surface bg(text->w, text->h);
2705 			SDL_Rect bg_rect {0, 0, text->w, text->h};
2706 			sdl::fill_surface_rect(bg, &bg_rect, 0xaa000000);
2707 			off_x -= text->w / 2;
2708 			off_y -= text->h / 2;
2709 			if (draw_terrain_codes_) {
2710 				off_y -= text->h / 2;
2711 			}
2712 			if (draw_num_of_bitmaps_) {
2713 				off_y -= text->h / 2;
2714 			}
2715 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg);
2716 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text);
2717 		}
2718 		if (draw_terrain_codes_ && (game_config::debug || !shrouded(loc))) {
2719 			int off_x = xpos + hex_size()/2;
2720 			int off_y = ypos + hex_size()/2;
2721 			surface text = font::get_rendered_text(lexical_cast<std::string>(get_map().get_terrain(loc)), font::SIZE_SMALL, font::NORMAL_COLOR);
2722 			surface bg(text->w, text->h);
2723 			SDL_Rect bg_rect {0, 0, text->w, text->h};
2724 			sdl::fill_surface_rect(bg, &bg_rect, 0xaa000000);
2725 			off_x -= text->w / 2;
2726 			off_y -= text->h / 2;
2727 			if (draw_coordinates_ && !draw_num_of_bitmaps_) {
2728 				off_y += text->h / 2;
2729 			} else if (draw_num_of_bitmaps_ && !draw_coordinates_) {
2730 				off_y -= text->h / 2;
2731 			}
2732 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg);
2733 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text);
2734 		}
2735 		if (draw_num_of_bitmaps_) {
2736 			int off_x = xpos + hex_size()/2;
2737 			int off_y = ypos + hex_size()/2;
2738 			surface text = font::get_rendered_text(lexical_cast<std::string>(num_images_bg + num_images_fg), font::SIZE_SMALL, font::NORMAL_COLOR);
2739 			surface bg(text->w, text->h);
2740 			SDL_Rect bg_rect {0, 0, text->w, text->h};
2741 			sdl::fill_surface_rect(bg, &bg_rect, 0xaa000000);
2742 			off_x -= text->w / 2;
2743 			off_y -= text->h / 2;
2744 			if (draw_coordinates_) {
2745 				off_y += text->h / 2;
2746 			}
2747 			if (draw_terrain_codes_) {
2748 				off_y += text->h / 2;
2749 			}
2750 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg);
2751 			drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text);
2752 		}
2753 	}
2754 
2755 	if(debug_foreground) {
2756 		drawing_buffer_add(LAYER_UNIT_DEFAULT, loc, xpos, ypos,
2757 			image::get_image("terrain/foreground.png", image_type));
2758 	}
2759 
2760 }
2761 
get_image_type(const map_location &)2762 image::TYPE display::get_image_type(const map_location& /*loc*/) {
2763 	return image::TOD_COLORED;
2764 }
2765 
2766 /*void display::draw_sidebar() {
2767 
2768 }*/
2769 
draw_image_for_report(surface & img,SDL_Rect & rect)2770 void display::draw_image_for_report(surface& img, SDL_Rect& rect)
2771 {
2772 	SDL_Rect visible_area = get_non_transparent_portion(img);
2773 	SDL_Rect target = rect;
2774 	if(visible_area.x != 0 || visible_area.y != 0 || visible_area.w != img->w || visible_area.h != img->h) {
2775 		if(visible_area.w == 0 || visible_area.h == 0) {
2776 			return;
2777 		}
2778 
2779 		if(visible_area.w > rect.w || visible_area.h > rect.h) {
2780 			img = get_surface_portion(img,visible_area);
2781 			img = scale_surface(img,rect.w,rect.h);
2782 			visible_area.x = 0;
2783 			visible_area.y = 0;
2784 			visible_area.w = img->w;
2785 			visible_area.h = img->h;
2786 		} else {
2787 			target.x = rect.x + (rect.w - visible_area.w)/2;
2788 			target.y = rect.y + (rect.h - visible_area.h)/2;
2789 			target.w = visible_area.w;
2790 			target.h = visible_area.h;
2791 		}
2792 
2793 		sdl_blit(img,&visible_area,screen_.getSurface(),&target);
2794 	} else {
2795 		if(img->w != rect.w || img->h != rect.h) {
2796 			img = scale_surface(img,rect.w,rect.h);
2797 		}
2798 
2799 		sdl_blit(img,nullptr,screen_.getSurface(),&target);
2800 	}
2801 }
2802 
2803 /**
2804  * Redraws the specified report (if anything has changed).
2805  * If a config is not supplied, it will be generated via
2806  * reports::generate_report().
2807  */
refresh_report(const std::string & report_name,const config * new_cfg)2808 void display::refresh_report(const std::string& report_name, const config * new_cfg)
2809 {
2810 	const theme::status_item *item = theme_.get_status_item(report_name);
2811 	if (!item) {
2812 		reportSurfaces_[report_name] = nullptr;
2813 		return;
2814 	}
2815 
2816 	// Now we will need the config. Generate one if needed.
2817 
2818 	boost::optional <events::mouse_handler &> mhb = boost::none;
2819 
2820 	if (resources::controller) {
2821 		mhb = resources::controller->get_mouse_handler_base();
2822 	}
2823 
2824 	reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
2825 
2826 	const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
2827 	if ( new_cfg == nullptr )
2828 		new_cfg = &generated_cfg;
2829 
2830 	SDL_Rect &rect = reportRects_[report_name];
2831 	const SDL_Rect &new_rect = item->location(screen_.screen_area());
2832 	surface &surf = reportSurfaces_[report_name];
2833 	config &report = reports_[report_name];
2834 
2835 	// Report and its location is unchanged since last time. Do nothing.
2836 	if (surf && rect == new_rect && report == *new_cfg) {
2837 		return;
2838 	}
2839 
2840 	// Update the config in reports_.
2841 	report = *new_cfg;
2842 
2843 	if (surf) {
2844 		sdl_blit(surf, nullptr, screen_.getSurface(), &rect);
2845 	}
2846 
2847 	// If the rectangle has just changed, assign the surface to it
2848 	if (!surf || new_rect != rect)
2849 	{
2850 		surf = nullptr;
2851 		rect = new_rect;
2852 
2853 		// If the rectangle is present, and we are blitting text,
2854 		// then we need to backup the surface.
2855 		// (Images generally won't need backing up,
2856 		// unless they are transparent, but that is done later).
2857 		if (rect.w > 0 && rect.h > 0) {
2858 			surf = get_surface_portion(screen_.getSurface(), rect);
2859 			if (reportSurfaces_[report_name] == nullptr) {
2860 				ERR_DP << "Could not backup background for report!" << std::endl;
2861 			}
2862 		}
2863 	}
2864 
2865 	tooltips::clear_tooltips(rect);
2866 
2867 	if (report.empty()) return;
2868 
2869 	int x = rect.x, y = rect.y;
2870 
2871 	// Add prefix, postfix elements.
2872 	// Make sure that they get the same tooltip
2873 	// as the guys around them.
2874 	std::string str = item->prefix();
2875 	if (!str.empty()) {
2876 		config &e = report.add_child_at("element", config(), 0);
2877 		e["text"] = str;
2878 		e["tooltip"] = report.child("element")["tooltip"];
2879 	}
2880 	str = item->postfix();
2881 	if (!str.empty()) {
2882 		config &e = report.add_child("element");
2883 		e["text"] = str;
2884 		e["tooltip"] = report.child("element", -1)["tooltip"];
2885 	}
2886 
2887 	// Loop through and display each report element.
2888 	int tallest = 0;
2889 	int image_count = 0;
2890 	bool used_ellipsis = false;
2891 	std::ostringstream ellipsis_tooltip;
2892 	SDL_Rect ellipsis_area = rect;
2893 
2894 	for (config::const_child_itors elements = report.child_range("element");
2895 		 elements.begin() != elements.end(); elements.pop_front())
2896 	{
2897 		SDL_Rect area {x, y, rect.w + rect.x - x, rect.h + rect.y - y};
2898 		if (area.h <= 0) break;
2899 
2900 		std::string t = elements.front()["text"];
2901 		if (!t.empty())
2902 		{
2903 			if (used_ellipsis) goto skip_element;
2904 
2905 			// Draw a text element.
2906 			font::pango_text text;
2907 			if (item->font_rgb_set()) {
2908 				text.set_foreground_color(item->font_rgb());
2909 			}
2910 			bool eol = false;
2911 			if (t[t.size() - 1] == '\n') {
2912 				eol = true;
2913 				t = t.substr(0, t.size() - 1);
2914 			}
2915 			text.set_font_size(item->font_size());
2916 			text.set_text(t, true);
2917 			text.set_maximum_width(area.w);
2918 			text.set_maximum_height(area.h, false);
2919 			surface s = text.render();
2920 
2921 			// check if next element is text with almost no space to show it
2922 			const int minimal_text = 12; // width in pixels
2923 			config::const_child_iterator ee = elements.begin();
2924 			if (!eol && rect.w - (x - rect.x + s->w) < minimal_text &&
2925 				++ee != elements.end() && !(*ee)["text"].empty())
2926 			{
2927 				// make this element longer to trigger rendering of ellipsis
2928 				// (to indicate that next elements have not enough space)
2929 				//NOTE this space should be longer than minimal_text pixels
2930 				t = t + "    ";
2931 				text.set_text(t, true);
2932 				s = text.render();
2933 				// use the area of this element for next tooltips
2934 				used_ellipsis = true;
2935 				ellipsis_area.x = x;
2936 				ellipsis_area.y = y;
2937 				ellipsis_area.w = s->w;
2938 				ellipsis_area.h = s->h;
2939 			}
2940 
2941 			screen_.blit_surface(x, y, s);
2942 			area.w = s->w;
2943 			area.h = s->h;
2944 			if (area.h > tallest) {
2945 				tallest = area.h;
2946 			}
2947 			if (eol) {
2948 				x = rect.x;
2949 				y += tallest;
2950 				tallest = 0;
2951 			} else {
2952 				x += area.w;
2953 			}
2954 		}
2955 		else if (!(t = elements.front()["image"].str()).empty())
2956 		{
2957 			if (used_ellipsis) goto skip_element;
2958 
2959 			// Draw an image element.
2960 			surface img(image::get_image(t));
2961 
2962 			if (!img) {
2963 				ERR_DP << "could not find image for report: '" << t << "'" << std::endl;
2964 				continue;
2965 			}
2966 
2967 			if (area.w < img->w && image_count) {
2968 				// We have more than one image, and this one doesn't fit.
2969 				img = image::get_image(game_config::images::ellipsis);
2970 				used_ellipsis = true;
2971 			}
2972 
2973 			if (img->w < area.w) area.w = img->w;
2974 			if (img->h < area.h) area.h = img->h;
2975 			draw_image_for_report(img, area);
2976 
2977 			++image_count;
2978 			if (area.h > tallest) {
2979 				tallest = area.h;
2980 			}
2981 
2982 			if (!used_ellipsis) {
2983 				x += area.w;
2984 			} else {
2985 				ellipsis_area = area;
2986 			}
2987 		}
2988 		else
2989 		{
2990 			// No text nor image, skip this element
2991 			continue;
2992 		}
2993 
2994 		skip_element:
2995 		t = elements.front()["tooltip"].t_str().c_str();
2996 		if (!t.empty()) {
2997 			if (!used_ellipsis) {
2998 				tooltips::add_tooltip(area, t, elements.front()["help"].t_str().c_str());
2999 			} else {
3000 				// Collect all tooltips for the ellipsis.
3001 				// TODO: need a better separator
3002 				// TODO: assign an action
3003 				ellipsis_tooltip << t;
3004 				config::const_child_iterator ee = elements.begin();
3005 				if (++ee != elements.end())
3006 					ellipsis_tooltip << "\n  _________\n\n";
3007 			}
3008 		}
3009 	}
3010 
3011 	if (used_ellipsis) {
3012 		tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str());
3013 	}
3014 }
3015 
invalidate_all()3016 void display::invalidate_all()
3017 {
3018 	DBG_DP << "invalidate_all()\n";
3019 	invalidateAll_ = true;
3020 	invalidated_.clear();
3021 }
3022 
invalidate(const map_location & loc)3023 bool display::invalidate(const map_location& loc)
3024 {
3025 	if(invalidateAll_)
3026 		return false;
3027 
3028 	bool tmp;
3029 	tmp = invalidated_.insert(loc).second;
3030 	return tmp;
3031 }
3032 
invalidate(const std::set<map_location> & locs)3033 bool display::invalidate(const std::set<map_location>& locs)
3034 {
3035 	if(invalidateAll_)
3036 		return false;
3037 	bool ret = false;
3038 	for (const map_location& loc : locs) {
3039 		ret = invalidated_.insert(loc).second || ret;
3040 	}
3041 	return ret;
3042 }
3043 
propagate_invalidation(const std::set<map_location> & locs)3044 bool display::propagate_invalidation(const std::set<map_location>& locs)
3045 {
3046 	if(invalidateAll_)
3047 		return false;
3048 
3049 	if(locs.size()<=1)
3050 		return false; // propagation never needed
3051 
3052 	bool result = false;
3053 	{
3054 		// search the first hex invalidated (if any)
3055 		std::set<map_location>::const_iterator i = locs.begin();
3056 		for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {}
3057 
3058 		if (i != locs.end()) {
3059 
3060 			// propagate invalidation
3061 			// 'i' is already in, but I suspect that splitting the range is bad
3062 			// especially because locs are often adjacents
3063 			size_t previous_size = invalidated_.size();
3064 			invalidated_.insert(locs.begin(), locs.end());
3065 			result = previous_size < invalidated_.size();
3066 		}
3067 	}
3068 	return result;
3069 }
3070 
invalidate_visible_locations_in_rect(const SDL_Rect & rect)3071 bool display::invalidate_visible_locations_in_rect(const SDL_Rect& rect)
3072 {
3073 	return invalidate_locations_in_rect(sdl::intersect_rects(map_area(),rect));
3074 }
3075 
invalidate_locations_in_rect(const SDL_Rect & rect)3076 bool display::invalidate_locations_in_rect(const SDL_Rect& rect)
3077 {
3078 	if(invalidateAll_)
3079 		return false;
3080 
3081 	bool result = false;
3082 	for (const map_location &loc : hexes_under_rect(rect)) {
3083 		result |= invalidate(loc);
3084 	}
3085 	return result;
3086 }
3087 
invalidate_animations_location(const map_location & loc)3088 void display::invalidate_animations_location(const map_location& loc) {
3089 	if (get_map().is_village(loc)) {
3090 		const int owner = dc_->village_owner(loc);
3091 		if (owner >= 0 && flags_[owner].need_update()
3092 		&& (!fogged(loc) || !dc_->teams()[currentTeam_].is_enemy(owner+1))) {
3093 			invalidate(loc);
3094 		}
3095 	}
3096 }
3097 
invalidate_animations()3098 void display::invalidate_animations()
3099 {
3100 	new_animation_frame();
3101 	animate_map_ = preferences::animate_map();
3102 	if (animate_map_) {
3103 		for (const map_location &loc : get_visible_hexes())
3104 		{
3105 			if (shrouded(loc)) continue;
3106 			if (builder_->update_animation(loc)) {
3107 				invalidate(loc);
3108 			} else {
3109 				invalidate_animations_location(loc);
3110 			}
3111 		}
3112 	}
3113 
3114 	for (const unit & u : dc_->units()) {
3115 		u.anim_comp().refresh();
3116 	}
3117 	for (const unit* u : *fake_unit_man_) {
3118 		u->anim_comp().refresh();
3119 	}
3120 
3121 	bool new_inval;
3122 	do {
3123 		new_inval = false;
3124 		for (const unit & u : dc_->units()) {
3125 			new_inval |=  u.anim_comp().invalidate(*this);
3126 		}
3127 		for (const unit* u : *fake_unit_man_) {
3128 			new_inval |=  u->anim_comp().invalidate(*this);
3129 		}
3130 	} while (new_inval);
3131 }
3132 
reset_standing_animations()3133 void display::reset_standing_animations()
3134 {
3135 	for(const unit & u : dc_->units()) {
3136 		u.anim_comp().set_standing();
3137 	}
3138 }
3139 
add_arrow(arrow & arrow)3140 void display::add_arrow(arrow& arrow)
3141 {
3142 	const arrow_path_t & arrow_path = arrow.get_path();
3143 	for (const map_location& loc : arrow_path)
3144 	{
3145 		arrows_map_[loc].push_back(&arrow);
3146 	}
3147 }
3148 
remove_arrow(arrow & arrow)3149 void display::remove_arrow(arrow& arrow)
3150 {
3151 	const arrow_path_t & arrow_path = arrow.get_path();
3152 	for (const map_location& loc : arrow_path)
3153 	{
3154 		arrows_map_[loc].remove(&arrow);
3155 	}
3156 }
3157 
update_arrow(arrow & arrow)3158 void display::update_arrow(arrow & arrow)
3159 {
3160 	const arrow_path_t & previous_path = arrow.get_previous_path();
3161 	for (const map_location& loc : previous_path)
3162 	{
3163 		arrows_map_[loc].remove(&arrow);
3164 	}
3165 	const arrow_path_t & arrow_path = arrow.get_path();
3166 	for (const map_location& loc : arrow_path)
3167 	{
3168 		arrows_map_[loc].push_back(&arrow);
3169 	}
3170 }
3171 
get_middle_location() const3172 map_location display::get_middle_location() const
3173 {
3174 	const SDL_Rect& rect = map_area();
3175 	return pixel_position_to_hex(xpos_ + rect.x + rect.w / 2 , ypos_ + rect.y + rect.h / 2 );
3176 }
3177 
write(config & cfg) const3178 void display::write(config& cfg) const
3179 {
3180 	cfg["view_locked"] = view_locked_;
3181 	cfg["color_adjust_red"] = color_adjust_.r;
3182 	cfg["color_adjust_green"] = color_adjust_.g;
3183 	cfg["color_adjust_blue"] = color_adjust_.b;
3184 	get_middle_location().write(cfg.add_child("location"));
3185 }
3186 
read(const config & cfg)3187 void display::read(const config& cfg)
3188 {
3189 	view_locked_ = cfg["view_locked"].to_bool(false);
3190 	color_adjust_.r = cfg["color_adjust_red"].to_int(0);
3191 	color_adjust_.g = cfg["color_adjust_green"].to_int(0);
3192 	color_adjust_.b = cfg["color_adjust_blue"].to_int(0);
3193 }
3194 
process_reachmap_changes()3195 void display::process_reachmap_changes()
3196 {
3197 	if (!reach_map_changed_) return;
3198 	if (reach_map_.empty() != reach_map_old_.empty()) {
3199 		// Invalidate everything except the non-darkened tiles
3200 		reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
3201 
3202 		for (const auto& hex : get_visible_hexes()) {
3203 			reach_map::iterator reach = full.find(hex);
3204 			if (reach == full.end()) {
3205 				// Location needs to be darkened or brightened
3206 				invalidate(hex);
3207 			} else if (reach->second != 1) {
3208 				// Number needs to be displayed or cleared
3209 				invalidate(hex);
3210 			}
3211 		}
3212 	} else if (!reach_map_.empty()) {
3213 		// Invalidate only changes
3214 		reach_map::iterator reach, reach_old;
3215 		for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
3216 			reach_old = reach_map_old_.find(reach->first);
3217 			if (reach_old == reach_map_old_.end()) {
3218 				invalidate(reach->first);
3219 			} else {
3220 				if (reach_old->second != reach->second) {
3221 					invalidate(reach->first);
3222 				}
3223 				reach_map_old_.erase(reach_old);
3224 			}
3225 		}
3226 		for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
3227 			invalidate(reach_old->first);
3228 		}
3229 	}
3230 	reach_map_old_ = reach_map_;
3231 	reach_map_changed_ = false;
3232 }
3233 
handle_window_event(const SDL_Event & event)3234 void display::handle_window_event(const SDL_Event& event) {
3235 	if (event.type == SDL_WINDOWEVENT) {
3236 			switch (event.window.event) {
3237 				case SDL_WINDOWEVENT_RESIZED:
3238 				case SDL_WINDOWEVENT_RESTORED:
3239 				case SDL_WINDOWEVENT_EXPOSED:
3240 					dirty_ = true;
3241 
3242 					break;
3243 			}
3244 	}
3245 
3246 
3247 }
3248 
handle_event(const SDL_Event & event)3249 void display::handle_event(const SDL_Event& event) {
3250 	if (gui2::dialogs::loading_screen::displaying()) {
3251 		return;
3252 	}
3253 	if (event.type == DRAW_ALL_EVENT) {
3254 		draw();
3255 	}
3256 }
3257 
3258 display *display::singleton_ = nullptr;
3259