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