The Battle for Wesnoth  1.19.3+dev
display.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Routines to set up the display, scroll and zoom the map.
19  */
20 
21 #include "display.hpp"
22 
23 #include "arrow.hpp"
24 #include "color.hpp"
25 #include "draw.hpp"
26 #include "draw_manager.hpp"
27 #include "fake_unit_manager.hpp"
28 #include "filesystem.hpp"
29 #include "font/sdl_ttf_compat.hpp"
30 #include "font/text.hpp"
31 #include "global.hpp"
33 #include "halo.hpp"
35 #include "log.hpp"
36 #include "map/map.hpp"
37 #include "map/label.hpp"
38 #include "minimap.hpp"
39 #include "overlay.hpp"
40 #include "play_controller.hpp" //note: this can probably be refactored out
41 #include "reports.hpp"
42 #include "resources.hpp"
43 #include "show_dialog.hpp"
44 #include "synced_context.hpp"
45 #include "team.hpp"
46 #include "terrain/builder.hpp"
47 #include "time_of_day.hpp"
48 #include "tooltips.hpp"
49 #include "units/unit.hpp"
51 #include "units/drawer.hpp"
52 #include "units/orb_status.hpp"
53 #include "video.hpp"
54 #include "whiteboard/manager.hpp"
55 
56 #include <boost/algorithm/string/trim.hpp>
57 
58 
59 #include <algorithm>
60 #include <array>
61 #include <cmath>
62 #include <iomanip>
63 #include <numeric>
64 #include <utility>
65 
66 #ifdef _WIN32
67 #include <windows.h>
68 #endif
69 
70 // Includes for bug #17573
71 
72 static lg::log_domain log_display("display");
73 #define ERR_DP LOG_STREAM(err, log_display)
74 #define WRN_DP LOG_STREAM(warn, log_display)
75 #define LOG_DP LOG_STREAM(info, log_display)
76 #define DBG_DP LOG_STREAM(debug, log_display)
77 
78 // These are macros instead of proper constants so that they auto-update if the game config is reloaded.
79 #define zoom_levels (game_config::zoom_levels)
80 #define final_zoom_index (static_cast<int>(zoom_levels.size()) - 1)
81 #define DefaultZoom (game_config::tile_size)
82 #define SmallZoom (DefaultZoom / 2)
83 #define MinZoom (zoom_levels.front())
84 #define MaxZoom (zoom_levels.back())
85 
86 namespace {
87  int prevLabel = 0;
88 }
89 
90 unsigned int display::zoom_ = DefaultZoom;
91 unsigned int display::last_zoom_ = SmallZoom;
92 
93 // Returns index of zoom_levels which is closest match to input zoom_level
94 // Assumption: zoom_levels is a sorted vector of ascending tile sizes
95 static int get_zoom_levels_index(unsigned int zoom_level)
96 {
97  zoom_level = std::clamp(zoom_level, MinZoom, MaxZoom); // ensure zoom_level is within zoom_levels bounds
98  auto iter = std::lower_bound(zoom_levels.begin(), zoom_levels.end(), zoom_level);
99 
100  // find closest match
101  if(iter != zoom_levels.begin() && iter != zoom_levels.end()) {
102  float diff = *iter - *(iter - 1);
103  float lower = (zoom_level - *(iter - 1)) / diff;
104  float upper = (*iter - zoom_level) / diff;
105 
106  // the previous element is closer to zoom_level than the current one
107  if(lower < upper) {
108  iter--;
109  }
110  }
111 
112  return std::distance(zoom_levels.begin(), iter);
113 }
114 
115 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, float submerge, float z_order)
116 {
117  halo::handle halo_handle;
118  if(halo != "") {
119  halo_handle = halo_man_.add(get_location_x(loc) + hex_size() / 2,
120  get_location_y(loc) + hex_size() / 2, halo, loc);
121  }
122 
123  std::vector<overlay>& overlays = get_overlays()[loc];
124  auto it = std::find_if(overlays.begin(), overlays.end(), [z_order](const overlay& ov) { return ov.z_order > z_order; });
125  overlays.emplace(it, img, halo, halo_handle, team_name, item_id, visible_under_fog, submerge, z_order);
126 }
127 
129 {
130  get_overlays().erase(loc);
131 }
132 
133 void display::remove_single_overlay(const map_location& loc, const std::string& toDelete)
134 {
135  std::vector<overlay>& overlays = get_overlays()[loc];
136  overlays.erase(
137  std::remove_if(
138  overlays.begin(), overlays.end(),
139  [&toDelete](const overlay& ov) { return ov.image == toDelete || ov.halo == toDelete || ov.id == toDelete; }
140  ),
141  overlays.end()
142  );
143 }
144 
146  std::weak_ptr<wb::manager> wb,
147  reports& reports_object,
148  const std::string& theme_id,
149  const config& level)
150  : dc_(dc)
151  , halo_man_()
152  , wb_(wb)
153  , exclusive_unit_draw_requests_()
154  , viewing_team_index_(0)
155  , dont_show_all_(false)
156  , xpos_(0)
157  , ypos_(0)
158  , view_locked_(false)
159  , theme_(theme::get_theme_config(theme_id.empty() ? prefs::get().theme() : theme_id), video::game_canvas())
160  , zoom_index_(0)
161  , fake_unit_man_(new fake_unit_manager(*this))
162  , builder_(new terrain_builder(level, (dc_ ? &dc_->map() : nullptr), theme_.border().tile_image, theme_.border().show_border))
163  , minimap_renderer_(nullptr)
164  , minimap_location_(sdl::empty_rect)
165  , redraw_background_(false)
166  , invalidateAll_(true)
167  , diagnostic_label_(0)
168  , invalidateGameStatus_(true)
169  , map_labels_(new map_labels(nullptr))
170  , reports_object_(&reports_object)
171  , scroll_event_("scrolled")
172  , frametimes_(50)
173  , fps_counter_()
174  , fps_start_()
175  , fps_actual_()
176  , reportLocations_()
177  , reportSurfaces_()
178  , reports_()
179  , menu_buttons_()
180  , action_buttons_()
181  , invalidated_()
182  , tod_hex_mask1(nullptr)
183  , tod_hex_mask2(nullptr)
184  , fog_images_()
185  , shroud_images_()
186  , selectedHex_()
187  , mouseoverHex_()
188  , keys_()
189  , animate_map_(true)
190  , animate_water_(true)
191  , flags_()
192  , playing_team_index_(0)
193  , drawing_buffer_()
194  , map_screenshot_(false)
195  , reach_map_()
196  , reach_map_old_()
197  , reach_map_changed_(true)
198  , fps_handle_(0)
199  , invalidated_hexes_(0)
200  , drawn_hexes_(0)
201  , redraw_observers_()
202  , debug_flags_()
203  , arrows_map_()
204  , color_adjust_()
205 {
206  //The following assertion fails when starting a campaign
207  assert(singleton_ == nullptr);
208  singleton_ = this;
209 
211 
212  blindfold_ctr_ = 0;
213 
214  read(level.child_or_empty("display"));
215 
218 
219  unsigned int tile_size = prefs::get().tile_size();
220  if(tile_size < MinZoom || tile_size > MaxZoom)
224  if(zoom_ != prefs::get().tile_size()) // correct saved tile_size if necessary
225  prefs::get().set_tile_size(zoom_);
226 
227  init_flags();
228 
229  if(!menu_buttons_.empty() || !action_buttons_.empty()) {
230  create_buttons();
231  }
232 
233 #ifdef _WIN32
234  // Increase timer resolution to prevent delays getting much longer than they should.
235  timeBeginPeriod(1u);
236 #endif
237 }
238 
240 {
241 #ifdef _WIN32
242  timeEndPeriod(1u);
243 #endif
244 
245  singleton_ = nullptr;
246  resources::fake_units = nullptr;
247 }
248 
249 void display::set_theme(const std::string& new_theme)
250 {
252  builder_->set_draw_border(theme_.border().show_border);
253  menu_buttons_.clear();
254  action_buttons_.clear();
255  create_buttons();
256  rebuild_all();
257  queue_rerender();
258 }
259 
261 {
262  flags_.clear();
263  if (!dc_) return;
264  flags_.resize(dc_->teams().size());
265 
266  for(const team& t : dc_->teams()) {
268  }
269 }
270 
272 {
273  std::string flag = t.flag();
274  std::string old_rgb = game_config::flag_rgb;
275  std::string new_rgb = t.color();
276 
277  if(flag.empty()) {
279  }
280 
281  LOG_DP << "Adding flag for side " << t.side() << " from animation " << flag;
282 
283  // Must recolor flag image
284  animated<image::locator> temp_anim;
285 
286  std::vector<std::string> items = utils::square_parenthetical_split(flag);
287 
288  for(const std::string& item : items) {
289  const std::vector<std::string>& sub_items = utils::split(item, ':');
290  std::string str = item;
291  int time = 100;
292 
293  if(sub_items.size() > 1) {
294  str = sub_items.front();
295  try {
296  time = std::max<int>(1, std::stoi(sub_items.back()));
297  } catch(const std::invalid_argument&) {
298  ERR_DP << "Invalid time value found when constructing flag for side " << t.side() << ": " << sub_items.back();
299  }
300  }
301 
302  std::stringstream temp;
303  temp << str << "~RC(" << old_rgb << ">"<< new_rgb << ")";
304  image::locator flag_image(temp.str());
305  temp_anim.add_frame(time, flag_image);
306  }
307 
308  animated<image::locator>& f = flags_[t.side() - 1];
309  f = temp_anim;
310  auto time = f.get_end_time();
311  if (time > 0) {
312  f.start_animation(randomness::rng::default_instance().get_random_int(0, time-1), true);
313  } else {
314  // this can happen if both flag and game_config::images::flag are empty.
315  ERR_DP << "missing flag for side " << t.side();
316  }
317 }
318 
320 {
321  for(const team& t : dc_->teams()) {
322  if(t.owns_village(loc) && (!fogged(loc) || !viewing_team().is_enemy(t.side()))) {
323  auto& flag = flags_[t.side() - 1];
324  flag.update_last_draw_time();
325 
326  const image::locator& image_flag = animate_map_
327  ? flag.get_current_frame()
328  : flag.get_first_frame();
329 
330  return image::get_texture(image_flag, image::TOD_COLORED);
331  }
332  }
333 
334  return texture();
335 }
336 
338 {
339  return dc_->teams()[playing_team_index()];
340 }
341 
343 {
344  return dc_->teams()[viewing_team_index()];
345 }
346 
347 void display::set_viewing_team_index(std::size_t teamindex, bool show_everything)
348 {
349  assert(teamindex < dc_->teams().size());
350  viewing_team_index_ = teamindex;
351  if(!show_everything) {
352  labels().set_team(&dc_->teams()[teamindex]);
353  dont_show_all_ = true;
354  } else {
355  labels().set_team(nullptr);
356  dont_show_all_ = false;
357  }
359  if(std::shared_ptr<wb::manager> w = wb_.lock()) {
360  w->on_viewer_change(teamindex);
361  }
362 }
363 
364 void display::set_playing_team_index(std::size_t teamindex)
365 {
366  assert(teamindex < dc_->teams().size());
367  playing_team_index_ = teamindex;
369 }
370 
372 {
373  if(loc.valid() && exclusive_unit_draw_requests_.find(loc) == exclusive_unit_draw_requests_.end()) {
375  return true;
376  } else {
377  return false;
378  }
379 }
380 
382 {
383  std::string id = "";
384  if(loc.valid())
385  {
387  //id will be set to the default "" string by the [] operator if the map doesn't have anything for that loc.
389  }
390  return id;
391 }
392 
393 void display::update_tod(const time_of_day* tod_override)
394 {
395  const time_of_day* tod = tod_override;
396  if(tod == nullptr) {
397  tod = &get_time_of_day();
398  }
399 
400  const tod_color col = color_adjust_ + tod->color;
401  image::set_color_adjustment(col.r, col.g, col.b);
402 
403  invalidate_all();
404 }
405 
406 void display::adjust_color_overlay(int r, int g, int b)
407 {
408  color_adjust_ = tod_color(r, g, b);
409  update_tod();
410 }
411 
412 void display::fill_images_list(const std::string& prefix, std::vector<std::string>& images)
413 {
414  if(prefix == ""){
415  return;
416  }
417 
418  // search prefix.png, prefix1.png, prefix2.png ...
419  for(int i=0; ; ++i){
420  std::ostringstream s;
421  s << prefix;
422  if(i != 0)
423  s << i;
424  s << ".png";
425  if(image::exists(s.str()))
426  images.push_back(s.str());
427  else if(i>0)
428  break;
429  }
430  if (images.empty())
431  images.emplace_back();
432 }
433 
435 {
436  builder_->rebuild_all();
437 }
438 
440 {
441  redraw_background_ = true;
442  builder_->reload_map();
443 }
444 
446 {
447  dc_ = dc;
448  builder_->change_map(&dc_->map()); //TODO: Should display_context own and initialize the builder object?
449 }
450 
451 void display::blindfold(bool value)
452 {
453  if(value == true)
454  ++blindfold_ctr_;
455  else
456  --blindfold_ctr_;
457 }
458 
460 {
461  return blindfold_ctr_ > 0;
462 }
463 
465 {
467 }
468 
470 {
472 }
473 
475 {
477 }
478 
480 {
481  rect max_area{0, 0, 0, 0};
482 
483  // hex_size() is always a multiple of 4
484  // and hex_width() a multiple of 3,
485  // so there shouldn't be off-by-one-errors
486  // due to rounding.
487  // To display a hex fully on screen,
488  // a little bit extra space is needed.
489  // Also added the border two times.
490  max_area.w = static_cast<int>((get_map().w() + 2 * theme_.border().size + 1.0 / 3.0) * hex_width());
491  max_area.h = static_cast<int>((get_map().h() + 2 * theme_.border().size + 0.5) * hex_size());
492 
493  return max_area;
494 }
495 
497 {
498  rect max_area = max_map_area();
499 
500  // if it's for map_screenshot, maximize and don't recenter
501  if(map_screenshot_) {
502  return max_area;
503  }
504 
505  rect res = map_outside_area();
506 
507  if(max_area.w < res.w) {
508  // map is smaller, center
509  res.x += (res.w - max_area.w) / 2;
510  res.w = max_area.w;
511  }
512 
513  if(max_area.h < res.h) {
514  // map is smaller, center
515  res.y += (res.h - max_area.h) / 2;
516  res.h = max_area.h;
517  }
518 
519  return res;
520 }
521 
523 {
524  if(map_screenshot_) {
525  return max_map_area();
526  } else {
528  }
529 }
530 
531 bool display::outside_area(const SDL_Rect& area, const int x, const int y)
532 {
533  const int x_thresh = hex_size();
534  const int y_thresh = hex_size();
535  return (x < area.x || x > area.x + area.w - x_thresh || y < area.y || y > area.y + area.h - y_thresh);
536 }
537 
538 // This function uses the screen as reference
539 const map_location display::hex_clicked_on(int xclick, int yclick) const
540 {
541  rect r = map_area();
542  if(!r.contains(xclick, yclick)) {
543  return map_location();
544  }
545 
546  xclick -= r.x;
547  yclick -= r.y;
548 
549  return pixel_position_to_hex(xpos_ + xclick, ypos_ + yclick);
550 }
551 
552 // This function uses the rect of map_area as reference
554 {
555  // adjust for the border
556  x -= static_cast<int>(theme_.border().size * hex_width());
557  y -= static_cast<int>(theme_.border().size * hex_size());
558  // The editor can modify the border and this will result in a negative y
559  // value. Instead of adding extra cases we just shift the hex. Since the
560  // editor doesn't use the direction this is no problem.
561  const int offset = y < 0 ? 1 : 0;
562  if(offset) {
563  x += hex_width();
564  y += hex_size();
565  }
566  const int s = hex_size();
567  const int tesselation_x_size = hex_width() * 2;
568  const int tesselation_y_size = s;
569  const int x_base = x / tesselation_x_size * 2;
570  const int x_mod = x % tesselation_x_size;
571  const int y_base = y / tesselation_y_size;
572  const int y_mod = y % tesselation_y_size;
573 
574  int x_modifier = 0;
575  int y_modifier = 0;
576 
577  if (y_mod < tesselation_y_size / 2) {
578  if ((x_mod * 2 + y_mod) < (s / 2)) {
579  x_modifier = -1;
580  y_modifier = -1;
581  } else if ((x_mod * 2 - y_mod) < (s * 3 / 2)) {
582  x_modifier = 0;
583  y_modifier = 0;
584  } else {
585  x_modifier = 1;
586  y_modifier = -1;
587  }
588 
589  } else {
590  if ((x_mod * 2 - (y_mod - s / 2)) < 0) {
591  x_modifier = -1;
592  y_modifier = 0;
593  } else if ((x_mod * 2 + (y_mod - s / 2)) < s * 2) {
594  x_modifier = 0;
595  y_modifier = 0;
596  } else {
597  x_modifier = 1;
598  y_modifier = 0;
599  }
600  }
601 
602  return map_location(x_base + x_modifier - offset, y_base + y_modifier - offset);
603 }
604 
606 {
607  if (loc_.y < rect_.bottom[loc_.x & 1])
608  ++loc_.y;
609  else {
610  ++loc_.x;
611  loc_.y = rect_.top[loc_.x & 1];
612  }
613 
614  return *this;
615 }
616 
617 // begin is top left, and end is after bottom right
619 {
620  return iterator(map_location(left, top[left & 1]), *this);
621 }
623 {
624  return iterator(map_location(right+1, top[(right+1) & 1]), *this);
625 }
626 
627 const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const
628 {
629  rect_of_hexes res;
630 
631  if(r.w <= 0 || r.h <= 0) {
632  // empty rect, return dummy values giving begin=end
633  res.left = 0;
634  res.right = -1; // end is right+1
635  res.top[0] = 0;
636  res.top[1] = 0;
637  res.bottom[0] = 0;
638  res.bottom[1] = 0;
639  return res;
640  }
641 
642  SDL_Rect map_rect = map_area();
643  // translate rect coordinates from screen-based to map_area-based
644  int x = xpos_ - map_rect.x + r.x;
645  int y = ypos_ - map_rect.y + r.y;
646  // we use the "double" type to avoid important rounding error (size of an hex!)
647  // we will also need to use std::floor to avoid bad rounding at border (negative values)
648  double tile_width = hex_width();
649  double tile_size = hex_size();
650  double border = theme_.border().size;
651  // we minus "0.(3)", for horizontal imbrication.
652  // reason is: two adjacent hexes each overlap 1/4 of their width, so for
653  // grid calculation 3/4 of tile width is used, which by default gives
654  // 18/54=0.(3). Note that, while tile_width is zoom dependent, 0.(3) is not.
655  res.left = static_cast<int>(std::floor(-border + x / tile_width - 0.3333333));
656  // we remove 1 pixel of the rectangle dimensions
657  // (the rounded division take one pixel more than needed)
658  res.right = static_cast<int>(std::floor(-border + (x + r.w - 1) / tile_width));
659 
660  // for odd x, we must shift up one half-hex. Since x will vary along the edge,
661  // we store here the y values for even and odd x, respectively
662  res.top[0] = static_cast<int>(std::floor(-border + y / tile_size));
663  res.top[1] = static_cast<int>(std::floor(-border + y / tile_size - 0.5));
664  res.bottom[0] = static_cast<int>(std::floor(-border + (y + r.h - 1) / tile_size));
665  res.bottom[1] = static_cast<int>(std::floor(-border + (y + r.h - 1) / tile_size - 0.5));
666 
667  // TODO: in some rare cases (1/16), a corner of the big rect is on a tile
668  // (the 72x72 rectangle containing the hex) but not on the hex itself
669  // Can maybe be optimized by using pixel_position_to_hex
670 
671  return res;
672 }
673 
674 bool display::shrouded(const map_location& loc) const
675 {
676  return is_blindfolded() || (dont_show_all_ && viewing_team().shrouded(loc));
677 }
678 
679 bool display::fogged(const map_location& loc) const
680 {
681  return is_blindfolded() || (dont_show_all_ && viewing_team().fogged(loc));
682 }
683 
685 {
686  return static_cast<int>(map_area().x + (loc.x + theme_.border().size) * hex_width() - xpos_);
687 }
688 
690 {
691  return static_cast<int>(map_area().y + (loc.y + theme_.border().size) * zoom_ - ypos_ + (is_odd(loc.x) ? zoom_/2 : 0));
692 }
693 
695 {
696  return {
697  get_location_x(loc),
698  get_location_y(loc)
699  };
700 }
701 
703 {
704  // TODO: don't return location for this,
705  // instead directly scroll to the clicked pixel position
706 
707  if(!minimap_area().contains(x, y)) {
708  return map_location();
709  }
710 
711  // we transform the coordinates from minimap to the full map image
712  // probably more adjustments to do (border, minimap shift...)
713  // but the mouse and human capacity to evaluate the rectangle center
714  // is not pixel precise.
715  int px = (x - minimap_location_.x) * get_map().w() * hex_width() / std::max(minimap_location_.w, 1);
716  int py = (y - minimap_location_.y) * get_map().h() * hex_size() / std::max(minimap_location_.h, 1);
717 
718  map_location loc = pixel_position_to_hex(px, py);
719  if(loc.x < 0) {
720  loc.x = 0;
721  } else if(loc.x >= get_map().w()) {
722  loc.x = get_map().w() - 1;
723  }
724 
725  if(loc.y < 0) {
726  loc.y = 0;
727  } else if(loc.y >= get_map().h()) {
728  loc.y = get_map().h() - 1;
729  }
730 
731  return loc;
732 }
733 
734 surface display::screenshot(bool map_screenshot)
735 {
736  if (!map_screenshot) {
737  LOG_DP << "taking ordinary screenshot";
738  return video::read_pixels();
739  }
740 
741  if (get_map().empty()) {
742  ERR_DP << "No map loaded, cannot create a map screenshot.";
743  return nullptr;
744  }
745 
746  // back up the current map view position and move to top-left
747  int old_xpos = xpos_;
748  int old_ypos = ypos_;
749  xpos_ = 0;
750  ypos_ = 0;
751 
752  // Reroute render output to a separate texture until the end of scope.
753  SDL_Rect area = max_map_area();
754  if (area.w > 1 << 16 || area.h > 1 << 16) {
755  WRN_DP << "Excessively large map screenshot area";
756  }
757  LOG_DP << "creating " << area.w << " by " << area.h
758  << " texture for map screenshot";
759  texture output_texture(area.w, area.h, SDL_TEXTUREACCESS_TARGET);
760  auto target_setter = draw::set_render_target(output_texture);
761  auto clipper = draw::override_clip(area);
762 
763  map_screenshot_ = true;
764 
765  DBG_DP << "invalidating region for map screenshot";
767 
768  DBG_DP << "drawing map screenshot";
769  draw();
770 
771  map_screenshot_ = false;
772 
773  // Restore map viewport position
774  xpos_ = old_xpos;
775  ypos_ = old_ypos;
776 
777  // Read rendered pixels back as an SDL surface.
778  LOG_DP << "reading pixels for map screenshot";
779  return video::read_pixels();
780 }
781 
782 std::shared_ptr<gui::button> display::find_action_button(const std::string& id)
783 {
784  for(auto& b : action_buttons_) {
785  if(b->id() == id) {
786  return b;
787  }
788  }
789  return nullptr;
790 }
791 
792 std::shared_ptr<gui::button> display::find_menu_button(const std::string& id)
793 {
794  for(auto& b : menu_buttons_) {
795  if(b->id() == id) {
796  return b;
797  }
798  }
799  return nullptr;
800 }
801 
803 {
804  DBG_DP << "positioning menu buttons...";
805  for(const auto& menu : theme_.menus()) {
806  if(auto b = find_menu_button(menu.get_id())) {
807  const rect& loc = menu.location(video::game_canvas());
808  b->set_location(loc);
809  b->set_measurements(0,0);
810  b->set_label(menu.title());
811  b->set_image(menu.image());
812  }
813  }
814 
815  DBG_DP << "positioning action buttons...";
816  for(const auto& action : theme_.actions()) {
817  if(auto b = find_action_button(action.get_id())) {
818  const rect& loc = action.location(video::game_canvas());
819  b->set_location(loc);
820  b->set_measurements(0,0);
821  b->set_label(action.title());
822  b->set_image(action.image());
823  }
824  }
825 }
826 
827 namespace
828 {
829 gui::button::TYPE string_to_button_type(const std::string& type)
830 {
831  if(type == "checkbox") {
833  } else if(type == "image") {
835  } else if(type == "radiobox") {
837  } else if(type == "turbo") {
839  } else {
841  }
842 }
843 
844 const std::string& get_direction(std::size_t n)
845 {
846  using namespace std::literals::string_literals;
847  static const std::array dirs{"-n"s, "-ne"s, "-se"s, "-s"s, "-sw"s, "-nw"s};
848  return dirs[n >= dirs.size() ? 0 : n];
849 }
850 } // namespace
851 
853 {
854  if(video::headless()) {
855  return;
856  }
857 
858  // Keep the old buttons around until we're done so we can check the previous state.
859  std::vector<std::shared_ptr<gui::button>> menu_work;
860  std::vector<std::shared_ptr<gui::button>> action_work;
861 
862  DBG_DP << "creating menu buttons...";
863  for(const auto& menu : theme_.menus()) {
864  if(!menu.is_button()) {
865  continue;
866  }
867 
868  auto b = std::make_shared<gui::button>(menu.title(), gui::button::TYPE_PRESS, menu.image(),
869  gui::button::DEFAULT_SPACE, true, menu.overlay(), font::SIZE_BUTTON_SMALL);
870 
871  DBG_DP << "drawing button " << menu.get_id();
872  b->set_id(menu.get_id());
873  if(!menu.tooltip().empty()) {
874  b->set_tooltip_string(menu.tooltip());
875  }
876 
877  if(auto b_prev = find_menu_button(b->id())) {
878  b->enable(b_prev->enabled());
879  }
880 
881  menu_work.push_back(std::move(b));
882  }
883 
884  DBG_DP << "creating action buttons...";
885  for(const auto& action : theme_.actions()) {
886  auto b = std::make_shared<gui::button>(action.title(), string_to_button_type(action.type()),
887  action.image(), gui::button::DEFAULT_SPACE, true, action.overlay(), font::SIZE_BUTTON_SMALL);
888 
889  DBG_DP << "drawing button " << action.get_id();
890  b->set_id(action.get_id());
891  if(!action.tooltip(0).empty()) {
892  b->set_tooltip_string(action.tooltip(0));
893  }
894 
895  if(auto b_prev = find_action_button(b->id())) {
896  b->enable(b_prev->enabled());
897  if(b_prev->get_type() == gui::button::TYPE_CHECK) {
898  b->set_check(b_prev->checked());
899  }
900  }
901 
902  action_work.push_back(std::move(b));
903  }
904 
905  menu_buttons_ = std::move(menu_work);
906  action_buttons_ = std::move(action_work);
907 
908  if (prevent_draw_) {
909  // buttons start hidden in this case
910  hide_buttons();
911  }
912 
913  layout_buttons();
914  DBG_DP << "buttons created";
915 }
916 
918 {
919  // This is currently unnecessary because every GUI1 widget is a TLD.
920  // They will draw themselves. Keeping code in case this changes.
921  return;
922 
923  //const rect clip = draw::get_clip();
924  //for(auto& btn : menu_buttons_) {
925  // if(clip.overlaps(btn->location())) {
926  // btn->set_dirty(true);
927  // btn->draw();
928  // }
929  //}
930 
931  //for(auto& btn : action_buttons_) {
932  // if(clip.overlaps(btn->location())) {
933  // btn->set_dirty(true);
934  // btn->draw();
935  // }
936  //}
937 }
938 
940 {
941  for (auto& button : menu_buttons_) {
942  button->hide();
943  }
944  for (auto& button : action_buttons_) {
945  button->hide();
946  }
947 }
948 
950 {
951  for (auto& button : menu_buttons_) {
952  button->hide(false);
953  }
954  for (auto& button : action_buttons_) {
955  button->hide(false);
956  }
957 }
958 
959 std::vector<texture> display::get_fog_shroud_images(const map_location& loc, image::TYPE image_type)
960 {
961  std::vector<std::string> names;
962  const auto adjacent = get_adjacent_tiles(loc);
963 
964  enum visibility { FOG = 0, SHROUD = 1, CLEAR = 2 };
965  std::array<visibility, 6> tiles;
966 
967  const std::array image_prefix{&game_config::fog_prefix, &game_config::shroud_prefix};
968 
969  for(int i = 0; i < 6; ++i) {
970  if(shrouded(adjacent[i])) {
971  tiles[i] = SHROUD;
972  } else if(!fogged(loc) && fogged(adjacent[i])) {
973  tiles[i] = FOG;
974  } else {
975  tiles[i] = CLEAR;
976  }
977  }
978 
979  for(int v = FOG; v != CLEAR; ++v) {
980  // Find somewhere that doesn't have overlap to use as a starting point
981  int start;
982  for(start = 0; start != 6; ++start) {
983  if(tiles[start] != v) {
984  break;
985  }
986  }
987 
988  if(start == 6) {
989  // Completely surrounded by fog or shroud. This might have
990  // a special graphic.
991  const std::string name = *image_prefix[v] + "-all.png";
992  if(image::exists(name)) {
993  names.push_back(name);
994  // Proceed to the next visibility (fog -> shroud -> clear).
995  continue;
996  }
997  // No special graphic found. We'll just combine some other images
998  // and hope it works out.
999  start = 0;
1000  }
1001 
1002  // Find all the directions overlap occurs from
1003  for(int i = (start + 1) % 6, cap1 = 0; i != start && cap1 != 6; ++cap1) {
1004  if(tiles[i] == v) {
1005  std::ostringstream stream;
1006  std::string name;
1007  stream << *image_prefix[v];
1008 
1009  for(int cap2 = 0; v == tiles[i] && cap2 != 6; i = (i + 1) % 6, ++cap2) {
1010  stream << get_direction(i);
1011 
1012  if(!image::exists(stream.str() + ".png")) {
1013  // If we don't have any surface at all,
1014  // then move onto the next overlapped area
1015  if(name.empty()) {
1016  i = (i + 1) % 6;
1017  }
1018  break;
1019  } else {
1020  name = stream.str();
1021  }
1022  }
1023 
1024  if(!name.empty()) {
1025  names.push_back(name + ".png");
1026  }
1027  } else {
1028  i = (i + 1) % 6;
1029  }
1030  }
1031  }
1032 
1033  // now get the textures
1034  std::vector<texture> res;
1035 
1036  for(const std::string& name : names) {
1037  if(texture tex = image::get_texture(name, image_type)) {
1038  res.push_back(std::move(tex));
1039  }
1040  }
1041 
1042  return res;
1043 }
1044 
1045 void display::get_terrain_images(const map_location& loc, const std::string& timeid, TERRAIN_TYPE terrain_type)
1046 {
1047  terrain_image_vector_.clear();
1048 
1050  const time_of_day& tod = get_time_of_day(loc);
1051 
1052  // get all the light transitions
1053  const auto adjs = get_adjacent_tiles(loc);
1054  std::array<const time_of_day*, adjs.size()> atods;
1055 
1056  for(std::size_t d = 0; d < adjs.size(); ++d) {
1057  atods[d] = &get_time_of_day(adjs[d]);
1058  }
1059 
1060  for(int d = 0; d < 6; ++d) {
1061  /*
1062  concave
1063  _____
1064  / \
1065  / atod1 \_____
1066  \ !tod / \
1067  \_____/ atod2 \
1068  / \__\ !tod /
1069  / \_____/
1070  \ tod /
1071  \_____/
1072  */
1073 
1074  const time_of_day& atod1 = *atods[d];
1075  const time_of_day& atod2 = *atods[(d + 1) % 6];
1076 
1077  if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color) {
1078  continue;
1079  }
1080 
1081  if(lt.empty()) {
1082  // color the full hex before adding transitions
1083  tod_color col = tod.color + color_adjust_;
1084  lt = image::get_light_string(0, col.r, col.g, col.b);
1085  }
1086 
1087  // add the directional transitions
1088  tod_color acol = atod1.color + color_adjust_;
1089  lt += image::get_light_string(d + 1, acol.r, acol.g, acol.b);
1090  }
1091 
1092  for(int d = 0; d < 6; ++d) {
1093  /*
1094  convex 1
1095  _____
1096  / \
1097  / atod1 \_____
1098  \ !tod / \
1099  \_____/ atod2 \
1100  / \__\ tod /
1101  / \_____/
1102  \ tod /
1103  \_____/
1104  */
1105 
1106  const time_of_day& atod1 = *atods[d];
1107  const time_of_day& atod2 = *atods[(d + 1) % 6];
1108 
1109  if(atod1.color == tod.color || atod1.color == atod2.color) {
1110  continue;
1111  }
1112 
1113  if(lt.empty()) {
1114  // color the full hex before adding transitions
1115  tod_color col = tod.color + color_adjust_;
1116  lt = image::get_light_string(0, col.r, col.g, col.b);
1117  }
1118 
1119  // add the directional transitions
1120  tod_color acol = atod1.color + color_adjust_;
1121  lt += image::get_light_string(d + 7, acol.r, acol.g, acol.b);
1122  }
1123 
1124  for(int d = 0; d < 6; ++d) {
1125  /*
1126  convex 2
1127  _____
1128  / \
1129  / atod1 \_____
1130  \ tod / \
1131  \_____/ atod2 \
1132  / \__\ !tod /
1133  / \_____/
1134  \ tod /
1135  \_____/
1136  */
1137 
1138  const time_of_day& atod1 = *atods[d];
1139  const time_of_day& atod2 = *atods[(d + 1) % 6];
1140 
1141  if(atod2.color == tod.color || atod1.color == atod2.color) {
1142  continue;
1143  }
1144 
1145  if(lt.empty()) {
1146  // color the full hex before adding transitions
1147  tod_color col = tod.color + color_adjust_;
1148  lt = image::get_light_string(0, col.r, col.g, col.b);
1149  }
1150 
1151  // add the directional transitions
1152  tod_color acol = atod2.color + color_adjust_;
1153  lt += image::get_light_string(d + 13, acol.r, acol.g, acol.b);
1154  }
1155 
1156  if(lt.empty()){
1157  tod_color col = tod.color + color_adjust_;
1158  if(!col.is_zero()){
1159  // no real lightmap needed but still color the hex
1160  lt = image::get_light_string(-1, col.r, col.g, col.b);
1161  }
1162  }
1163 
1164  const terrain_builder::TERRAIN_TYPE builder_terrain_type = terrain_type == FOREGROUND
1167 
1168  if(const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc, timeid, builder_terrain_type)) {
1169  // Cache the offmap name. Since it is themeable it can change, so don't make it static.
1170  const std::string off_map_name = "terrain/" + theme_.border().tile_image;
1171  for(const auto& terrain : *terrains) {
1172  const image::locator& image = animate_map_ ? terrain.get_current_frame() : terrain.get_first_frame();
1173 
1174  // We prevent ToD coloring and brightening of off-map tiles,
1175  // We need to test for the tile to be rendered and
1176  // not the location, since the transitions are rendered
1177  // over the offmap-terrain and these need a ToD coloring.
1178  texture tex;
1179  const bool off_map = (image.get_filename() == off_map_name
1180  || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos);
1181 
1182  if(off_map) {
1184  } else if(lt.empty()) {
1186  } else {
1187  tex = image::get_lighted_texture(image, lt);
1188  }
1189 
1190  if(tex) {
1191  terrain_image_vector_.push_back(std::move(tex));
1192  }
1193  }
1194  }
1195 }
1196 
1197 namespace
1198 {
1199 constexpr std::array layer_groups {
1203  drawing_layer::reachmap // Make sure the movement doesn't show above fog and reachmap.
1204 };
1205 
1206 enum {
1207  // you may adjust the following when needed:
1208 
1209  // maximum border. 3 should be safe even if a larger border is in use somewhere
1210  MAX_BORDER = 3,
1211 
1212  // store x, y, and layer in one 32 bit integer
1213  // 4 most significant bits == layer group => 16
1214  BITS_FOR_LAYER_GROUP = 4,
1215 
1216  // 10 second most significant bits == y => 1024
1217  BITS_FOR_Y = 10,
1218 
1219  // 1 third most significant bit == x parity => 2
1220  BITS_FOR_X_PARITY = 1,
1221 
1222  // 8 fourth most significant bits == layer => 256
1223  BITS_FOR_LAYER = 8,
1224 
1225  // 9 least significant bits == x / 2 => 512 (really 1024 for x)
1226  BITS_FOR_X_OVER_2 = 9,
1227 
1228  SHIFT_LAYER = BITS_FOR_X_OVER_2,
1229 
1230  SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER,
1231 
1232  SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
1233 
1234  SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y
1235 };
1236 
1237 uint32_t generate_hex_key(const drawing_layer layer, const map_location& loc)
1238 {
1239  // Start with the index of last group entry...
1240  uint32_t group_i = layer_groups.size() - 1;
1241 
1242  // ...and works backwards until the group containing the specified layer is found.
1243  while(layer < layer_groups[group_i]) {
1244  --group_i;
1245  }
1246 
1247  // the parity of x must be more significant than the layer but less significant than y.
1248  // Thus basically every row is split in two: First the row containing all the odd x
1249  // then the row containing all the even x. Since thus the least significant bit of x is
1250  // not required for x ordering anymore it can be shifted out to the right.
1251  const uint32_t x_parity = static_cast<uint32_t>(loc.x) & 1;
1252 
1253  uint32_t key = 0;
1254  static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key) * 8, "Bit field too small");
1255 
1256  key = (group_i << SHIFT_LAYER_GROUP) | (static_cast<uint32_t>(loc.y + MAX_BORDER) << SHIFT_Y);
1257  key |= (x_parity << SHIFT_X_PARITY);
1258  key |= (static_cast<uint32_t>(layer) << SHIFT_LAYER) | static_cast<uint32_t>(loc.x + MAX_BORDER) / 2;
1259 
1260  return key;
1261 }
1262 } // namespace
1263 
1264 void display::drawing_buffer_add(const drawing_layer layer, const map_location& loc, decltype(draw_helper::do_draw) draw_func)
1265 {
1266  const rect dest {
1267  get_location_x(loc),
1268  get_location_y(loc),
1269  int(zoom_),
1270  int(zoom_)
1271  };
1272 
1273  drawing_buffer_.AGGREGATE_EMPLACE(generate_hex_key(layer, loc), draw_func, dest);
1274 }
1275 
1277 {
1278  DBG_DP << "committing drawing buffer"
1279  << " with " << drawing_buffer_.size() << " items";
1280 
1281  // std::list::sort() is a stable sort
1282  drawing_buffer_.sort();
1283 
1284  const auto clipper = draw::reduce_clip(map_area());
1285 
1286  /*
1287  * Info regarding the rendering algorithm.
1288  *
1289  * In order to render a hex properly it needs to be rendered per row. On
1290  * this row several layers need to be drawn at the same time. Mainly the
1291  * unit and the background terrain. This is needed since both can spill
1292  * in the next hex. The foreground terrain needs to be drawn before to
1293  * avoid decapitation a unit.
1294  *
1295  * This ended in the following priority order:
1296  * layergroup > location > layer > 'draw_helper' > surface
1297  */
1298  for(const draw_helper& helper : drawing_buffer_) {
1299  std::invoke(helper.do_draw, helper.dest);
1300  }
1301 
1302  drawing_buffer_.clear();
1303 }
1304 
1305 // frametime is in milliseconds
1306 static unsigned calculate_fps(unsigned frametime)
1307 {
1308  return frametime != 0u ? 1000u / frametime : 999u;
1309 }
1310 
1312 {
1314  const int sample_freq = 10;
1315 
1316  if(current_frame_sample_ != sample_freq) {
1317  return;
1318  }
1319 
1320  const auto minmax_it = std::minmax_element(frametimes_.begin(), frametimes_.end());
1321  const unsigned render_avg = std::accumulate(frametimes_.begin(), frametimes_.end(), 0) / frametimes_.size();
1322  const int avg_fps = calculate_fps(render_avg);
1323  const int max_fps = calculate_fps(*minmax_it.first);
1324  const int min_fps = calculate_fps(*minmax_it.second);
1325  fps_history_.emplace_back(min_fps, avg_fps, max_fps);
1327 
1328  // flush out the stored fps values every so often
1329  if(fps_history_.size() == 1000) {
1330  std::string filename = filesystem::get_user_data_dir()+"/fps_log.csv";
1331  filesystem::scoped_ostream fps_log = filesystem::ostream_file(filename, std::ios_base::binary | std::ios_base::app);
1332  for(const auto& fps : fps_history_) {
1333  *fps_log << std::get<0>(fps) << "," << std::get<1>(fps) << "," << std::get<2>(fps) << "\n";
1334  }
1335  fps_history_.clear();
1336  }
1337 
1338  if(fps_handle_ != 0) {
1340  fps_handle_ = 0;
1341  }
1342  std::ostringstream stream;
1343  stream << "<tt> min/avg/max/act</tt>\n";
1344  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";
1345  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";
1346  if (game_config::debug) {
1347  stream << "\nhex: " << drawn_hexes_*1.0/sample_freq;
1349  stream << " (" << (invalidated_hexes_-drawn_hexes_)*1.0/sample_freq << ")";
1350  }
1351  drawn_hexes_ = 0;
1352  invalidated_hexes_ = 0;
1353 
1354  font::floating_label flabel(stream.str());
1355  flabel.set_font_size(12);
1357  flabel.set_position(10, 100);
1359 
1361 }
1362 
1364 {
1365  if(fps_handle_ != 0) {
1367  fps_handle_ = 0;
1368  drawn_hexes_ = 0;
1369  invalidated_hexes_ = 0;
1370  }
1371 }
1372 
1374 {
1375  // Most panels are transparent.
1376  if (panel.image().empty()) {
1377  return;
1378  }
1379 
1380  const rect& loc = panel.location(video::game_canvas());
1381 
1382  if (!loc.overlaps(draw::get_clip())) {
1383  return;
1384  }
1385 
1386  DBG_DP << "drawing panel " << panel.get_id() << ' ' << loc;
1387 
1388  texture tex(image::get_texture(panel.image()));
1389  if (!tex) {
1390  ERR_DP << "failed to load panel " << panel.get_id()
1391  << " texture: " << panel.image();
1392  return;
1393  }
1394 
1395  draw::tiled(tex, loc);
1396 }
1397 
1399 {
1400  const rect& loc = label.location(video::game_canvas());
1401 
1402  if (!loc.overlaps(draw::get_clip())) {
1403  return;
1404  }
1405 
1406  const std::string& text = label.text();
1407  const color_t text_color = label.font_rgb_set() ? label.font_rgb() : font::NORMAL_COLOR;
1408  const std::string& icon = label.icon();
1409 
1410  DBG_DP << "drawing label " << label.get_id() << ' ' << loc;
1411 
1412  if(icon.empty() == false) {
1413  draw::blit(image::get_texture(icon), loc);
1414 
1415  if(text.empty() == false) {
1416  tooltips::add_tooltip(loc,text);
1417  }
1418  } else if(text.empty() == false) {
1419  font::pango_draw_text(true, loc, label.font_size(),
1420  text_color, text, loc.x, loc.y
1421  );
1422  }
1423 }
1424 
1425 bool display::draw_all_panels(const rect& region)
1426 {
1427  bool drew = false;
1429 
1430  for(const auto& panel : theme_.panels()) {
1431  if(region.overlaps(panel.location(game_canvas))) {
1432  draw_panel(panel);
1433  drew = true;
1434  }
1435  }
1436 
1437  for(const auto& label : theme_.labels()) {
1438  if(region.overlaps(label.location(game_canvas))) {
1439  draw_label(label);
1440  drew = true;
1441  }
1442  }
1443 
1444  return drew;
1445 }
1446 
1448  const drawing_layer layer,
1449  const std::string& text,
1450  std::size_t font_size,
1451  color_t color,
1452  double x_in_hex,
1453  double y_in_hex)
1454 {
1455  if (text.empty()) return;
1456 
1458  renderer.set_text(text, false);
1459  renderer.set_font_size(font_size * get_zoom_factor());
1460  renderer.set_maximum_width(-1);
1461  renderer.set_maximum_height(-1, false);
1462  renderer.set_foreground_color(color);
1463  renderer.set_add_outline(true);
1464 
1465  drawing_buffer_add(layer, loc, [x_in_hex, y_in_hex, res = renderer.render_and_get_texture()](const rect& dest) {
1466  draw::blit(res, {
1467  dest.x - (res.w() / 2) + static_cast<int>(x_in_hex * dest.w),
1468  dest.y - (res.h() / 2) + static_cast<int>(y_in_hex * dest.h),
1469  res.w(),
1470  res.h()
1471  });
1472  });
1473 }
1474 
1476 {
1478  selectedHex_ = hex;
1481 }
1482 
1484 {
1485  if(mouseoverHex_ == hex) {
1486  return;
1487  }
1489  mouseoverHex_ = hex;
1491 }
1492 
1493 void display::set_diagnostic(const std::string& msg)
1494 {
1495  if(diagnostic_label_ != 0) {
1497  diagnostic_label_ = 0;
1498  }
1499 
1500  if(!msg.empty()) {
1501  font::floating_label flabel(msg);
1503  flabel.set_color(font::YELLOW_COLOR);
1504  flabel.set_position(300, 50);
1505  flabel.set_clip_rect(map_outside_area());
1506 
1508  }
1509 }
1510 
1512 {
1513  static int time_between_draws = prefs::get().draw_delay();
1514  if(time_between_draws < 0) {
1515  time_between_draws = 1000 / video::current_refresh_rate();
1516  }
1517 
1518  frametimes_.push_back(SDL_GetTicks() - last_frame_finished_);
1519  fps_counter_++;
1520  using std::chrono::duration_cast;
1521  using std::chrono::seconds;
1522  using std::chrono::steady_clock;
1523  const seconds current_second = duration_cast<seconds>(steady_clock::now().time_since_epoch());
1524  if(current_second != fps_start_) {
1525  fps_start_ = current_second;
1527  fps_counter_ = 0;
1528  }
1529 
1530  last_frame_finished_ = SDL_GetTicks();
1531 }
1532 
1534 {
1535  for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
1536  if((*i)->pressed()) {
1537  const std::size_t index = std::distance(action_buttons_.begin(), i);
1538  if(index >= theme_.actions().size()) {
1539  assert(false);
1540  return nullptr;
1541  }
1542  return &theme_.actions()[index];
1543  }
1544  }
1545 
1546  return nullptr;
1547 }
1548 
1550 {
1551  for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) {
1552  if((*i)->pressed()) {
1553  const std::size_t index = std::distance(menu_buttons_.begin(), i);
1554  if(index >= theme_.menus().size()) {
1555  assert(false);
1556  return nullptr;
1557  }
1558  return theme_.get_menu_item((*i)->id());
1559  }
1560  }
1561 
1562  return nullptr;
1563 }
1564 
1565 void display::announce(const std::string& message, const color_t& color, const announce_options& options)
1566 {
1567  if(options.discard_previous) {
1568  font::remove_floating_label(prevLabel);
1569  }
1570  font::floating_label flabel(message);
1572  flabel.set_color(color);
1573  flabel.set_position(
1575  flabel.set_lifetime(options.lifetime);
1576  flabel.set_clip_rect(map_outside_area());
1577 
1578  prevLabel = font::add_floating_label(flabel);
1579 }
1580 
1582 {
1583  if(video::headless()) {
1584  return;
1585  }
1586 
1587  const rect& area = minimap_area();
1588  if(area.empty()){
1589  return;
1590  }
1591 
1593  get_map(),
1594  dc_->teams().empty() ? nullptr : &viewing_team(),
1595  nullptr,
1596  (selectedHex_.valid() && !is_blindfolded()) ? &reach_map_ : nullptr
1597  );
1598 
1599  redraw_minimap();
1600 }
1601 
1603 {
1605 }
1606 
1608 {
1609  const rect& area = minimap_area();
1610 
1611  if(area.empty() || !area.overlaps(draw::get_clip())) {
1612  return;
1613  }
1614 
1615  if(!minimap_renderer_) {
1616  return;
1617  }
1618 
1619  const auto clipper = draw::reduce_clip(area);
1620 
1621  // Draw the minimap background.
1622  draw::fill(area, 31, 31, 23);
1623 
1624  // Draw the minimap and update its location for mouse and units functions
1625  minimap_location_ = std::invoke(minimap_renderer_, area);
1626 
1628 
1629  // calculate the visible portion of the map:
1630  // scaling between minimap and full map images
1631  double xscaling = 1.0 * minimap_location_.w / (get_map().w() * hex_width());
1632  double yscaling = 1.0 * minimap_location_.h / (get_map().h() * hex_size());
1633 
1634  // we need to shift with the border size
1635  // and the 0.25 from the minimap balanced drawing
1636  // and the possible difference between real map and outside off-map
1637  SDL_Rect map_rect = map_area();
1638  SDL_Rect map_out_rect = map_outside_area();
1639  double border = theme_.border().size;
1640  double shift_x = -border * hex_width() - (map_out_rect.w - map_rect.w) / 2;
1641  double shift_y = -(border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2;
1642 
1643  int view_x = static_cast<int>((xpos_ + shift_x) * xscaling);
1644  int view_y = static_cast<int>((ypos_ + shift_y) * yscaling);
1645  int view_w = static_cast<int>(map_out_rect.w * xscaling);
1646  int view_h = static_cast<int>(map_out_rect.h * yscaling);
1647 
1648  SDL_Rect outline_rect {
1649  minimap_location_.x + view_x - 1,
1650  minimap_location_.y + view_y - 1,
1651  view_w + 2,
1652  view_h + 2
1653  };
1654 
1655  draw::rect(outline_rect, 255, 255, 255);
1656 }
1657 
1659 {
1660  if (!prefs::get().minimap_draw_units() || is_blindfolded()) return;
1661 
1662  double xscaling = 1.0 * minimap_location_.w / get_map().w();
1663  double yscaling = 1.0 * minimap_location_.h / get_map().h();
1664 
1665  for(const auto& u : dc_->units()) {
1666  if (fogged(u.get_location()) ||
1667  (viewing_team().is_enemy(u.side()) &&
1668  u.invisible(u.get_location())) ||
1669  u.get_hidden()) {
1670  continue;
1671  }
1672 
1673  int side = u.side();
1674  color_t col = team::get_minimap_color(side);
1675 
1676  if(!prefs::get().minimap_movement_coding()) {
1677  auto status = orb_status::allied;
1678  if(viewing_team().is_enemy(side)) {
1679  status = orb_status::enemy;
1680  } else if(viewing_team().side() == side) {
1681  status = dc_->unit_orb_status(u);
1682  } else {
1683  // no-op, status is already set to orb_status::allied;
1684  }
1686  }
1687 
1688  double u_x = u.get_location().x * xscaling;
1689  double u_y = (u.get_location().y + (is_odd(u.get_location().x) ? 1 : -1)/4.0) * yscaling;
1690  // use 4/3 to compensate the horizontal hexes imbrication
1691  double u_w = 4.0 / 3.0 * xscaling;
1692  double u_h = yscaling;
1693 
1694  SDL_Rect r {
1695  minimap_location_.x + int(std::round(u_x))
1696  , minimap_location_.y + int(std::round(u_y))
1697  , int(std::round(u_w))
1698  , int(std::round(u_h))
1699  };
1700 
1701  draw::fill(r, col.r, col.g, col.b, col.a);
1702  }
1703 }
1704 
1705 bool display::scroll(int xmove, int ymove, bool force)
1706 {
1707  if(view_locked_ && !force) {
1708  return false;
1709  }
1710 
1711  // No move offset, do nothing.
1712  if(xmove == 0 && ymove == 0) {
1713  return false;
1714  }
1715 
1716  int new_x = xpos_ + xmove;
1717  int new_y = ypos_ + ymove;
1718 
1719  bounds_check_position(new_x, new_y);
1720 
1721  // Camera position doesn't change, exit.
1722  if(xpos_ == new_x && ypos_ == new_y) {
1723  return false;
1724  }
1725 
1726  const int diff_x = xpos_ - new_x;
1727  const int diff_y = ypos_ - new_y;
1728 
1729  xpos_ = new_x;
1730  ypos_ = new_y;
1731 
1732  /* Adjust floating label positions. This only affects labels whose position is anchored
1733  * to the map instead of the screen. In order to do that, we want to adjust their drawing
1734  * coordinates in the opposite direction of the screen scroll.
1735  *
1736  * The check a few lines up prevents any scrolling from happening if the camera position
1737  * doesn't change. Without that, the label still scroll even when the map edge is reached.
1738  * If that's removed, the following formula should work instead:
1739  *
1740  * const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
1741  */
1742  font::scroll_floating_labels(diff_x, diff_y);
1743 
1745 
1746  //
1747  // NOTE: the next three blocks can be removed once we switch to accelerated rendering.
1748  //
1749 
1750  if(!video::headless()) {
1751  rect dst = map_area();
1752  dst.x += diff_x;
1753  dst.y += diff_y;
1754  dst.clip(map_area());
1755 
1756  rect src = dst;
1757  src.x -= diff_x;
1758  src.y -= diff_y;
1759 
1760  // swap buffers
1762 
1763  // Set the source region to blit from
1764  back_.set_src(src);
1765 
1766  // copy from the back to the front buffer
1767  auto rts = draw::set_render_target(front_);
1768  draw::blit(back_, dst);
1769 
1770  back_.clear_src();
1771 
1772  // queue repaint
1774  }
1775 
1776  if(diff_y != 0) {
1777  SDL_Rect r = map_area();
1778 
1779  if(diff_y < 0) {
1780  r.y = r.y + r.h + diff_y;
1781  }
1782 
1783  r.h = std::abs(diff_y);
1785  }
1786 
1787  if(diff_x != 0) {
1788  SDL_Rect r = map_area();
1789 
1790  if(diff_x < 0) {
1791  r.x = r.x + r.w + diff_x;
1792  }
1793 
1794  r.w = std::abs(diff_x);
1796  }
1797 
1799 
1800  redraw_minimap();
1801 
1802  return true;
1803 }
1804 
1806 {
1807  return zoom_ == MaxZoom;
1808 }
1809 
1811 {
1812  return zoom_ == MinZoom;
1813 }
1814 
1815 bool display::set_zoom(bool increase)
1816 {
1817  // Ensure we don't try to access nonexistent vector indices.
1818  zoom_index_ = std::clamp(increase ? zoom_index_ + 1 : zoom_index_ - 1, 0, final_zoom_index);
1819 
1820  // No validation check is needed in the next step since we've already set the index here and
1821  // know the new zoom value is indeed valid.
1822  return set_zoom(zoom_levels[zoom_index_], false);
1823 }
1824 
1825 bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_index)
1826 {
1827  unsigned int new_zoom = std::clamp(amount, MinZoom, MaxZoom);
1828 
1829  LOG_DP << "new_zoom = " << new_zoom;
1830 
1831  if(new_zoom == zoom_) {
1832  return false;
1833  }
1834 
1835  if(validate_value_and_set_index) {
1836  zoom_index_ = get_zoom_levels_index (new_zoom);
1837  new_zoom = zoom_levels[zoom_index_];
1838  }
1839 
1840  if((new_zoom / 4) * 4 != new_zoom) {
1841  WRN_DP << "set_zoom forcing zoom " << new_zoom
1842  << " which is not a multiple of 4."
1843  << " This will likely cause graphical glitches.";
1844  }
1845 
1846  const SDL_Rect& outside_area = map_outside_area();
1847  const SDL_Rect& area = map_area();
1848 
1849  // Turn the zoom factor to a double in order to avoid rounding errors.
1850  double zoom_factor = static_cast<double>(new_zoom) / static_cast<double>(zoom_);
1851 
1852  // INVARIANT: xpos_ + area.w == xend where xend is as in bounds_check_position()
1853  //
1854  // xpos_: Position of the leftmost visible map pixel of the viewport, in pixels.
1855  // Affected by the current zoom: this->zoom_ pixels to the hex.
1856  //
1857  // xpos_ + area.w/2: Position of the center of the viewport, in pixels.
1858  //
1859  // (xpos_ + area.w/2) * new_zoom/zoom_: Position of the center of the
1860  // viewport, as it would be under new_zoom.
1861  //
1862  // (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
1863  // leftmost visible map pixel, as it would be under new_zoom.
1864  xpos_ = std::round(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2));
1865  ypos_ = std::round(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2));
1866  xpos_ -= (outside_area.w - area.w) / 2;
1867  ypos_ -= (outside_area.h - area.h) / 2;
1868 
1869  zoom_ = new_zoom;
1871  if(zoom_ != DefaultZoom) {
1872  last_zoom_ = zoom_;
1873  }
1874 
1875  prefs::get().set_tile_size(zoom_);
1876 
1878  redraw_background_ = true;
1879  invalidate_all();
1880 
1881  return true;
1882 }
1883 
1885 {
1886  if (zoom_ != DefaultZoom) {
1887  last_zoom_ = zoom_;
1889  } else {
1890  // When we are already at the default zoom,
1891  // switch to the last zoom used
1893  }
1894 }
1895 
1897 {
1898  const auto [x, y] = get_location(loc);
1899  return !outside_area(map_area(), x, y);
1900 }
1901 
1903 {
1904  const auto [x, y] = get_location(loc);
1905  const SDL_Rect &area = map_area();
1906  int hw = hex_width(), hs = hex_size();
1907  return x + hs >= area.x - hw && x < area.x + area.w + hw &&
1908  y + hs >= area.y - hs && y < area.y + area.h + hs;
1909 }
1910 
1911 void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force)
1912 {
1913  if(!force && (view_locked_ || !prefs::get().scroll_to_action())) return;
1914  if(video::headless()) {
1915  return;
1916  }
1917  const SDL_Rect area = map_area();
1918  const int xmove_expected = screenxpos - (area.x + area.w/2);
1919  const int ymove_expected = screenypos - (area.y + area.h/2);
1920 
1921  int xpos = xpos_ + xmove_expected;
1922  int ypos = ypos_ + ymove_expected;
1923  bounds_check_position(xpos, ypos);
1924  int xmove = xpos - xpos_;
1925  int ymove = ypos - ypos_;
1926 
1927  if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || prefs::get().scroll_speed() > 99) {
1928  scroll(xmove,ymove,true);
1929  redraw_minimap();
1930  events::draw();
1931  return;
1932  }
1933 
1934  // Doing an animated scroll, with acceleration etc.
1935 
1936  int x_old = 0;
1937  int y_old = 0;
1938 
1939  const double dist_total = std::hypot(xmove, ymove);
1940  double dist_moved = 0.0;
1941 
1942  int t_prev = SDL_GetTicks();
1943 
1944  double velocity = 0.0;
1945  while (dist_moved < dist_total) {
1946  events::pump();
1947 
1948  int t = SDL_GetTicks();
1949  double dt = (t - t_prev) / 1000.0;
1950  if (dt > 0.200) {
1951  // Do not skip too many frames on slow PCs
1952  dt = 0.200;
1953  }
1954  t_prev = t;
1955 
1956  const double accel_time = 0.3 / turbo_speed(); // seconds until full speed is reached
1957  const double decel_time = 0.4 / turbo_speed(); // seconds from full speed to stop
1958 
1959  double velocity_max = prefs::get().scroll_speed() * 60.0;
1960  velocity_max *= turbo_speed();
1961  double accel = velocity_max / accel_time;
1962  double decel = velocity_max / decel_time;
1963 
1964  // If we started to decelerate now, where would we stop?
1965  double stop_time = velocity / decel;
1966  double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time;
1967  if (dist_stop > dist_total || velocity > velocity_max) {
1968  velocity -= decel * dt;
1969  if (velocity < 1.0) velocity = 1.0;
1970  } else {
1971  velocity += accel * dt;
1972  if (velocity > velocity_max) velocity = velocity_max;
1973  }
1974 
1975  dist_moved += velocity * dt;
1976  if (dist_moved > dist_total) dist_moved = dist_total;
1977 
1978  int x_new = std::round(xmove * dist_moved / dist_total);
1979  int y_new = std::round(ymove * dist_moved / dist_total);
1980 
1981  int dx = x_new - x_old;
1982  int dy = y_new - y_old;
1983 
1984  scroll(dx,dy,true);
1985  x_old += dx;
1986  y_old += dy;
1987 
1988  redraw_minimap();
1989  events::draw();
1990  }
1991 }
1992 
1993 void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, bool check_fogged, bool force)
1994 {
1995  if(get_map().on_board(loc) == false) {
1996  ERR_DP << "Tile at " << loc << " isn't on the map, can't scroll to the tile.";
1997  return;
1998  }
1999 
2000  std::vector<map_location> locs;
2001  locs.push_back(loc);
2002  scroll_to_tiles(locs, scroll_type, check_fogged,false,0.0,force);
2003 }
2004 
2006  SCROLL_TYPE scroll_type, bool check_fogged,
2007  double add_spacing, bool force)
2008 {
2009  std::vector<map_location> locs;
2010  locs.push_back(loc1);
2011  locs.push_back(loc2);
2012  scroll_to_tiles(locs, scroll_type, check_fogged, false, add_spacing,force);
2013 }
2014 
2015 void display::scroll_to_tiles(const std::vector<map_location>::const_iterator & begin,
2016  const std::vector<map_location>::const_iterator & end,
2017  SCROLL_TYPE scroll_type, bool check_fogged,
2018  bool only_if_possible, double add_spacing, bool force)
2019 {
2020  // basically we calculate the min/max coordinates we want to have on-screen
2021  int minx = 0;
2022  int maxx = 0;
2023  int miny = 0;
2024  int maxy = 0;
2025  bool valid = false;
2026 
2027  for(std::vector<map_location>::const_iterator itor = begin; itor != end ; ++itor) {
2028  if(get_map().on_board(*itor) == false) continue;
2029  if(check_fogged && fogged(*itor)) continue;
2030 
2031  const auto [x, y] = get_location(*itor);
2032 
2033  if (!valid) {
2034  minx = x;
2035  maxx = x;
2036  miny = y;
2037  maxy = y;
2038  valid = true;
2039  } else {
2040  int minx_new = std::min<int>(minx,x);
2041  int miny_new = std::min<int>(miny,y);
2042  int maxx_new = std::max<int>(maxx,x);
2043  int maxy_new = std::max<int>(maxy,y);
2044  SDL_Rect r = map_area();
2045  r.x = minx_new;
2046  r.y = miny_new;
2047  if(outside_area(r, maxx_new, maxy_new)) {
2048  // we cannot fit all locations to the screen
2049  if (only_if_possible) return;
2050  break;
2051  }
2052  minx = minx_new;
2053  miny = miny_new;
2054  maxx = maxx_new;
2055  maxy = maxy_new;
2056  }
2057  }
2058  //if everything is fogged or the location list is empty
2059  if(!valid) return;
2060 
2061  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2062  SDL_Rect r = map_area();
2063  int spacing = std::round(add_spacing * hex_size());
2064  r.x += spacing;
2065  r.y += spacing;
2066  r.w -= 2*spacing;
2067  r.h -= 2*spacing;
2068  if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) {
2069  return;
2070  }
2071  }
2072 
2073  // let's do "normal" rectangle math from now on
2074  SDL_Rect locs_bbox;
2075  locs_bbox.x = minx;
2076  locs_bbox.y = miny;
2077  locs_bbox.w = maxx - minx + hex_size();
2078  locs_bbox.h = maxy - miny + hex_size();
2079 
2080  // target the center
2081  int target_x = locs_bbox.x + locs_bbox.w/2;
2082  int target_y = locs_bbox.y + locs_bbox.h/2;
2083 
2084  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2085  // when doing an ONSCREEN scroll we do not center the target unless needed
2086  SDL_Rect r = map_area();
2087  int map_center_x = r.x + r.w/2;
2088  int map_center_y = r.y + r.h/2;
2089 
2090  int h = r.h;
2091  int w = r.w;
2092 
2093  // we do not want to be only inside the screen rect, but center a bit more
2094  double inside_frac = 0.5; // 0.0 = always center the target, 1.0 = scroll the minimum distance
2095  w = static_cast<int>(w * inside_frac);
2096  h = static_cast<int>(h * inside_frac);
2097 
2098  // shrink the rectangle by the size of the locations rectangle we found
2099  // such that the new task to fit a point into a rectangle instead of rectangle into rectangle
2100  w -= locs_bbox.w;
2101  h -= locs_bbox.h;
2102 
2103  if (w < 1) w = 1;
2104  if (h < 1) h = 1;
2105 
2106  r.x = target_x - w/2;
2107  r.y = target_y - h/2;
2108  r.w = w;
2109  r.h = h;
2110 
2111  // now any point within r is a possible target to scroll to
2112  // we take the one with the minimum distance to map_center
2113  // which will always be at the border of r
2114 
2115  if (map_center_x < r.x) {
2116  target_x = r.x;
2117  target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
2118  } else if (map_center_x > r.x+r.w-1) {
2119  target_x = r.x + r.w - 1;
2120  target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
2121  } else if (map_center_y < r.y) {
2122  target_y = r.y;
2123  target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
2124  } else if (map_center_y > r.y+r.h-1) {
2125  target_y = r.y + r.h - 1;
2126  target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
2127  } else {
2128  ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all...";
2129  // keep the target at the center
2130  }
2131  }
2132 
2133  scroll_to_xy(target_x, target_y,scroll_type,force);
2134 }
2135 
2136 
2138 {
2139  zoom_ = std::clamp(zoom_, MinZoom, MaxZoom);
2141 }
2142 
2143 void display::bounds_check_position(int& xpos, int& ypos) const
2144 {
2145  const int tile_width = hex_width();
2146 
2147  // Adjust for the border 2 times
2148  const int xend = static_cast<int>(tile_width * (get_map().w() + 2 * theme_.border().size) + tile_width / 3);
2149  const int yend = static_cast<int>(zoom_ * (get_map().h() + 2 * theme_.border().size) + zoom_ / 2);
2150 
2151  xpos = std::clamp(xpos, 0, xend - map_area().w);
2152  ypos = std::clamp(ypos, 0, yend - map_area().h);
2153 }
2154 
2155 double display::turbo_speed() const
2156 {
2157  bool res = prefs::get().turbo();
2158  if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT]) {
2159  res = !res;
2160  }
2161 
2162  res |= video::headless();
2163  if(res)
2164  return prefs::get().turbo_speed();
2165  else
2166  return 1.0;
2167 }
2168 
2170 {
2171  prevent_draw_ = pd;
2172  if (!pd) {
2173  // ensure buttons are visible
2174  unhide_buttons();
2175  }
2176 }
2177 
2179 {
2180  return prevent_draw_;
2181 }
2182 
2183 submerge_data display::get_submerge_data(const rect& dest, double submerge, const point& size, uint8_t alpha, bool hreverse, bool vreverse)
2184 {
2186  if(submerge <= 0.0) {
2187  return data;
2188  }
2189 
2190  // Set up blit destinations
2191  data.unsub_dest = dest;
2192  const int dest_sub_h = dest.h * submerge;
2193  data.unsub_dest.h -= dest_sub_h;
2194  const int dest_y_mid = dest.y + data.unsub_dest.h;
2195 
2196  // Set up blit src regions
2197  const int submersion_line = size.y * (1.0 - submerge);
2198  data.unsub_src = {0, 0, size.x, submersion_line};
2199 
2200  // Set up shader vertices
2201  const color_t c_mid(255, 255, 255, 0.3 * alpha);
2202  const int pixels_submerged = size.y * submerge;
2203  const int bot_alpha = std::max(0.3 - pixels_submerged * 0.015, 0.0) * alpha;
2204  const color_t c_bot(255, 255, 255, bot_alpha);
2205  const SDL_FPoint pML{float(dest.x), float(dest_y_mid)};
2206  const SDL_FPoint pMR{float(dest.x + dest.w), float(dest_y_mid)};
2207  const SDL_FPoint pBL{float(dest.x), float(dest.y + dest.h)};
2208  const SDL_FPoint pBR{float(dest.x + dest.w), float(dest.y + dest.h)};
2209  data.alpha_verts = {
2210  SDL_Vertex{pML, c_mid, {0.0, float(1.0 - submerge)}},
2211  SDL_Vertex{pMR, c_mid, {1.0, float(1.0 - submerge)}},
2212  SDL_Vertex{pBL, c_bot, {0.0, 1.0}},
2213  SDL_Vertex{pBR, c_bot, {1.0, 1.0}},
2214  };
2215 
2216  if(hreverse) {
2217  for(SDL_Vertex& v : data.alpha_verts) {
2218  v.tex_coord.x = 1.0 - v.tex_coord.x;
2219  }
2220  }
2221  if(vreverse) {
2222  for(SDL_Vertex& v : data.alpha_verts) {
2223  v.tex_coord.y = 1.0 - v.tex_coord.y;
2224  }
2225  }
2226 
2227  return data;
2228 }
2229 
2231  const std::string& old_mask,
2232  const std::string& new_mask)
2233 {
2234  // TODO: hwaccel - this needs testing as it's not used in mainline
2237 
2238  int duration = 300 / turbo_speed();
2239  int start = SDL_GetTicks();
2240  for(int now = start; now < start + duration; now = SDL_GetTicks()) {
2241  float prop_f = float(now - start) / float(duration);
2242  uint8_t p = float_to_color(prop_f);
2243  tod_hex_alpha2 = p;
2244  tod_hex_alpha1 = ~p;
2247  }
2248 
2249  tod_hex_mask1.reset();
2250  tod_hex_mask2.reset();
2251 }
2252 
2253 void display::fade_to(const color_t& c, int duration)
2254 {
2255  uint32_t start = SDL_GetTicks();
2256  color_t fade_start = fade_color_;
2257  color_t fade_end = c;
2258 
2259  // If we started transparent, assume the same colour
2260  if(fade_start.a == 0) {
2261  fade_start.r = fade_end.r;
2262  fade_start.g = fade_end.g;
2263  fade_start.b = fade_end.b;
2264  }
2265 
2266  // If we are ending transparent, assume the same colour
2267  if(fade_end.a == 0) {
2268  fade_end.r = fade_start.r;
2269  fade_end.g = fade_start.g;
2270  fade_end.b = fade_start.b;
2271  }
2272 
2273  // Smoothly blend and display
2274  for(uint32_t now = start; now < start + duration; now = SDL_GetTicks()) {
2275  float prop_f = float(now - start) / float(duration);
2276  uint8_t p = float_to_color(prop_f);
2277  fade_color_ = fade_start.smooth_blend(fade_end, p);
2280  }
2281  fade_color_ = fade_end;
2283  events::draw();
2284 }
2285 
2287 {
2288  fade_color_ = c;
2289 }
2290 
2292 {
2293  if(video::headless())
2294  return;
2295 
2296  DBG_DP << "redrawing everything";
2297 
2298  // This is specifically for game_display.
2299  // It would probably be better to simply make this function virtual,
2300  // if game_display needs to do special processing.
2301  invalidateGameStatus_ = true;
2302 
2303  reportLocations_.clear();
2304  reportSurfaces_.clear();
2305  reports_.clear();
2306 
2308 
2310 
2312 
2313  if(!menu_buttons_.empty() || !action_buttons_.empty()) {
2314  create_buttons();
2315  }
2316 
2317  if(resources::controller) {
2319  if(command_executor != nullptr) {
2320  // This function adds button overlays,
2321  // it needs to be run after recreating the buttons.
2322  command_executor->set_button_state();
2323  }
2324  }
2325 
2326  if (!gui::in_dialog()) {
2328  }
2329 
2330  redraw_background_ = true;
2331 
2332  // This is only for one specific use, which is by the editor controller.
2333  // It would be vastly better if this didn't exist.
2334  for(std::function<void(display&)> f : redraw_observers_) {
2335  f(*this);
2336  }
2337 
2338  invalidate_all();
2339 
2341 }
2342 
2344 {
2345  // Could redraw a smaller region if the display doesn't use it all,
2346  // but when does that ever happen?
2348 }
2349 
2350 void display::add_redraw_observer(std::function<void(display&)> f)
2351 {
2352  redraw_observers_.push_back(f);
2353 }
2354 
2356 {
2357  redraw_observers_.clear();
2358 }
2359 
2361 {
2362  if(video::headless()) {
2363  DBG_DP << "display::draw denied";
2364  return;
2365  }
2366  //DBG_DP << "display::draw";
2367 
2368  // I have no idea why this is messing with sync context,
2369  // but i'm not going to touch it.
2371 
2372  // This isn't the best, but also isn't important enough to do better.
2374  DBG_DP << "display::draw redraw background";
2377  redraw_background_ = false;
2378  }
2379 
2380  if(!get_map().empty()) {
2381  if(!invalidated_.empty()) {
2382  draw_invalidated();
2383  invalidated_.clear();
2384  }
2386  }
2387 
2388  if(prefs::get().show_fps() || debug_flag_set(DEBUG_BENCHMARK)) {
2389  update_fps_label();
2390  update_fps_count();
2391  } else if(fps_handle_ != 0) {
2392  clear_fps_label();
2393  }
2394 }
2395 
2397 {
2398  //DBG_DP << "display::update";
2399  // Ensure render textures are correctly sized and up-to-date.
2401 
2402  // Trigger cache rebuild if animated water preference has changed.
2403  if(animate_water_ != prefs::get().animate_water()) {
2404  animate_water_ = prefs::get().animate_water();
2405  builder_->rebuild_cache_all();
2406  }
2407 
2409  invalidate_all();
2410  }
2411 }
2412 
2414 {
2415  //DBG_DP << "display::layout";
2416 
2417  // There's nothing that actually does layout here, it all happens in
2418  // response to events. This isn't ideal, but neither is changing that.
2419 
2420  // Post-layout / Pre-render
2421 
2422  if (!get_map().empty()) {
2423  if(redraw_background_) {
2424  invalidateAll_ = true;
2425  }
2426  if(invalidateAll_) {
2427  DBG_DP << "draw() with invalidateAll";
2428 
2429  // toggle invalidateAll_ first to allow regular invalidations
2430  invalidateAll_ = false;
2432 
2433  redraw_minimap();
2434  }
2435  }
2436 
2437  // invalidate animated terrain, units and haloes
2439 
2440  // Update and invalidate floating labels as necessary
2442 }
2443 
2445 {
2446  // This should render the game map and units.
2447  // It is not responsible for halos and floating labels.
2448  //DBG_DP << "display::render";
2449 
2450  // No need to render if we aren't going to draw anything.
2451  if(prevent_draw_) {
2452  DBG_DP << "render prevented";
2453  return;
2454  }
2455 
2456  // render to the offscreen buffer
2457  auto target_setter = draw::set_render_target(front_);
2458  draw();
2459 
2460  // update the minimap texture, if necessary
2461  // TODO: highdpi - high DPI minimap
2462  const rect& area = minimap_area();
2463  if(!area.empty() && !minimap_renderer_) {
2465  }
2466 }
2467 
2468 bool display::expose(const rect& region)
2469 {
2470  if(prevent_draw_) {
2471  DBG_DP << "draw prevented";
2472  return false;
2473  }
2474 
2475  rect clipped_region = draw::get_clip().intersect(region);
2476 
2477  // Blit from the pre-rendered front buffer.
2478  if(clipped_region.overlaps(map_outside_area())) {
2479  front_.set_src(clipped_region);
2480  draw::blit(front_, clipped_region);
2481  front_.clear_src();
2482  }
2483 
2484  // Render halos.
2485  halo_man_.render(clipped_region);
2486 
2487  // Render UI elements.
2488  // Ideally buttons would be drawn as part of panels,
2489  // but they are currently TLDs so they draw themselves.
2490  // This also means they draw over tooltips...
2491  draw_all_panels(clipped_region);
2492  draw_reports(clipped_region);
2493  if(clipped_region.overlaps(minimap_area())) {
2494  draw_minimap();
2495  }
2496 
2497  // Floating labels should probably be separated by type,
2498  // but they aren't so they all get drawn here.
2500 
2501  // If there's a fade, apply it over everything
2502  if(fade_color_.a) {
2503  draw::fill(map_outside_area().intersect(region), fade_color_);
2504  }
2505 
2506  DBG_DP << "display::expose " << region;
2507 
2508  // The display covers the entire screen.
2509  // We will always be drawing something.
2510  return true;
2511 }
2512 
2514 {
2515  assert(!map_screenshot_);
2516  // There's no good way to determine this, as themes can put things
2517  // anywhere. Just return the entire game canvas.
2518  return video::game_canvas();
2519 }
2520 
2522 {
2523  if(video::headless()) {
2524  return;
2525  }
2526 
2527  // We ignore any logical offset on the underlying window buffer.
2528  // Render buffer size is always a simple multiple of the draw area.
2529  rect darea = video::game_canvas();
2530  rect oarea = darea * video::get_pixel_scale();
2531 
2532  // Check that the front buffer size is correct.
2533  // Buffers are always resized together, so we only need to check one.
2535  point dsize = front_.draw_size();
2536  bool raw_size_changed = size.x != oarea.w || size.y != oarea.h;
2537  bool draw_size_changed = dsize.x != darea.w || dsize.y != darea.h;
2538  if (!raw_size_changed && !draw_size_changed) {
2539  // buffers are fine
2540  return;
2541  }
2542 
2543  if(raw_size_changed) {
2544  LOG_DP << "regenerating render buffers as " << oarea;
2545  front_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2546  back_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2547  }
2548  if(raw_size_changed || draw_size_changed) {
2549  LOG_DP << "updating render buffer draw size to " << darea;
2550  front_.set_draw_size(darea.w, darea.h);
2551  back_.set_draw_size(darea.w, darea.h);
2552  }
2553 
2554  // Fill entire texture with black, just in case
2555  for(int i = 0; i < 2; ++i) {
2556  auto setter = draw::set_render_target(i ? back_ : front_);
2557  draw::fill(0,0,0);
2558  }
2559 
2560  // Fill in the background area on both textures.
2562 
2563  queue_rerender();
2564 }
2565 
2567 {
2568  // This could be optimized to avoid the map area,
2569  // but it's only called on game creation or zoom anyway.
2570  const SDL_Rect clip_rect = map_outside_area();
2572  for(int i = 0; i < 2; ++i) {
2573  auto setter = draw::set_render_target(i ? back_ : front_);
2574  if(bgtex) {
2575  draw::tiled(bgtex, clip_rect);
2576  } else {
2577  draw::fill(clip_rect, 0, 0, 0);
2578  }
2579  }
2580 }
2581 
2583 {
2584  return *map_labels_;
2585 }
2586 
2588 {
2589  return *map_labels_;
2590 }
2591 
2593 {
2594  return map_area();
2595 }
2596 
2598 {
2599  // log_scope("display::draw_invalidated");
2600  SDL_Rect clip_rect = get_clip_rect();
2601  const auto clipper = draw::reduce_clip(clip_rect);
2602 
2603  DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes with clip " << clip_rect;
2604 
2605  // The unit drawer can't function without teams
2606  utils::optional<unit_drawer> drawer{};
2607  if(!dc_->teams().empty()) {
2608  drawer.emplace(*this);
2609  }
2610 
2611  for(const map_location& loc : invalidated_) {
2612  const auto [xpos, ypos] = get_location(loc);
2613 
2614  rect hex_rect(xpos, ypos, zoom_, zoom_);
2615  if(!hex_rect.overlaps(clip_rect)) {
2616  continue;
2617  }
2618 
2619  draw_hex(loc);
2620  drawn_hexes_ += 1;
2621 
2622  if(drawer) {
2623  const auto u_it = dc_->units().find(loc);
2624  const auto request = exclusive_unit_draw_requests_.find(loc);
2625 
2626  if(u_it != dc_->units().end() && (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id())) {
2627  drawer->redraw_unit(*u_it);
2628  }
2629  }
2630 
2631  draw_manager::invalidate_region(hex_rect.intersect(clip_rect));
2632  }
2633 
2634  invalidated_hexes_ += invalidated_.size();
2635 }
2636 
2638 {
2639  const bool on_map = get_map().on_board(loc);
2640  const time_of_day& tod = get_time_of_day(loc);
2641 
2642  int num_images_fg = 0;
2643  int num_images_bg = 0;
2644 
2645  const bool is_shrouded = shrouded(loc);
2646 
2647  // unshrouded terrain (the normal case)
2648  if(!is_shrouded) {
2649  get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
2650  num_images_bg = terrain_image_vector_.size();
2651 
2652  drawing_buffer_add(drawing_layer::terrain_bg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2653  for(const texture& t : images) {
2654  draw::blit(t, dest);
2655  }
2656  });
2657 
2658  get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
2659  num_images_fg = terrain_image_vector_.size();
2660 
2661  drawing_buffer_add(drawing_layer::terrain_fg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2662  for(const texture& t : images) {
2663  draw::blit(t, dest);
2664  }
2665  });
2666 
2667  // Draw the grid, if that's been enabled
2668  if(prefs::get().grid()) {
2671 
2673  [tex = image::get_texture(grid_top, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2674 
2676  [tex = image::get_texture(grid_bottom, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2677  }
2678 
2679  // overlays (TODO: can we just draw all the overlays in one pass instead of per-hex?)
2680  draw_overlays_at(loc);
2681 
2682  // village-control flags.
2683  if(get_map().is_village(loc)) {
2685  [tex = get_flag(loc)](const rect& dest) { draw::blit(tex, dest); });
2686  }
2687  }
2688 
2689  // Draw the time-of-day mask on top of the terrain in the hex.
2690  // tod may differ from tod if hex is illuminated.
2691  const std::string& tod_hex_mask = tod.image_mask;
2692  if(tod_hex_mask1 || tod_hex_mask2) {
2693  drawing_buffer_add(drawing_layer::terrain_fg, loc, [this](const rect& dest) mutable {
2695  draw::blit(tod_hex_mask1, dest);
2696 
2698  draw::blit(tod_hex_mask2, dest);
2699  });
2700  } else if(!tod_hex_mask.empty()) {
2702  [tex = image::get_texture(tod_hex_mask, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
2703  }
2704 
2705  // Paint arrows
2706  if(auto arrows_in_hex = arrows_map_.find(loc); arrows_in_hex != arrows_map_.end()) {
2707  std::vector<texture> to_draw;
2708  for(const arrow* a : arrows_in_hex->second) {
2709  to_draw.push_back(image::get_texture(a->get_image_for_loc(loc)));
2710  }
2711 
2712  drawing_buffer_add(drawing_layer::arrows, loc, [to_draw = std::move(to_draw)](const rect& dest) {
2713  for(const texture& t : to_draw) {
2714  draw::blit(t, dest);
2715  }
2716  });
2717  }
2718 
2719  // Apply shroud, fog and linger overlay
2720 
2721  if(is_shrouded || fogged(loc)) {
2722  // TODO: better noise function
2723  const auto get_variant = [&loc](const std::vector<std::string>& variants) -> const auto& {
2724  return variants[std::abs(loc.x + loc.y) % variants.size()];
2725  };
2726 
2727  const std::string& img = get_variant(is_shrouded ? shroud_images_ : fog_images_);
2729  [tex = image::get_texture(img, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2730  }
2731 
2732  if(!is_shrouded) {
2734  for(const texture& t : images) {
2735  draw::blit(t, dest);
2736  }
2737  });
2738  }
2739 
2741  using namespace std::string_literals;
2743  [tex = image::get_texture("terrain/foreground.png"s)](const rect& dest) { draw::blit(tex, dest); });
2744  }
2745 
2746  if(on_map) {
2747  // This might be slight overkill. Basically, we want to check that none of the
2748  // first three bits in the debug flag bitset are set so we can avoid creating
2749  // a stringstream, a temp string, and attempting to trim it for every hex even
2750  // when none of these flags are set. This gives us a temp object with all bits
2751  // past the first three zeroed out.
2752  if((std::as_const(debug_flags_) << (__NUM_DEBUG_FLAGS - DEBUG_FOREGROUND)).none()) {
2753  return;
2754  }
2755 
2756  std::ostringstream ss;
2758  ss << loc << '\n';
2759  }
2760 
2762  ss << get_map().get_terrain(loc) << '\n';
2763  }
2764 
2766  ss << (num_images_bg + num_images_fg) << '\n';
2767  }
2768 
2769  std::string output = ss.str();
2771 
2772  if(output.empty()) {
2773  return;
2774  }
2775 
2777  renderer.set_text(output, false);
2778  renderer.set_font_size(font::SIZE_TINY);
2779  renderer.set_alignment(PANGO_ALIGN_CENTER);
2780  renderer.set_foreground_color(font::NORMAL_COLOR);
2781  renderer.set_maximum_height(-1, false);
2782  renderer.set_maximum_width(-1);
2783 
2784  drawing_buffer_add(drawing_layer::fog_shroud, loc, [tex = renderer.render_and_get_texture()](const rect& dest) {
2785  const rect text_dest {
2786  (dest.x + dest.w / 2) - (tex.w() / 2),
2787  (dest.y + dest.h / 2) - (tex.h() / 2),
2788  tex.w(),
2789  tex.h()
2790  };
2791 
2792  // Add a little horizontal padding to the bg
2793  const rect bg_dest {
2794  text_dest.x - 3,
2795  text_dest.y - 3,
2796  text_dest.w + 6,
2797  text_dest.h + 6
2798  };
2799 
2800  draw::fill(bg_dest, {0, 0, 0, 0xaa});
2801  draw::blit(tex, text_dest);
2802  });
2803  }
2804 }
2805 
2807 {
2808  auto it = get_overlays().find(loc);
2809  if(it == get_overlays().end()) {
2810  return;
2811  }
2812 
2813  std::vector<overlay>& overlays = it->second;
2814  if(overlays.empty()) {
2815  return;
2816  }
2817 
2818  const time_of_day& tod = get_time_of_day(loc);
2819  tod_color tod_col = tod.color + color_adjust_;
2820 
2821  image::light_string lt = image::get_light_string(-1, tod_col.r, tod_col.g, tod_col.b);
2822 
2823  for(const overlay& ov : overlays) {
2824  if(fogged(loc) && !ov.visible_in_fog) {
2825  continue;
2826  }
2827 
2828  if(dont_show_all_ && !ov.team_name.empty()) {
2829  // dont_show_all_ imples that viewing_team() is a valid index to get_teams()
2830  const std::string& current_team_name = viewing_team().team_name();
2831  const std::vector<std::string>& current_team_names = utils::split(current_team_name);
2832  const std::vector<std::string>& team_names = utils::split(ov.team_name);
2833 
2834  bool item_visible_for_team = std::find_first_of(team_names.begin(), team_names.end(),
2835  current_team_names.begin(), current_team_names.end()) != team_names.end();
2836 
2837  if(!item_visible_for_team) {
2838  continue;
2839  }
2840  }
2841 
2842  texture tex = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos
2843  ? image::get_lighted_texture(ov.image, lt)
2844  : image::get_texture(ov.image, image::HEXED);
2845 
2846  // Base submerge value for the terrain at this location
2847  const double ter_sub = get_map().get_terrain_info(loc).unit_submerge();
2848 
2850  drawing_layer::terrain_bg, loc, [this, tex, ter_sub, ovr_sub = ov.submerge](const rect& dest) mutable {
2851  if(ovr_sub > 0.0 && ter_sub > 0.0) {
2852  // Adjust submerge appropriately
2853  double submerge = ter_sub * ovr_sub;
2854 
2855  submerge_data data
2856  = this->get_submerge_data(dest, submerge, tex.draw_size(), ALPHA_OPAQUE, false, false);
2857 
2858  // set clip for dry part
2859  // smooth_shaded doesn't use the clip information so it's fine to set it up front
2860  // TODO: do we need to unset this?
2861  tex.set_src(data.unsub_src);
2862 
2863  // draw underwater part
2864  draw::smooth_shaded(tex, data.alpha_verts);
2865 
2866  // draw dry part
2867  draw::blit(tex, data.unsub_dest);
2868  } else {
2869  // draw whole texture
2870  draw::blit(tex, dest);
2871  }
2872  });
2873  }
2874 }
2875 
2876 /**
2877  * Redraws the specified report (if anything has changed).
2878  * If a config is not supplied, it will be generated via
2879  * reports::generate_report().
2880  */
2881 void display::refresh_report(const std::string& report_name, const config * new_cfg)
2882 {
2883  const theme::status_item *item = theme_.get_status_item(report_name);
2884  if (!item) {
2885  // This should be a warning, but unfortunately there are too many
2886  // unused reports to easily deal with.
2887  //WRN_DP << "no report '" << report_name << "' in theme";
2888  return;
2889  }
2890 
2891  // Now we will need the config. Generate one if needed.
2892 
2894 
2895  if (resources::controller) {
2897  }
2898 
2899  reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
2900 
2901  const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
2902  if ( new_cfg == nullptr )
2903  new_cfg = &generated_cfg;
2904 
2905  rect& loc = reportLocations_[report_name];
2906  const rect& new_loc = item->location(video::game_canvas());
2907  config &report = reports_[report_name];
2908 
2909  // Report and its location is unchanged since last time. Do nothing.
2910  if (loc == new_loc && report == *new_cfg) {
2911  return;
2912  }
2913 
2914  DBG_DP << "updating report: " << report_name;
2915 
2916  // Mark both old and new locations for redraw.
2919 
2920  // Update the config and current location.
2921  report = *new_cfg;
2922  loc = new_loc;
2923 
2924  // Not 100% sure this is okay
2925  // but it seems to be working so i'm not changing it.
2927 
2928  if (report.empty()) return;
2929 
2930  // Add prefix, postfix elements.
2931  // Make sure that they get the same tooltip
2932  // as the guys around them.
2933  std::string str = item->prefix();
2934  if (!str.empty()) {
2935  config &e = report.add_child_at("element", config(), 0);
2936  e["text"] = str;
2937  e["tooltip"] = report.mandatory_child("element")["tooltip"];
2938  }
2939  str = item->postfix();
2940  if (!str.empty()) {
2941  config &e = report.add_child("element");
2942  e["text"] = str;
2943  e["tooltip"] = report.mandatory_child("element", -1)["tooltip"];
2944  }
2945 
2946  // Do a fake run of drawing the report, so tooltips can be determined.
2947  // TODO: this is horrible, refactor reports to actually make sense
2948  draw_report(report_name, true);
2949 }
2950 
2951 void display::draw_report(const std::string& report_name, bool tooltip_test)
2952 {
2953  const theme::status_item *item = theme_.get_status_item(report_name);
2954  if (!item) {
2955  // This should be a warning, but unfortunately there are too many
2956  // unused reports to easily deal with.
2957  //WRN_DP << "no report '" << report_name << "' in theme";
2958  return;
2959  }
2960 
2961  const rect& loc = reportLocations_[report_name];
2962  const config& report = reports_[report_name];
2963 
2964  int x = loc.x, y = loc.y;
2965 
2966  // Loop through and display each report element.
2967  int tallest = 0;
2968  int image_count = 0;
2969  bool used_ellipsis = false;
2970  std::ostringstream ellipsis_tooltip;
2971  SDL_Rect ellipsis_area = loc;
2972 
2973  for (config::const_child_itors elements = report.child_range("element");
2974  elements.begin() != elements.end(); elements.pop_front())
2975  {
2976  SDL_Rect area {x, y, loc.w + loc.x - x, loc.h + loc.y - y};
2977  if (area.h <= 0) break;
2978 
2979  std::string t = elements.front()["text"];
2980  if (!t.empty())
2981  {
2982  if (used_ellipsis) goto skip_element;
2983 
2984  // Draw a text element.
2986  bool eol = false;
2987  if (t[t.size() - 1] == '\n') {
2988  eol = true;
2989  t = t.substr(0, t.size() - 1);
2990  }
2991  // If stripping left the text empty, skip it.
2992  if (t.empty()) {
2993  // Blank text has a null size when rendered.
2994  // It does not, however, have a null size when the size
2995  // is requested with get_size(). Hence this check.
2996  continue;
2997  }
2998  text.set_link_aware(false)
2999  .set_text(t, true);
3001  .set_font_size(item->font_size())
3003  .set_alignment(PANGO_ALIGN_LEFT)
3004  .set_foreground_color(item->font_rgb_set() ? item->font_rgb() : font::NORMAL_COLOR)
3005  .set_maximum_width(area.w)
3006  .set_maximum_height(area.h, false)
3007  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
3009 
3010  point tsize = text.get_size();
3011 
3012  // check if next element is text with almost no space to show it
3013  const int minimal_text = 12; // width in pixels
3014  config::const_child_iterator ee = elements.begin();
3015  if (!eol && loc.w - (x - loc.x + tsize.x) < minimal_text &&
3016  ++ee != elements.end() && !(*ee)["text"].empty())
3017  {
3018  // make this element longer to trigger rendering of ellipsis
3019  // (to indicate that next elements have not enough space)
3020  //NOTE this space should be longer than minimal_text pixels
3021  t = t + " ";
3022  text.set_text(t, true);
3023  tsize = text.get_size();
3024  // use the area of this element for next tooltips
3025  used_ellipsis = true;
3026  ellipsis_area.x = x;
3027  ellipsis_area.y = y;
3028  ellipsis_area.w = tsize.x;
3029  ellipsis_area.h = tsize.y;
3030  }
3031 
3032  area.w = tsize.x;
3033  area.h = tsize.y;
3034  if (!tooltip_test) {
3035  draw::blit(text.render_and_get_texture(), area);
3036  }
3037  if (area.h > tallest) {
3038  tallest = area.h;
3039  }
3040  if (eol) {
3041  x = loc.x;
3042  y += tallest;
3043  tallest = 0;
3044  } else {
3045  x += area.w;
3046  }
3047  }
3048  else if (!(t = elements.front()["image"].str()).empty())
3049  {
3050  if (used_ellipsis) goto skip_element;
3051 
3052  // Draw an image element.
3054 
3055  if (!img) {
3056  ERR_DP << "could not find image for report: '" << t << "'";
3057  continue;
3058  }
3059 
3060  if (area.w < img.w() && image_count) {
3061  // We have more than one image, and this one doesn't fit.
3063  used_ellipsis = true;
3064  }
3065 
3066  if (img.w() < area.w) area.w = img.w();
3067  if (img.h() < area.h) area.h = img.h();
3068  if (!tooltip_test) {
3069  draw::blit(img, area);
3070  }
3071 
3072  ++image_count;
3073  if (area.h > tallest) {
3074  tallest = area.h;
3075  }
3076 
3077  if (!used_ellipsis) {
3078  x += area.w;
3079  } else {
3080  ellipsis_area = area;
3081  }
3082  }
3083  else
3084  {
3085  // No text nor image, skip this element
3086  continue;
3087  }
3088 
3089  skip_element:
3090  t = elements.front()["tooltip"].t_str().c_str();
3091  if (!t.empty()) {
3092  if (tooltip_test && !used_ellipsis) {
3093  tooltips::add_tooltip(area, t, elements.front()["help"].t_str().c_str());
3094  } else {
3095  // Collect all tooltips for the ellipsis.
3096  // TODO: need a better separator
3097  // TODO: assign an action
3098  ellipsis_tooltip << t;
3099  config::const_child_iterator ee = elements.begin();
3100  if (++ee != elements.end())
3101  ellipsis_tooltip << "\n _________\n\n";
3102  }
3103  }
3104  }
3105 
3106  if (tooltip_test && used_ellipsis) {
3107  tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str());
3108  }
3109 }
3110 
3111 bool display::draw_reports(const rect& region)
3112 {
3113  bool drew = false;
3114  for(const auto& it : reports_) {
3115  const std::string& name = it.first;
3116  const rect& loc = reportLocations_[name];
3117  if(loc.overlaps(region)) {
3118  draw_report(name);
3119  drew = true;
3120  }
3121  }
3122  return drew;
3123 }
3124 
3126 {
3127  DBG_DP << "invalidate_all()";
3128  invalidateAll_ = true;
3129  invalidated_.clear();
3130 }
3131 
3133 {
3135  return false;
3136 
3137  bool tmp;
3138  tmp = invalidated_.insert(loc).second;
3139  return tmp;
3140 }
3141 
3142 bool display::invalidate(const std::set<map_location>& locs)
3143 {
3145  return false;
3146  bool ret = false;
3147  for (const map_location& loc : locs) {
3148  ret = invalidated_.insert(loc).second || ret;
3149  }
3150  return ret;
3151 }
3152 
3153 bool display::propagate_invalidation(const std::set<map_location>& locs)
3154 {
3155  if(invalidateAll_)
3156  return false;
3157 
3158  if(locs.size()<=1)
3159  return false; // propagation never needed
3160 
3161  bool result = false;
3162  {
3163  // search the first hex invalidated (if any)
3164  std::set<map_location>::const_iterator i = locs.begin();
3165  for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {}
3166 
3167  if (i != locs.end()) {
3168 
3169  // propagate invalidation
3170  // 'i' is already in, but I suspect that splitting the range is bad
3171  // especially because locs are often adjacents
3172  size_t previous_size = invalidated_.size();
3173  invalidated_.insert(locs.begin(), locs.end());
3174  result = previous_size < invalidated_.size();
3175  }
3176  }
3177  return result;
3178 }
3179 
3181 {
3182  return invalidate_locations_in_rect(map_area().intersect(rect));
3183 }
3184 
3186 {
3188  return false;
3189 
3190  DBG_DP << "invalidating locations in " << rect;
3191 
3192  bool result = false;
3193  for(const map_location& loc : hexes_under_rect(rect)) {
3194  //DBG_DP << "invalidating " << loc.x << ',' << loc.y;
3195  result |= invalidate(loc);
3196  }
3197  return result;
3198 }
3199 
3201 {
3202  if(get_map().is_village(loc)) {
3203  const int owner = dc_->village_owner(loc) - 1;
3204  if(owner >= 0 && flags_[owner].need_update()
3205  && (!fogged(loc) || !viewing_team().is_enemy(owner + 1))) {
3206  invalidate(loc);
3207  }
3208  }
3209 }
3210 
3212 {
3213  // There are timing issues with this, but i'm not touching it.
3215  animate_map_ = prefs::get().animate_map();
3216  if(animate_map_) {
3217  for(const map_location& loc : get_visible_hexes()) {
3218  if(shrouded(loc))
3219  continue;
3220  if(builder_->update_animation(loc)) {
3221  invalidate(loc);
3222  } else {
3224  }
3225  }
3226  }
3227 
3228  for(const unit& u : dc_->units()) {
3229  u.anim_comp().refresh();
3230  }
3231  for(const unit* u : *fake_unit_man_) {
3232  u->anim_comp().refresh();
3233  }
3234 
3235  bool new_inval;
3236  do {
3237  new_inval = false;
3238  for(const unit& u : dc_->units()) {
3239  new_inval |= u.anim_comp().invalidate(*this);
3240  }
3241  for(const unit* u : *fake_unit_man_) {
3242  new_inval |= u->anim_comp().invalidate(*this);
3243  }
3244  } while(new_inval);
3245 
3246  halo_man_.update();
3247 }
3248 
3250 {
3251  for(const unit & u : dc_->units()) {
3252  u.anim_comp().set_standing();
3253  }
3254 }
3255 
3257 {
3258  for(const map_location& loc : arrow.get_path()) {
3259  arrows_map_[loc].push_back(&arrow);
3260  }
3261 }
3262 
3264 {
3265  for(const map_location& loc : arrow.get_path()) {
3266  arrows_map_[loc].remove(&arrow);
3267  }
3268 }
3269 
3271 {
3272  for(const map_location& loc : arrow.get_previous_path()) {
3273  arrows_map_[loc].remove(&arrow);
3274  }
3275 
3276  for(const map_location& loc : arrow.get_path()) {
3277  arrows_map_[loc].push_back(&arrow);
3278  }
3279 }
3280 
3282 {
3283  const SDL_Rect& rect = map_area();
3284  return pixel_position_to_hex(xpos_ + rect.x + rect.w / 2 , ypos_ + rect.y + rect.h / 2 );
3285 }
3286 
3287 void display::write(config& cfg) const
3288 {
3289  cfg["view_locked"] = view_locked_;
3290  cfg["color_adjust_red"] = color_adjust_.r;
3291  cfg["color_adjust_green"] = color_adjust_.g;
3292  cfg["color_adjust_blue"] = color_adjust_.b;
3293  get_middle_location().write(cfg.add_child("location"));
3294 }
3295 
3296 void display::read(const config& cfg)
3297 {
3298  view_locked_ = cfg["view_locked"].to_bool(false);
3299  color_adjust_.r = cfg["color_adjust_red"].to_int(0);
3300  color_adjust_.g = cfg["color_adjust_green"].to_int(0);
3301  color_adjust_.b = cfg["color_adjust_blue"].to_int(0);
3302 }
3303 
3305 {
3306  if (!reach_map_changed_) return;
3307  if (reach_map_.empty() != reach_map_old_.empty()) {
3308  // Invalidate everything except the non-darkened tiles
3309  reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
3310 
3311  for (const auto& hex : get_visible_hexes()) {
3312  reach_map::iterator reach = full.find(hex);
3313  if (reach == full.end()) {
3314  // Location needs to be darkened or brightened
3315  invalidate(hex);
3316  } else if (reach->second != 1) {
3317  // Number needs to be displayed or cleared
3318  invalidate(hex);
3319  }
3320  }
3321  } else if (!reach_map_.empty()) {
3322  // Invalidate only changes
3323  reach_map::iterator reach, reach_old;
3324  for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
3325  reach_old = reach_map_old_.find(reach->first);
3326  if (reach_old == reach_map_old_.end()) {
3327  invalidate(reach->first);
3328  } else {
3329  if (reach_old->second != reach->second) {
3330  invalidate(reach->first);
3331  }
3332  reach_map_old_.erase(reach_old);
3333  }
3334  }
3335  for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
3336  invalidate(reach_old->first);
3337  }
3338  }
3340  reach_map_changed_ = false;
3341 }
3342 
3343 display *display::singleton_ = nullptr;
void new_animation_frame()
Definition: animated.cpp:31
Arrows destined to be drawn on the map.
double t
Definition: astarsearch.cpp:63
double g
Definition: astarsearch.cpp:63
std::vector< std::string > names
Definition: build_info.cpp:67
Definitions for the terrain builder.
void add_frame(int duration, const T &value, bool force_change=false)
Adds a frame to an animation.
Arrows destined to be drawn on the map.
Definition: arrow.hpp:30
const arrow_path_t & get_previous_path() const
Definition: arrow.cpp:127
const arrow_path_t & get_path() const
Definition: arrow.cpp:122
image::locator get_image_for_loc(const map_location &hex) const
Definition: arrow.cpp:138
color_t rep() const
High-contrast shade, intended for the minimap markers.
Definition: color_range.hpp:94
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:367
config & add_child_at(config_key_type key, const config &val, std::size_t index)
Definition: config.cpp:467
child_itors child_range(config_key_type key)
Definition: config.cpp:273
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:283
bool empty() const
Definition: config.cpp:852
config & add_child(config_key_type key)
Definition: config.cpp:441
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
orb_status unit_orb_status(const unit &u) const
Returns an enumurated summary of whether this unit can move and/or attack.
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
virtual const gamemap & map() const =0
virtual const std::vector< team > & teams() const =0
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:89
const team & viewing_team() const
Definition: display.cpp:342
void unhide_buttons()
Unhide theme buttons so they draw again.
Definition: display.cpp:949
int xpos_
Position of the top-left corner of the viewport, in pixels.
Definition: display.hpp:739
void set_viewing_team_index(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:347
bool map_screenshot_
Used to indicate to drawing functions that we are doing a map screenshot.
Definition: display.hpp:887
void draw_text_in_hex(const map_location &loc, const drawing_layer layer, const std::string &text, std::size_t font_size, color_t color, double x_in_hex=0.5, double y_in_hex=0.5)
Draw text on a hex.
Definition: display.cpp:1447
void layout_buttons()
Definition: display.cpp:802
bool redraw_background_
Definition: display.hpp:754
void update_render_textures()
Ensure render textures are valid and correct.
Definition: display.cpp:2521
static unsigned int last_zoom_
The previous value of zoom_.
Definition: display.hpp:749
std::size_t viewing_team_index_
Definition: display.hpp:731
void write(config &cfg) const
Definition: display.cpp:3287
static bool zoom_at_min()
Definition: display.cpp:1810
void get_terrain_images(const map_location &loc, const std::string &timeid, TERRAIN_TYPE terrain_type)
Definition: display.cpp:1045
void remove_overlay(const map_location &loc)
remove_overlay will remove all overlays on a tile.
Definition: display.cpp:128
map_location selectedHex_
Definition: display.hpp:786
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1581
void clear_fps_label()
Definition: display.cpp:1363
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1602
point get_location(const map_location &loc) const
Definition: display.cpp:694
unsigned int fps_counter_
Definition: display.hpp:766
bool invalidate_locations_in_rect(const SDL_Rect &rect)
invalidate all hexes under the rectangle rect (in screen coordinates)
Definition: display.cpp:3185
const map_location hex_clicked_on(int x, int y) const
given x,y co-ordinates of an onscreen pixel, will return the location of the hex that this pixel corr...
Definition: display.cpp:539
virtual void render() override
Update offscreen render buffers.
Definition: display.cpp:2444
void remove_single_overlay(const map_location &loc, const std::string &toDelete)
remove_single_overlay will remove a single overlay from a tile
Definition: display.cpp:133
static bool outside_area(const SDL_Rect &area, const int x, const int y)
Check if the bbox of the hex at x,y has pixels outside the area rectangle.
Definition: display.cpp:531
void fade_tod_mask(const std::string &old, const std::string &new_)
ToD mask smooth fade.
Definition: display.cpp:2230
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3132
void set_playing_team_index(std::size_t team)
sets the team whose turn it currently is
Definition: display.cpp:364
uint8_t tod_hex_alpha1
Definition: display.hpp:781
const team & playing_team() const
Definition: display.cpp:337
void announce(const std::string &msg, const color_t &color=font::GOOD_COLOR, const announce_options &options=announce_options())
Announce a message prominently.
Definition: display.cpp:1565
bool view_locked_
Definition: display.hpp:740
double turbo_speed() const
Definition: display.cpp:2155
@ ONSCREEN
Definition: display.hpp:503
@ ONSCREEN_WARP
Definition: display.hpp:503
int current_frame_sample_
Definition: display.hpp:765
int invalidated_hexes_
Count work done for the debug info displayed under fps.
Definition: display.hpp:915
void adjust_color_overlay(int r, int g, int b)
Add r,g,b to the colors for all images displayed on the map.
Definition: display.cpp:406
bool add_exclusive_draw(const map_location &loc, unit &unit)
Allows a unit to request to be the only one drawn in its hex.
Definition: display.cpp:371
void set_fade(const color_t &color)
Definition: display.cpp:2286
void queue_repaint()
Queues repainting to the screen, but doesn't rerender.
Definition: display.cpp:2343
bool reach_map_changed_
Definition: display.hpp:904
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:262
int get_location_x(const map_location &loc) const
Functions to get the on-screen positions of hexes.
Definition: display.cpp:684
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:265
const rect & unit_image_area() const
Definition: display.cpp:474
TERRAIN_TYPE
Definition: display.hpp:719
@ FOREGROUND
Definition: display.hpp:719
@ BACKGROUND
Definition: display.hpp:719
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1705
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:3153
void clear_redraw_observers()
Clear the redraw observers.
Definition: display.cpp:2355
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:311
const theme::action * action_pressed()
Definition: display.cpp:1533
static submerge_data get_submerge_data(const rect &dest, double submerge, const point &size, uint8_t alpha, bool hreverse, bool vreverse)
Definition: display.cpp:2183
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:782
void set_theme(const std::string &new_theme)
Definition: display.cpp:249
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:434
void change_display_context(const display_context *dc)
Definition: display.cpp:445
void add_redraw_observer(std::function< void(display &)> f)
Adds a redraw observer, a function object to be called when a full rerender is queued.
Definition: display.cpp:2350
void set_prevent_draw(bool pd=true)
Prevent the game display from drawing.
Definition: display.cpp:2169
unsigned int fps_actual_
Definition: display.hpp:768
virtual overlay_map & get_overlays()=0
theme theme_
Definition: display.hpp:741
tod_color color_adjust_
Definition: display.hpp:963
bool get_prevent_draw()
Definition: display.cpp:2178
virtual void layout() override
Finalize screen layout.
Definition: display.cpp:2413
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1483
void update_tod(const time_of_day *tod_override=nullptr)
Applies r,g,b coloring to the map.
Definition: display.cpp:393
static bool zoom_at_max()
Definition: display.cpp:1805
static display * singleton_
Definition: display.hpp:968
std::shared_ptr< gui::button > find_menu_button(const std::string &id)
Definition: display.cpp:792
void render_map_outside_area()
Draw/redraw the off-map background area.
Definition: display.cpp:2566
map_labels & labels()
Definition: display.cpp:2582
const map_location pixel_position_to_hex(int x, int y) const
given x,y co-ordinates of a pixel on the map, will return the location of the hex that this pixel cor...
Definition: display.cpp:553
std::size_t playing_team_index() const
The playing team is the team whose turn it is.
Definition: display.hpp:112
void remove_arrow(arrow &)
Definition: display.cpp:3263
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:923
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:935
@ __NUM_DEBUG_FLAGS
Dummy entry to size the bitmask.
Definition: display.hpp:938
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:929
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:932
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:926
void process_reachmap_changes()
Definition: display.cpp:3304
void draw_minimap_units()
Definition: display.cpp:1658
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1993
void invalidate_animations_location(const map_location &loc)
Per-location invalidation called by invalidate_animations() Extra game per-location invalidation (vil...
Definition: display.cpp:3200
map_location mouseoverHex_
Definition: display.hpp:787
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:679
const gamemap & get_map() const
Definition: display.hpp:107
bool set_zoom(bool increase)
Zooms the display in (true) or out (false).
Definition: display.cpp:1815
std::map< std::string, rect > reportLocations_
Definition: display.hpp:772
const rect_of_hexes get_visible_hexes() const
Returns the rectangular area of visible hexes.
Definition: display.hpp:359
bool invalidateAll_
Definition: display.hpp:755
int drawn_hexes_
Definition: display.hpp:916
void draw_overlays_at(const map_location &loc)
Definition: display.cpp:2806
texture get_flag(const map_location &loc)
Definition: display.cpp:319
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3125
std::map< std::string, config > reports_
Definition: display.hpp:774
std::bitset< __NUM_DEBUG_FLAGS > debug_flags_
Currently set debug flags.
Definition: display.hpp:958
SDL_Rect minimap_location_
Definition: display.hpp:753
map_location get_middle_location() const
Definition: display.cpp:3281
void bounds_check_position()
Definition: display.cpp:2137
std::function< rect(rect)> minimap_renderer_
Definition: display.hpp:752
surface screenshot(bool map_screenshot=false)
Capture a (map-)screenshot into a surface.
Definition: display.cpp:734
void init_flags()
Init the flag list and the team colors used by ~TC.
Definition: display.cpp:260
std::vector< std::shared_ptr< gui::button > > action_buttons_
Definition: display.hpp:775
void drawing_buffer_add(const drawing_layer layer, const map_location &loc, decltype(draw_helper::do_draw) draw_func)
Add an item to the drawing buffer.
Definition: display.cpp:1264
const rect_of_hexes hexes_under_rect(const SDL_Rect &r) const
Return the rectangular area of hexes overlapped by r (r is in screen coordinates)
Definition: display.cpp:627
void scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force=true)
Definition: display.cpp:1911
rect map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition: display.cpp:522
bool prevent_draw_
Definition: display.hpp:566
exclusive_unit_draw_requests_t exclusive_unit_draw_requests_
map of hexes where only one unit should be drawn, the one identified by the associated id string
Definition: display.hpp:694
bool tile_nearly_on_screen(const map_location &loc) const
Checks if location loc or one of the adjacent tiles is visible on screen.
Definition: display.cpp:1902
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:439
rect max_map_area() const
Returns the maximum area used for the map regardless to resolution and view size.
Definition: display.cpp:479
void update_fps_count()
Definition: display.cpp:1511
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1896
void update_fps_label()
Definition: display.cpp:1311
map_location minimap_location_on(int x, int y)
given x,y co-ordinates of the mouse, will return the location of the hex in the minimap that the mous...
Definition: display.cpp:702
void scroll_to_tiles(map_location loc1, map_location loc2, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, double add_spacing=0.0, bool force=true)
Scroll such that location loc1 is on-screen.
Definition: display.cpp:2005
bool is_blindfolded() const
Definition: display.cpp:459
std::vector< texture > terrain_image_vector_
Definition: display.hpp:805
std::vector< std::string > fog_images_
Definition: display.hpp:783
int fps_handle_
Handle for the label which displays frames per second.
Definition: display.hpp:913
texture tod_hex_mask2
Definition: display.hpp:780
bool invalidate_visible_locations_in_rect(const SDL_Rect &rect)
Definition: display.cpp:3180
std::map< map_location, std::list< arrow * > > arrows_map_
Maps the list of arrows for each location.
Definition: display.hpp:961
std::vector< std::function< void(display &)> > redraw_observers_
Definition: display.hpp:918
void read(const config &cfg)
Definition: display.cpp:3296
const theme::menu * menu_pressed()
Definition: display.cpp:1549
const std::unique_ptr< terrain_builder > builder_
Definition: display.hpp:751
std::vector< animated< image::locator > > flags_
Animated flags for each team.
Definition: display.hpp:801
static void fill_images_list(const std::string &prefix, std::vector< std::string > &images)
Definition: display.cpp:412
int ypos_
Definition: display.hpp:739
void draw_report(const std::string &report_name, bool test_run=false)
Draw the specified report.
Definition: display.cpp:2951
std::set< map_location > invalidated_
Definition: display.hpp:776
color_t fade_color_
Definition: display.hpp:577
virtual const time_of_day & get_time_of_day(const map_location &loc=map_location::null_location()) const =0
std::vector< texture > get_fog_shroud_images(const map_location &loc, image::TYPE image_type)
Definition: display.cpp:959
void fade_to(const color_t &color, int duration)
Screen fade.
Definition: display.cpp:2253
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2291
void create_buttons()
Definition: display.cpp:852
int blindfold_ctr_
Definition: display.hpp:684
std::string remove_exclusive_draw(const map_location &loc)
Cancels an exclusive draw request.
Definition: display.cpp:381
bool invalidateGameStatus_
Definition: display.hpp:757
reach_map reach_map_old_
Definition: display.hpp:903
virtual void draw_invalidated()
Only called when there's actual redrawing to do.
Definition: display.cpp:2597
void drawing_buffer_commit()
Draws the drawing_buffer_ and clears it.
Definition: display.cpp:1276
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:496
int zoom_index_
Definition: display.hpp:747
void draw_panel(const theme::panel &panel)
Definition: display.cpp:1373
std::chrono::seconds fps_start_
Definition: display.hpp:767
void draw_buttons()
Definition: display.cpp:917
static int hex_width()
Function which returns the width of a hex in pixels, up to where the next hex starts.
Definition: display.hpp:256
void reset_standing_animations()
Definition: display.cpp:3249
bool animate_water_
Local version of prefs::get().animate_water, used to detect when it's changed.
Definition: display.hpp:794
bool debug_flag_set(DEBUG_FLAG flag) const
Definition: display.hpp:941
void toggle_default_zoom()
Sets the zoom amount to the default.
Definition: display.cpp:1884
virtual rect get_clip_rect() const
Get the clipping rectangle for drawing.
Definition: display.cpp:2592
CKey keys_
Definition: display.hpp:788
reports * reports_object_
Definition: display.hpp:759
bool draw_reports(const rect &region)
Draw all reports in the given region.
Definition: display.cpp:3111
void invalidate_animations()
Function to invalidate animated terrains and units which may have changed.
Definition: display.cpp:3211
virtual rect screen_location() override
Return the current draw location of the display, on the screen.
Definition: display.cpp:2513
void hide_buttons()
Hide theme buttons so they don't draw.
Definition: display.cpp:939
virtual bool expose(const rect &region) override
Paint the indicated region to the screen.
Definition: display.cpp:2468
std::list< draw_helper > drawing_buffer_
Definition: display.hpp:860
uint8_t tod_hex_alpha2
Definition: display.hpp:782
std::vector< std::shared_ptr< gui::button > > menu_buttons_
Definition: display.hpp:775
virtual void update() override
Update animations and internal state.
Definition: display.cpp:2396
void draw()
Perform rendering of invalidated items.
Definition: display.cpp:2360
int get_location_y(const map_location &loc) const
Definition: display.cpp:689
const rect & minimap_area() const
mapx is the width of the portion of the display which shows the game area.
Definition: display.cpp:464
texture back_
Definition: display.hpp:602
events::generic_event scroll_event_
Event raised when the map is being scrolled.
Definition: display.hpp:762
bool show_everything() const
Definition: display.hpp:105
virtual void draw_hex(const map_location &loc)
Redraws a single gamemap location.
Definition: display.cpp:2637
texture tod_hex_mask1
Definition: display.hpp:779
virtual ~display()
Definition: display.cpp:239
const rect & palette_area() const
Definition: display.cpp:469
std::map< map_location, unsigned int > reach_map
Definition: display.hpp:901
std::size_t playing_team_index_
Definition: display.hpp:819
std::vector< std::tuple< int, int, int > > fps_history_
Definition: display.hpp:965
halo::manager halo_man_
Definition: display.hpp:689
void reinit_flags_for_team(const team &)
Rebuild the flag list (not team colors) for a single side.
Definition: display.cpp:271
void draw_minimap()
Actually draw the minimap.
Definition: display.cpp:1607
texture front_
Render textures, for intermediate rendering.
Definition: display.hpp:601
std::size_t viewing_team_index() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:122
void update_arrow(arrow &a)
Called by arrow objects when they change.
Definition: display.cpp:3270
void draw_label(const theme::label &label)
Definition: display.cpp:1398
const std::unique_ptr< fake_unit_manager > fake_unit_man_
Definition: display.hpp:750
const display_context * dc_
Definition: display.hpp:688
bool animate_map_
Local cache for prefs::get().animate_map, since it is constantly queried.
Definition: display.hpp:791
std::vector< std::string > shroud_images_
Definition: display.hpp:784
static unsigned int zoom_
The current zoom, in pixels (on screen) per 72 pixels (in the graphic assets), i.e....
Definition: display.hpp:746
bool shrouded(const map_location &loc) const
Returns true if location (x,y) is covered in shroud.
Definition: display.cpp:674
bool dont_show_all_
Definition: display.hpp:732
void blindfold(bool flag)
Definition: display.cpp:451
void refresh_report(const std::string &report_name, const config *new_cfg=nullptr)
Update the given report.
Definition: display.cpp:2881
std::weak_ptr< wb::manager > wb_
Definition: display.hpp:690
void add_arrow(arrow &)
Definition: display.cpp:3256
const std::unique_ptr< map_labels > map_labels_
Definition: display.hpp:758
boost::circular_buffer< unsigned > frametimes_
Definition: display.hpp:764
uint32_t last_frame_finished_
Definition: display.hpp:769
void set_diagnostic(const std::string &msg)
Definition: display.cpp:1493
virtual void select_hex(map_location hex)
Definition: display.cpp:1475
int diagnostic_label_
Definition: display.hpp:756
std::map< std::string, texture > reportSurfaces_
Definition: display.hpp:773
bool draw_all_panels(const rect &region)
Redraws all panels intersecting the given region.
Definition: display.cpp:1425
reach_map reach_map_
Definition: display.hpp:902
void add_overlay(const map_location &loc, const std::string &image, const std::string &halo="", const std::string &team_name="", const std::string &item_id="", bool visible_under_fog=true, float submerge=0.0f, float z_order=0)
Functions to add and remove overlays from locations.
Definition: display.cpp:115
display(const display_context *dc, std::weak_ptr< wb::manager > wb, reports &reports_object, const std::string &theme_id, const config &level)
Definition: display.cpp:145
virtual void notify_observers()
Manages a list of fake units for the display object.
void set_lifetime(int lifetime, int fadeout=100)
void set_position(double xpos, double ypos)
void set_alignment(ALIGN align)
void set_color(const color_t &color)
void set_clip_rect(const SDL_Rect &r)
void set_font_size(int font_size)
Text class.
Definition: text.hpp:79
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:529
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:150
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:564
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:539
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:507
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:600
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:620
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:517
pango_text & set_link_aware(bool b)
Definition: text.cpp:643
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:462
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:575
pango_text & set_maximum_width(int width)
Definition: text.cpp:548
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:118
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:97
@ DEFAULT_SPACE
Definition: button.hpp:32
@ TYPE_TURBO
Definition: button.hpp:29
@ TYPE_PRESS
Definition: button.hpp:29
@ TYPE_IMAGE
Definition: button.hpp:29
@ TYPE_CHECK
Definition: button.hpp:29
@ TYPE_RADIO
Definition: button.hpp:29
void update()
Process animations, remove deleted halos, and invalidate screen regions now requiring redraw.
Definition: halo.cpp:444
handle add(int x, int y, const std::string &image, const map_location &loc, halo::ORIENTATION orientation=NORMAL, bool infinite=true)
Add a haloing effect using 'image centered on (x,y).
Definition: halo.cpp:424
void render(const rect &r)
Render halos in region.
Definition: halo.cpp:449
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
void recalculate_shroud()
Definition: label.cpp:278
void set_team(const team *)
Definition: label.cpp:139
void recalculate_labels()
Definition: label.cpp:245
events::mouse_handler & get_mouse_handler_base() override
Get a reference to a mouse handler member a derived class uses.
hotkey::command_executor * get_hotkey_command_executor() override
Optionally get a command executor to handle context menu events.
static prefs & get()
int scroll_speed()
bool turbo()
static rng & default_instance()
Definition: random.cpp:73
config generate_report(const std::string &name, const context &ct, bool only_static=false)
Definition: reports.cpp:1802
An object to leave the synced context during draw or unsynced wml items when we don’t know whether we...
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
static color_t get_minimap_color(int side)
Definition: team.cpp:964
const std::string & team_name() const
Definition: team.hpp:282
bool shrouded(const map_location &loc) const
Definition: team.cpp:650
bool fogged(const map_location &loc) const
Definition: team.cpp:659
The class terrain_builder is constructed from a config object, and a gamemap object.
Definition: builder.hpp:48
std::vector< animated< image::locator > > imagelist
A shorthand typedef for a list of animated image locators, the base data type returned by the get_ter...
Definition: builder.hpp:74
TERRAIN_TYPE
Used as a parameter for the get_terrain_at function.
Definition: builder.hpp:51
@ BACKGROUND
Represents terrains which are to be drawn behind unit sprites.
Definition: builder.hpp:52
@ FOREGROUND
Represents terrains which are to be drawn in front of them.
Definition: builder.hpp:56
double unit_submerge() const
Definition: terrain.hpp:138
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition: texture.cpp:208
void set_src(const rect &r)
Set the source region of the texture used for drawing operations.
Definition: texture.cpp:129
point draw_size() const
The size of the texture in draw-space.
Definition: texture.hpp:122
void set_draw_size(int w, int h)
Set the intended size of the texture, in draw-space.
Definition: texture.hpp:131
point get_raw_size() const
The raw internal texture size.
Definition: texture.cpp:112
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
void set_alpha_mod(uint8_t alpha)
Alpha modifier.
Definition: texture.cpp:151
void clear_src()
Clear the source region.
Definition: texture.hpp:167
const std::string & get_id() const
Definition: theme.hpp:55
virtual rect & location(const SDL_Rect &screen) const
Definition: theme.cpp:317
const std::string & image() const
Definition: theme.hpp:158
Definition: theme.hpp:44
const border_t & border() const
Definition: theme.hpp:283
static NOT_DANGLING const config & get_theme_config(const std::string &id)
Returns the saved config for the theme with the given ID.
Definition: theme.cpp:1003
const rect & unit_image_location(const SDL_Rect &screen) const
Definition: theme.hpp:278
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:917
const std::vector< action > & actions() const
Definition: theme.hpp:259
const std::vector< menu > & menus() const
Definition: theme.hpp:257
const rect & mini_map_location(const SDL_Rect &screen) const
Definition: theme.hpp:276
const std::vector< panel > & panels() const
Definition: theme.hpp:255
const rect & main_map_location(const SDL_Rect &screen) const
Definition: theme.hpp:274
bool set_resolution(const SDL_Rect &screen)
Definition: theme.cpp:604
const rect & palette_location(const SDL_Rect &screen) const
Definition: theme.hpp:280
const status_item * get_status_item(const std::string &item) const
Definition: theme.cpp:908
const std::vector< label > & labels() const
Definition: theme.hpp:256
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:133
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition: color.hpp:280
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1347
#define MaxZoom
Definition: display.cpp:84
#define final_zoom_index
Definition: display.cpp:80
static int get_zoom_levels_index(unsigned int zoom_level)
Definition: display.cpp:95
static unsigned calculate_fps(unsigned frametime)
Definition: display.cpp:1306
#define LOG_DP
Definition: display.cpp:75
#define SmallZoom
Definition: display.cpp:82
#define WRN_DP
Definition: display.cpp:74
#define DefaultZoom
Definition: display.cpp:81
static lg::log_domain log_display("display")
#define MinZoom
Definition: display.cpp:83
#define ERR_DP
Definition: display.cpp:73
#define zoom_levels
Definition: display.cpp:79
#define DBG_DP
Definition: display.cpp:76
map_display and display: classes which take care of displaying the map and game-data on the screen.
static SDL_Renderer * renderer()
Definition: draw.cpp:31
Drawing functions, for drawing things on the screen.
drawing_layer
@ border
The border of the map.
@ terrain_bg
Terrain drawn behind the unit.
@ reachmap
Overlay on unreachable hexes.
@ grid_bottom
Bottom half part of grid image.
@ unit_default
Default layer for drawing units.
@ unit_first
Reserve layers to be selected for wml.
@ terrain_fg
Terrain drawn in front of the unit.
@ fog_shroud
Fog and shroud.
@ unit_move_default
Default layer for drawing moving units.
@ arrows
Arrows from the arrows framework.
@ grid_top
Top half part of grid image.
Declarations for File-IO.
std::size_t i
Definition: function.cpp:965
int w
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:207
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:205
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:474
static std::ostream & output()
Definition: log.cpp:72
Standard logging facilities (interface).
bool is_shrouded(const display *disp, const map_location &loc)
Our definition of map labels being obscured is if the tile is obscured, or the tile below is obscured...
Definition: label.cpp:32
constexpr bool is_odd(T num)
Definition: math.hpp:36
CURSOR_TYPE get()
Definition: cursor.cpp:216
void invalidate_region(const rect &region)
Mark a region of the screen as requiring redraw.
void invalidate_all()
Mark the entire screen as requiring redraw.
render_target_setter set_render_target(const texture &t)
Set the given texture as the active render target.
Definition: draw.cpp:664
clip_setter override_clip(const SDL_Rect &clip)
Override the clipping area.
Definition: draw.cpp:497
clip_setter reduce_clip(const SDL_Rect &clip)
Set the clipping area to the intersection of the current clipping area and the given rectangle.
Definition: draw.cpp:502
void tiled(const texture &tex, const SDL_Rect &dst, bool centered=false, bool mirrored=false)
Tile a texture to fill a region.
Definition: draw.cpp:369
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:50
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:310
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:522
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:150
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
void draw()
Trigger a draw cycle.
Definition: events.cpp:751
void pump_and_draw()
pump() then immediately draw()
Definition: events.hpp:150
void pump()
Process all events currently in the queue.
Definition: events.cpp:479
std::string get_user_data_dir()
Definition: filesystem.cpp:827
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:54
const int SIZE_FLOAT_LABEL
Definition: constants.cpp:32
@ FONT_SANS_SERIF
const color_t YELLOW_COLOR
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:1118
const int SIZE_PLUS
Definition: constants.cpp:29
const int SIZE_TINY
Definition: constants.cpp:23
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
const color_t BAD_COLOR
void remove_floating_label(int handle, int fadeout)
removes the floating label given by 'handle' from the screen
const int SIZE_BUTTON_SMALL
Definition: constants.cpp:26
rect pango_draw_text(bool actually_draw, const rect &area, int size, const color_t &color, const std::string &text, int x, int y, bool use_tooltips, pango_text::FONT_STYLE style)
Draws text on the screen.
void scroll_floating_labels(double xmove, double ymove)
moves all floating labels that have 'scroll_mode' set to ANCHOR_LABEL_MAP
void update_floating_labels()
void draw_floating_labels()
const color_t NORMAL_COLOR
std::string ellipsis
std::string grid_top
std::string grid_bottom
std::string flag_rgb
std::string fog_prefix
Definition: game_config.cpp:55
const bool & debug
Definition: game_config.cpp:92
const color_range & color_info(std::string_view name)
std::string shroud_prefix
Definition: game_config.cpp:55
unsigned int tile_size
Definition: game_config.cpp:52
bool in_dialog()
Definition: show_dialog.cpp:56
Definition: halo.cpp:39
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:411
Functions to load and save images from/to disk.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:818
std::function< rect(rect)> prep_minimap_for_rendering(const gamemap &map, const team *vw, const unit_map *units, const std::map< map_location, unsigned int > *reach_map, bool ignore_terrain_disabled)
Prepares the minimap texture and returns a function which will render it to the current rendering tar...
Definition: minimap.cpp:40
texture get_lighted_texture(const image::locator &i_locator, const light_string &ls)
Definition: picture.cpp:745
std::basic_string< signed char > light_string
Type used to store color information of central and adjacent hexes.
Definition: picture.hpp:123
TYPE
Used to specify the rendering format of images.
Definition: picture.hpp:162
@ HEXED
Standard hexagonal tile mask applied, removing portions that don't fit.
Definition: picture.hpp:166
@ TOD_COLORED
Same as HEXED, but with Time of Day color tint applied.
Definition: picture.hpp:168
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:920
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:577
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
Definition: picture.cpp:486
std::string get_orb_color(orb_status os)
Wrapper for the various prefs::get().unmoved_color(), moved_color(), etc methods, using the enum inst...
Definition: orb_status.cpp:40
Unit and team statistics.
::tod_manager * tod_manager
Definition: resources.cpp:29
fake_unit_manager * fake_units
Definition: resources.cpp:30
play_controller * controller
Definition: resources.cpp:21
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
int add_tooltip(const SDL_Rect &origin, const std::string &message, const std::string &action)
Definition: tooltips.cpp:299
void clear_tooltips()
Definition: tooltips.cpp:241
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
void trim(std::string_view &s)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:83
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp:141
surface read_pixels(SDL_Rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:580
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:429
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:483
int current_refresh_rate()
The refresh rate of the screen.
Definition: video.cpp:488
Definition: display.hpp:45
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
@ allied
Belongs to a friendly side.
@ enemy
Belongs to a non-friendly side; normally visualised by not displaying an orb.
std::string_view data
Definition: picture.cpp:178
Transitional API for porting SDL_ttf-based code to Pango.
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
std::string filename
Filename.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
constexpr color_t smooth_blend(const color_t &c, uint8_t p) const
Blend smoothly with another color_t.
Definition: color.hpp:240
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:620
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:629
int lifetime
Lifetime measured in milliseconds.
Definition: display.hpp:622
Helper for rendering the map by ordering draw operations.
Definition: display.hpp:844
std::function< void(const rect &)> do_draw
Handles the actual drawing at this location.
Definition: display.hpp:849
very simple iterator to walk into the rect_of_hexes
Definition: display.hpp:329
iterator & operator++()
increment y first, then when reaching bottom, increment x
Definition: display.cpp:605
const rect_of_hexes & rect_
Definition: display.hpp:347
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:322
iterator end() const
Definition: display.cpp:622
iterator begin() const
Definition: display.cpp:618
Encapsulates the map of the game.
Definition: location.hpp:38
bool valid() const
Definition: location.hpp:89
void write(config &cfg) const
Definition: location.cpp:211
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
bool empty() const
False if both w and h are > 0, true otherwise.
Definition: rect.cpp:47
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:52
void clip(const SDL_Rect &r)
Clip this rectangle by the given rectangle.
Definition: rect.cpp:99
rect intersect(const SDL_Rect &r) const
Calculates the intersection of this rectangle and another; that is, the maximal rectangle that is con...
Definition: rect.cpp:90
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:72
double size
Definition: theme.hpp:91
std::string tile_image
Definition: theme.hpp:94
std::string background_image
Definition: theme.hpp:93
bool show_border
Definition: theme.hpp:96
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
std::string id
Definition: time_of_day.hpp:90
tod_color color
The color modifications that should be made to the game board to reflect the time of day.
std::string image_mask
The image that is to be laid over all images while this time of day lasts.
Definition: time_of_day.hpp:96
Small struct to store and manipulate ToD color adjusts.
Definition: time_of_day.hpp:27
bool is_zero() const
Definition: time_of_day.hpp:36
mock_char c
mock_party p
static map_location::DIRECTION n
static map_location::DIRECTION s
#define d
#define e
#define h
#define f
#define b