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