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