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