The Battle for Wesnoth  1.19.10+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
826 {
827 
828  // named namespace called in game_display.cpp
829 
830 const std::string& get_direction(std::size_t n)
831 {
832  using namespace std::literals::string_literals;
833  static const std::array dirs{"-n"s, "-ne"s, "-se"s, "-s"s, "-sw"s, "-nw"s};
834  return dirs[n >= dirs.size() ? 0 : n];
835 }
836 } // namespace display_direction
837 
839 {
840  if(video::headless()) {
841  return;
842  }
843 
844  // Keep the old buttons around until we're done so we can check the previous state.
845  std::vector<std::shared_ptr<gui::button>> menu_work;
846  std::vector<std::shared_ptr<gui::button>> action_work;
847 
848  DBG_DP << "creating menu buttons...";
849  for(const auto& menu : theme_.menus()) {
850  if(!menu.is_button()) {
851  continue;
852  }
853 
854  auto b = std::make_shared<gui::button>(menu.title(), gui::button::TYPE_PRESS, menu.image(),
855  gui::button::DEFAULT_SPACE, true, menu.overlay(), font::SIZE_BUTTON_SMALL);
856 
857  DBG_DP << "drawing button " << menu.get_id();
858  b->set_id(menu.get_id());
859  if(!menu.tooltip().empty()) {
860  b->set_tooltip_string(menu.tooltip());
861  }
862 
863  if(auto b_prev = find_menu_button(b->id())) {
864  b->enable(b_prev->enabled());
865  }
866 
867  menu_work.push_back(std::move(b));
868  }
869 
870  DBG_DP << "creating action buttons...";
871  for(const auto& action : theme_.actions()) {
872  auto b = std::make_shared<gui::button>(action.title(), string_to_button_type(action.type()),
873  action.image(), gui::button::DEFAULT_SPACE, true, action.overlay(), font::SIZE_BUTTON_SMALL);
874 
875  DBG_DP << "drawing button " << action.get_id();
876  b->set_id(action.get_id());
877  if(!action.tooltip(0).empty()) {
878  b->set_tooltip_string(action.tooltip(0));
879  }
880 
881  if(auto b_prev = find_action_button(b->id())) {
882  b->enable(b_prev->enabled());
883  if(b_prev->get_type() == gui::button::TYPE_CHECK) {
884  b->set_check(b_prev->checked());
885  }
886  }
887 
888  action_work.push_back(std::move(b));
889  }
890 
891  menu_buttons_ = std::move(menu_work);
892  action_buttons_ = std::move(action_work);
893 
894  if (prevent_draw_) {
895  // buttons start hidden in this case
896  hide_buttons();
897  }
898 
899  layout_buttons();
900  DBG_DP << "buttons created";
901 }
902 
904 {
905  // This is currently unnecessary because every GUI1 widget is a TLD.
906  // They will draw themselves. Keeping code in case this changes.
907  return;
908 
909  //const rect clip = draw::get_clip();
910  //for(auto& btn : menu_buttons_) {
911  // if(clip.overlaps(btn->location())) {
912  // btn->set_dirty(true);
913  // btn->draw();
914  // }
915  //}
916 
917  //for(auto& btn : action_buttons_) {
918  // if(clip.overlaps(btn->location())) {
919  // btn->set_dirty(true);
920  // btn->draw();
921  // }
922  //}
923 }
924 
926 {
927  for (auto& button : menu_buttons_) {
928  button->hide();
929  }
930  for (auto& button : action_buttons_) {
931  button->hide();
932  }
933 }
934 
936 {
937  for (auto& button : menu_buttons_) {
938  button->hide(false);
939  }
940  for (auto& button : action_buttons_) {
941  button->hide(false);
942  }
943 }
944 
945 std::vector<texture> display::get_fog_shroud_images(const map_location& loc, image::TYPE image_type)
946 {
947  using namespace display_direction;
948  std::vector<std::string> names;
949  const auto adjacent = get_adjacent_tiles(loc);
950 
951  enum visibility { FOG = 0, SHROUD = 1, CLEAR = 2 };
952  std::array<visibility, 6> tiles;
953 
954  const std::array image_prefix{&game_config::fog_prefix, &game_config::shroud_prefix};
955 
956  for(int i = 0; i < 6; ++i) {
957  if(shrouded(adjacent[i])) {
958  tiles[i] = SHROUD;
959  } else if(!fogged(loc) && fogged(adjacent[i])) {
960  tiles[i] = FOG;
961  } else {
962  tiles[i] = CLEAR;
963  }
964  }
965 
966  for(int v = FOG; v != CLEAR; ++v) {
967  // Find somewhere that doesn't have overlap to use as a starting point
968  int start;
969  for(start = 0; start != 6; ++start) {
970  if(tiles[start] != v) {
971  break;
972  }
973  }
974 
975  if(start == 6) {
976  // Completely surrounded by fog or shroud. This might have
977  // a special graphic.
978  const std::string name = *image_prefix[v] + "-all.png";
979  if(image::exists(name)) {
980  names.push_back(name);
981  // Proceed to the next visibility (fog -> shroud -> clear).
982  continue;
983  }
984  // No special graphic found. We'll just combine some other images
985  // and hope it works out.
986  start = 0;
987  }
988 
989  // Find all the directions overlap occurs from
990  for(int i = (start + 1) % 6, cap1 = 0; i != start && cap1 != 6; ++cap1) {
991  if(tiles[i] == v) {
992  std::ostringstream stream;
993  std::string name;
994  stream << *image_prefix[v];
995 
996  for(int cap2 = 0; v == tiles[i] && cap2 != 6; i = (i + 1) % 6, ++cap2) {
997  stream << get_direction(i);
998 
999  if(!image::exists(stream.str() + ".png")) {
1000  // If we don't have any surface at all,
1001  // then move onto the next overlapped area
1002  if(name.empty()) {
1003  i = (i + 1) % 6;
1004  }
1005  break;
1006  } else {
1007  name = stream.str();
1008  }
1009  }
1010 
1011  if(!name.empty()) {
1012  names.push_back(name + ".png");
1013  }
1014  } else {
1015  i = (i + 1) % 6;
1016  }
1017  }
1018  }
1019 
1020  // now get the textures
1021  std::vector<texture> res;
1022 
1023  for(const std::string& name : names) {
1024  if(texture tex = image::get_texture(name, image_type)) {
1025  res.push_back(std::move(tex));
1026  }
1027  }
1028 
1029  return res;
1030 }
1031 
1033 {
1034  terrain_image_vector_.clear();
1035 
1036  static std::vector<image::light_adjust> lighting;
1037  lighting.clear();
1038 
1039  const time_of_day& tod = get_time_of_day(loc);
1040 
1041  // get all the light transitions
1042  const auto adjs = get_adjacent_tiles(loc);
1043  std::array<const time_of_day*, adjs.size()> atods;
1044 
1045  for(std::size_t d = 0; d < adjs.size(); ++d) {
1046  atods[d] = &get_time_of_day(adjs[d]);
1047  }
1048 
1049  for(int d = 0; d < 6; ++d) {
1050  /*
1051  concave
1052  _____
1053  / \
1054  / atod1 \_____
1055  \ !tod / \
1056  \_____/ atod2 \
1057  / \__\ !tod /
1058  / \_____/
1059  \ tod /
1060  \_____/
1061  */
1062 
1063  const time_of_day& atod1 = *atods[d];
1064  const time_of_day& atod2 = *atods[(d + 1) % 6];
1065 
1066  if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color) {
1067  continue;
1068  }
1069 
1070  if(lighting.empty()) {
1071  // color the full hex before adding transitions
1072  tod_color col = tod.color + color_adjust_;
1073  lighting.emplace_back(0, col.r, col.g, col.b);
1074  }
1075 
1076  // add the directional transitions
1077  tod_color acol = atod1.color + color_adjust_;
1078  lighting.emplace_back(d + 1, acol.r, acol.g, acol.b);
1079  }
1080 
1081  for(int d = 0; d < 6; ++d) {
1082  /*
1083  convex 1
1084  _____
1085  / \
1086  / atod1 \_____
1087  \ !tod / \
1088  \_____/ atod2 \
1089  / \__\ tod /
1090  / \_____/
1091  \ tod /
1092  \_____/
1093  */
1094 
1095  const time_of_day& atod1 = *atods[d];
1096  const time_of_day& atod2 = *atods[(d + 1) % 6];
1097 
1098  if(atod1.color == tod.color || atod1.color == atod2.color) {
1099  continue;
1100  }
1101 
1102  if(lighting.empty()) {
1103  // color the full hex before adding transitions
1104  tod_color col = tod.color + color_adjust_;
1105  lighting.emplace_back(0, col.r, col.g, col.b);
1106  }
1107 
1108  // add the directional transitions
1109  tod_color acol = atod1.color + color_adjust_;
1110  lighting.emplace_back(d + 7, acol.r, acol.g, acol.b);
1111  }
1112 
1113  for(int d = 0; d < 6; ++d) {
1114  /*
1115  convex 2
1116  _____
1117  / \
1118  / atod1 \_____
1119  \ tod / \
1120  \_____/ atod2 \
1121  / \__\ !tod /
1122  / \_____/
1123  \ tod /
1124  \_____/
1125  */
1126 
1127  const time_of_day& atod1 = *atods[d];
1128  const time_of_day& atod2 = *atods[(d + 1) % 6];
1129 
1130  if(atod2.color == tod.color || atod1.color == atod2.color) {
1131  continue;
1132  }
1133 
1134  if(lighting.empty()) {
1135  // color the full hex before adding transitions
1136  tod_color col = tod.color + color_adjust_;
1137  lighting.emplace_back(0, col.r, col.g, col.b);
1138  }
1139 
1140  // add the directional transitions
1141  tod_color acol = atod2.color + color_adjust_;
1142  lighting.emplace_back(d + 13, acol.r, acol.g, acol.b);
1143  }
1144 
1145  if(lighting.empty()){
1146  tod_color col = tod.color + color_adjust_;
1147  if(!col.is_zero()){
1148  // no real lightmap needed but still color the hex
1149  lighting.emplace_back(-1, col.r, col.g, col.b);
1150  }
1151  }
1152 
1153  const terrain_builder::TERRAIN_TYPE builder_terrain_type = terrain_type == FOREGROUND
1156 
1157  if(const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc, timeid, builder_terrain_type)) {
1158  // Cache the offmap name. Since it is themeable it can change, so don't make it static.
1159  const std::string off_map_name = "terrain/" + theme_.border().tile_image;
1160  for(const auto& terrain : *terrains) {
1161  const image::locator& image = animate_map_ ? terrain.get_current_frame() : terrain.get_first_frame();
1162 
1163  // We prevent ToD coloring and brightening of off-map tiles,
1164  // We need to test for the tile to be rendered and
1165  // not the location, since the transitions are rendered
1166  // over the offmap-terrain and these need a ToD coloring.
1167  texture tex;
1168  const bool off_map = (image.get_filename() == off_map_name
1169  || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos);
1170 
1171  if(off_map) {
1173  } else if(lighting.empty()) {
1175  } else {
1176  tex = image::get_lighted_texture(image, lighting);
1177  }
1178 
1179  if(tex) {
1180  terrain_image_vector_.push_back(std::move(tex));
1181  }
1182  }
1183  }
1184 }
1185 
1186 namespace
1187 {
1188 constexpr std::array layer_groups {
1192 };
1193 
1194 enum {
1195  // you may adjust the following when needed:
1196 
1197  // maximum border. 3 should be safe even if a larger border is in use somewhere
1198  MAX_BORDER = 3,
1199 
1200  // store x, y, and layer in one 32 bit integer
1201  // 4 most significant bits == layer group => 16
1202  BITS_FOR_LAYER_GROUP = 4,
1203 
1204  // 10 second most significant bits == y => 1024
1205  BITS_FOR_Y = 10,
1206 
1207  // 1 third most significant bit == x parity => 2
1208  BITS_FOR_X_PARITY = 1,
1209 
1210  // 8 fourth most significant bits == layer => 256
1211  BITS_FOR_LAYER = 8,
1212 
1213  // 9 least significant bits == x / 2 => 512 (really 1024 for x)
1214  BITS_FOR_X_OVER_2 = 9,
1215 
1216  SHIFT_LAYER = BITS_FOR_X_OVER_2,
1217 
1218  SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER,
1219 
1220  SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
1221 
1222  SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y
1223 };
1224 
1225 uint32_t generate_hex_key(const drawing_layer layer, const map_location& loc)
1226 {
1227  // Start with the index of last group entry...
1228  uint32_t group_i = layer_groups.size() - 1;
1229 
1230  // ...and works backwards until the group containing the specified layer is found.
1231  while(layer < layer_groups[group_i]) {
1232  --group_i;
1233  }
1234 
1235  // the parity of x must be more significant than the layer but less significant than y.
1236  // Thus basically every row is split in two: First the row containing all the odd x
1237  // then the row containing all the even x. Since thus the least significant bit of x is
1238  // not required for x ordering anymore it can be shifted out to the right.
1239  const uint32_t x_parity = static_cast<uint32_t>(loc.x) & 1;
1240 
1241  uint32_t key = 0;
1242  static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key) * 8, "Bit field too small");
1243 
1244  key = (group_i << SHIFT_LAYER_GROUP) | (static_cast<uint32_t>(loc.y + MAX_BORDER) << SHIFT_Y);
1245  key |= (x_parity << SHIFT_X_PARITY);
1246  key |= (static_cast<uint32_t>(layer) << SHIFT_LAYER) | static_cast<uint32_t>(loc.x + MAX_BORDER) / 2;
1247 
1248  return key;
1249 }
1250 } // namespace
1251 
1253 {
1254  drawing_buffer_.AGGREGATE_EMPLACE(generate_hex_key(layer, loc), std::move(draw_func), get_location_rect(loc));
1255 }
1256 
1258 {
1259  DBG_DP << "committing drawing buffer"
1260  << " with " << drawing_buffer_.size() << " items";
1261 
1262  // std::list::sort() is a stable sort
1263  drawing_buffer_.sort();
1264 
1265  const auto clipper = draw::reduce_clip(map_area());
1266 
1267  /*
1268  * Info regarding the rendering algorithm.
1269  *
1270  * In order to render a hex properly it needs to be rendered per row. On
1271  * this row several layers need to be drawn at the same time. Mainly the
1272  * unit and the background terrain. This is needed since both can spill
1273  * in the next hex. The foreground terrain needs to be drawn before to
1274  * avoid decapitation a unit.
1275  *
1276  * This ended in the following priority order:
1277  * layergroup > location > layer > 'draw_helper' > surface
1278  */
1279  for(const draw_helper& helper : drawing_buffer_) {
1280  std::invoke(helper.do_draw, helper.dest);
1281  }
1282 
1283  drawing_buffer_.clear();
1284 }
1285 
1287 {
1288  // Most panels are transparent.
1289  if (panel.image().empty()) {
1290  return;
1291  }
1292 
1293  const rect& loc = panel.location(video::game_canvas());
1294 
1295  if (!loc.overlaps(draw::get_clip())) {
1296  return;
1297  }
1298 
1299  DBG_DP << "drawing panel " << panel.get_id() << ' ' << loc;
1300 
1301  texture tex(image::get_texture(panel.image()));
1302  if (!tex) {
1303  ERR_DP << "failed to load panel " << panel.get_id()
1304  << " texture: " << panel.image();
1305  return;
1306  }
1307 
1308  draw::tiled(tex, loc);
1309 }
1310 
1312 {
1313  const rect& loc = label.location(video::game_canvas());
1314 
1315  if (!loc.overlaps(draw::get_clip())) {
1316  return;
1317  }
1318 
1319  const std::string& text = label.text();
1320  const color_t text_color = label.font_rgb_set() ? label.font_rgb() : font::NORMAL_COLOR;
1321  const std::string& icon = label.icon();
1322 
1323  DBG_DP << "drawing label " << label.get_id() << ' ' << loc;
1324 
1325  if(icon.empty() == false) {
1327 
1328  if(text.empty() == false) {
1329  tooltips::add_tooltip(loc,text);
1330  }
1331  } else if(text.empty() == false) {
1333  renderer.set_text(text, false);
1334  renderer.set_family_class(font::family_class::sans_serif);
1335  renderer.set_font_size(label.font_size());
1336  renderer.set_font_style(font::pango_text::STYLE_NORMAL);
1337  renderer.set_foreground_color(text_color);
1338  renderer.set_ellipse_mode(PANGO_ELLIPSIZE_END);
1339  renderer.set_maximum_width(loc.w);
1340  renderer.set_maximum_height(loc.h, true);
1341 
1342  auto t = renderer.render_and_get_texture();
1343  draw::blit(t, rect{ loc.origin(), t.draw_size() });
1344  }
1345 }
1346 
1347 bool display::draw_all_panels(const rect& region)
1348 {
1349  bool drew = false;
1351 
1352  for(const auto& panel : theme_.panels()) {
1353  if(region.overlaps(panel.location(game_canvas))) {
1354  draw_panel(panel);
1355  drew = true;
1356  }
1357  }
1358 
1359  for(const auto& label : theme_.labels()) {
1360  if(region.overlaps(label.location(game_canvas))) {
1361  draw_label(label);
1362  drew = true;
1363  }
1364  }
1365 
1366  return drew;
1367 }
1368 
1370  const drawing_layer layer,
1371  const std::string& text,
1372  std::size_t font_size,
1373  color_t color,
1374  double x_in_hex,
1375  double y_in_hex)
1376 {
1377  if (text.empty()) return;
1378 
1380  renderer.set_text(text, false);
1381  renderer.set_font_size(font_size * get_zoom_factor());
1382  renderer.set_maximum_width(-1);
1383  renderer.set_maximum_height(-1, false);
1384  renderer.set_foreground_color(color);
1385  renderer.set_add_outline(true);
1386 
1387  drawing_buffer_add(layer, loc, [x_in_hex, y_in_hex, tex = renderer.render_and_get_texture()](const rect& dest) {
1388  draw::blit(tex, rect{ dest.point_at(x_in_hex, y_in_hex) - tex.draw_size() / 2, tex.draw_size() });
1389  });
1390 }
1391 
1393 {
1395  selectedHex_ = hex;
1398 }
1399 
1401 {
1402  if(mouseoverHex_ == hex) {
1403  return;
1404  }
1406  mouseoverHex_ = hex;
1408 }
1409 
1410 void display::set_diagnostic(const std::string& msg)
1411 {
1412  if(diagnostic_label_ != 0) {
1414  diagnostic_label_ = 0;
1415  }
1416 
1417  if(!msg.empty()) {
1418  font::floating_label flabel(msg);
1420  flabel.set_color(font::YELLOW_COLOR);
1421  flabel.set_position(300, 50);
1422  flabel.set_clip_rect(map_outside_area());
1423 
1425  }
1426 }
1427 
1429 {
1430  for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
1431  if((*i)->pressed()) {
1432  const std::size_t index = std::distance(action_buttons_.begin(), i);
1433  if(index >= theme_.actions().size()) {
1434  assert(false);
1435  return nullptr;
1436  }
1437  return &theme_.actions()[index];
1438  }
1439  }
1440 
1441  return nullptr;
1442 }
1443 
1445 {
1446  for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) {
1447  if((*i)->pressed()) {
1448  const std::size_t index = std::distance(menu_buttons_.begin(), i);
1449  if(index >= theme_.menus().size()) {
1450  assert(false);
1451  return nullptr;
1452  }
1453  return theme_.get_menu_item((*i)->id());
1454  }
1455  }
1456 
1457  return nullptr;
1458 }
1459 
1460 void display::announce(const std::string& message, const color_t& color, const announce_options& options)
1461 {
1462  if(options.discard_previous) {
1463  font::remove_floating_label(prevLabel);
1464  }
1465  font::floating_label flabel(message);
1467  flabel.set_color(color);
1468  flabel.set_position(
1470  flabel.set_lifetime(options.lifetime);
1471  flabel.set_clip_rect(map_outside_area());
1472 
1473  prevLabel = font::add_floating_label(flabel);
1474 }
1475 
1477 {
1478  if(video::headless()) {
1479  return;
1480  }
1481 
1482  const rect& area = minimap_area();
1483  if(area.empty()){
1484  return;
1485  }
1486 
1488  context().map(),
1489  context().teams().empty() ? nullptr : &viewing_team(),
1490  nullptr,
1491  (selectedHex_.valid() && !is_blindfolded()) ? &reach_map_ : nullptr
1492  );
1493 
1494  redraw_minimap();
1495 }
1496 
1498 {
1500 }
1501 
1503 {
1504  const rect& area = minimap_area();
1505 
1506  if(area.empty() || !area.overlaps(draw::get_clip())) {
1507  return;
1508  }
1509 
1510  if(!minimap_renderer_) {
1511  return;
1512  }
1513 
1514  const auto clipper = draw::reduce_clip(area);
1515 
1516  // Draw the minimap background.
1517  draw::fill(area, 31, 31, 23);
1518 
1519  // Draw the minimap and update its location for mouse and units functions
1520  minimap_location_ = std::invoke(minimap_renderer_, area);
1521 
1523 
1524  // calculate the visible portion of the map:
1525  // scaling between minimap and full map images
1526  double xscaling = 1.0 * minimap_location_.w / (context().map().w() * hex_width());
1527  double yscaling = 1.0 * minimap_location_.h / (context().map().h() * hex_size());
1528 
1529  // we need to shift with the border size
1530  // and the 0.25 from the minimap balanced drawing
1531  // and the possible difference between real map and outside off-map
1532  rect map_rect = map_area();
1533  rect map_out_rect = map_outside_area();
1534  double border = theme_.border().size;
1535  double shift_x = -border * hex_width() - (map_out_rect.w - map_rect.w) / 2;
1536  double shift_y = -(border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2;
1537 
1538  int view_x = static_cast<int>((viewport_origin_.x + shift_x) * xscaling);
1539  int view_y = static_cast<int>((viewport_origin_.y + shift_y) * yscaling);
1540  int view_w = static_cast<int>(map_out_rect.w * xscaling);
1541  int view_h = static_cast<int>(map_out_rect.h * yscaling);
1542 
1543  rect outline_rect {
1544  minimap_location_.x + view_x - 1,
1545  minimap_location_.y + view_y - 1,
1546  view_w + 2,
1547  view_h + 2
1548  };
1549 
1550  draw::rect(outline_rect, 255, 255, 255);
1551 }
1552 
1554 {
1555  if (!prefs::get().minimap_draw_units() || is_blindfolded()) return;
1556 
1557  double xscaling = 1.0 * minimap_location_.w / context().map().w();
1558  double yscaling = 1.0 * minimap_location_.h / context().map().h();
1559 
1560  for(const auto& u : context().units()) {
1561  if (fogged(u.get_location()) ||
1562  (viewing_team().is_enemy(u.side()) &&
1563  u.invisible(u.get_location())) ||
1564  u.get_hidden()) {
1565  continue;
1566  }
1567 
1568  int side = u.side();
1569  color_t col = team::get_minimap_color(side);
1570 
1571  if(!prefs::get().minimap_movement_coding()) {
1572  auto status = orb_status::allied;
1573  if(viewing_team().is_enemy(side)) {
1574  status = orb_status::enemy;
1575  } else if(viewing_team().side() == side) {
1576  status = context().unit_orb_status(u);
1577  } else {
1578  // no-op, status is already set to orb_status::allied;
1579  }
1581  }
1582 
1583  double u_x = u.get_location().x * xscaling;
1584  double u_y = (u.get_location().y + (is_odd(u.get_location().x) ? 1 : -1)/4.0) * yscaling;
1585  // use 4/3 to compensate the horizontal hexes imbrication
1586  double u_w = 4.0 / 3.0 * xscaling;
1587  double u_h = yscaling;
1588 
1589  rect r {
1590  minimap_location_.x + int(std::round(u_x))
1591  , minimap_location_.y + int(std::round(u_y))
1592  , int(std::round(u_w))
1593  , int(std::round(u_h))
1594  };
1595 
1596  draw::fill(r, col.r, col.g, col.b, col.a);
1597  }
1598 }
1599 
1600 bool display::scroll(const point& amount, bool force)
1601 {
1602  if(view_locked_ && !force) {
1603  return false;
1604  }
1605 
1606  // No move offset, do nothing.
1607  if(amount == point{}) {
1608  return false;
1609  }
1610 
1611  point new_pos = viewport_origin_ + amount;
1612  bounds_check_position(new_pos.x, new_pos.y);
1613 
1614  // Camera position doesn't change, exit.
1615  if(viewport_origin_ == new_pos) {
1616  return false;
1617  }
1618 
1619  point diff = viewport_origin_ - new_pos;
1620  viewport_origin_ = new_pos;
1621 
1622  /* Adjust floating label positions. This only affects labels whose position is anchored
1623  * to the map instead of the screen. In order to do that, we want to adjust their drawing
1624  * coordinates in the opposite direction of the screen scroll.
1625  *
1626  * The check a few lines up prevents any scrolling from happening if the camera position
1627  * doesn't change. Without that, the label still scroll even when the map edge is reached.
1628  * If that's removed, the following formula should work instead:
1629  *
1630  * const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
1631  */
1632  font::scroll_floating_labels(diff.x, diff.y);
1633 
1635 
1636  //
1637  // NOTE: the next three blocks can be removed once we switch to accelerated rendering.
1638  //
1639 
1640  if(!video::headless()) {
1641  rect dst = map_area();
1642  dst.shift(diff);
1643  dst.clip(map_area());
1644 
1645  rect src = dst;
1646  src.shift(-diff);
1647 
1648  // swap buffers
1650 
1651  // Set the source region to blit from
1652  back_.set_src(src);
1653 
1654  // copy from the back to the front buffer
1655  auto rts = draw::set_render_target(front_);
1656  draw::blit(back_, dst);
1657 
1658  back_.clear_src();
1659 
1660  // queue repaint
1662  }
1663 
1664  if(diff.y != 0) {
1665  rect r = map_area();
1666 
1667  if(diff.y < 0) {
1668  r.y = r.y + r.h + diff.y;
1669  }
1670 
1671  r.h = std::abs(diff.y);
1673  }
1674 
1675  if(diff.x != 0) {
1676  rect r = map_area();
1677 
1678  if(diff.x < 0) {
1679  r.x = r.x + r.w + diff.x;
1680  }
1681 
1682  r.w = std::abs(diff.x);
1684  }
1685 
1687 
1688  redraw_minimap();
1689 
1690  return true;
1691 }
1692 
1694 {
1695  return zoom_ == MaxZoom;
1696 }
1697 
1699 {
1700  return zoom_ == MinZoom;
1701 }
1702 
1703 bool display::set_zoom(bool increase)
1704 {
1705  // Ensure we don't try to access nonexistent vector indices.
1706  zoom_index_ = std::clamp(increase ? zoom_index_ + 1 : zoom_index_ - 1, 0, final_zoom_index);
1707 
1708  // No validation check is needed in the next step since we've already set the index here and
1709  // know the new zoom value is indeed valid.
1710  return set_zoom(zoom_levels[zoom_index_], false);
1711 }
1712 
1713 bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_index)
1714 {
1715  unsigned int new_zoom = std::clamp(amount, MinZoom, MaxZoom);
1716 
1717  LOG_DP << "new_zoom = " << new_zoom;
1718 
1719  if(new_zoom == zoom_) {
1720  return false;
1721  }
1722 
1723  if(validate_value_and_set_index) {
1724  zoom_index_ = get_zoom_levels_index (new_zoom);
1725  new_zoom = zoom_levels[zoom_index_];
1726  }
1727 
1728  if((new_zoom / 4) * 4 != new_zoom) {
1729  WRN_DP << "set_zoom forcing zoom " << new_zoom
1730  << " which is not a multiple of 4."
1731  << " This will likely cause graphical glitches.";
1732  }
1733 
1735  const rect area = map_area();
1736 
1737  // Turn the zoom factor to a double in order to avoid rounding errors.
1738  double zoom_factor = static_cast<double>(new_zoom) / static_cast<double>(zoom_);
1739 
1740  // INVARIANT: xpos_ + area.w == xend where xend is as in bounds_check_position()
1741  //
1742  // xpos_: Position of the leftmost visible map pixel of the viewport, in pixels.
1743  // Affected by the current zoom: this->zoom_ pixels to the hex.
1744  //
1745  // xpos_ + area.w/2: Position of the center of the viewport, in pixels.
1746  //
1747  // (xpos_ + area.w/2) * new_zoom/zoom_: Position of the center of the
1748  // viewport, as it would be under new_zoom.
1749  //
1750  // (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
1751  // leftmost visible map pixel, as it would be under new_zoom.
1752  viewport_origin_.x = std::round(((viewport_origin_.x + area.w / 2) * zoom_factor) - (area.w / 2));
1753  viewport_origin_.y = std::round(((viewport_origin_.y + area.h / 2) * zoom_factor) - (area.h / 2));
1754  viewport_origin_ -= (outside_area.size() - area.size()) / 2;
1755 
1756  zoom_ = new_zoom;
1758  if(zoom_ != DefaultZoom) {
1759  last_zoom_ = zoom_;
1760  }
1761 
1762  prefs::get().set_tile_size(zoom_);
1763 
1765  redraw_background_ = true;
1766  invalidate_all();
1767 
1768  return true;
1769 }
1770 
1772 {
1773  if (zoom_ != DefaultZoom) {
1774  last_zoom_ = zoom_;
1776  } else {
1777  // When we are already at the default zoom,
1778  // switch to the last zoom used
1780  }
1781 }
1782 
1784 {
1786 }
1787 
1789 {
1790  const auto [x, y] = get_location(loc);
1791  const rect area = map_area();
1792  int hw = hex_width(), hs = hex_size();
1793  return x + hs >= area.x - hw && x < area.x + area.w + hw &&
1794  y + hs >= area.y - hs && y < area.y + area.h + hs;
1795 }
1796 
1797 void display::scroll_to_xy(const point& screen_coordinates, SCROLL_TYPE scroll_type, bool force)
1798 {
1799  if(!force && (view_locked_ || !prefs::get().scroll_to_action())) return;
1800  if(video::headless()) {
1801  return;
1802  }
1803 
1804  point expected_move = screen_coordinates - map_area().center();
1805 
1806  point new_pos = viewport_origin_ + expected_move;
1807  bounds_check_position(new_pos.x, new_pos.y);
1808 
1809  point move = new_pos - viewport_origin_;
1810 
1811  if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || prefs::get().scroll_speed() > 99) {
1812  scroll(move, true);
1813  redraw_minimap();
1814  events::draw();
1815  return;
1816  }
1817 
1818  // Doing an animated scroll, with acceleration etc.
1819 
1820  point prev_pos;
1821  const double dist_total = std::hypot(move.x, move.y);
1822  double dist_moved = 0.0;
1823 
1824  using fractional_seconds = std::chrono::duration<double>;
1825  auto prev_time = std::chrono::steady_clock::now();
1826 
1827  double velocity = 0.0;
1828  while (dist_moved < dist_total) {
1829  events::pump();
1830 
1831  auto time = std::chrono::steady_clock::now();
1832  auto dt = fractional_seconds{time - prev_time};
1833 
1834  // Do not skip too many frames on slow PCs
1835  dt = std::min<fractional_seconds>(dt, 200ms);
1836  prev_time = time;
1837 
1838  const double dt_as_double = dt.count();
1839  const double accel_time = 0.3 / turbo_speed(); // seconds until full speed is reached
1840  const double decel_time = 0.4 / turbo_speed(); // seconds from full speed to stop
1841 
1842  double velocity_max = prefs::get().scroll_speed() * 60.0;
1843  velocity_max *= turbo_speed();
1844  double accel = velocity_max / accel_time;
1845  double decel = velocity_max / decel_time;
1846 
1847  // If we started to decelerate now, where would we stop?
1848  double stop_time = velocity / decel;
1849  double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time;
1850  if (dist_stop > dist_total || velocity > velocity_max) {
1851  velocity -= decel * dt_as_double;
1852  if (velocity < 1.0) velocity = 1.0;
1853  } else {
1854  velocity += accel * dt_as_double;
1855  if (velocity > velocity_max) velocity = velocity_max;
1856  }
1857 
1858  dist_moved += velocity * dt_as_double;
1859  if (dist_moved > dist_total) dist_moved = dist_total;
1860 
1861  point next_pos(
1862  std::round(move.x * dist_moved / dist_total),
1863  std::round(move.y * dist_moved / dist_total)
1864  );
1865 
1866  point diff = next_pos - prev_pos;
1867  scroll(diff, true);
1868  prev_pos += diff;
1869 
1870  redraw_minimap();
1871  events::draw();
1872  }
1873 }
1874 
1875 void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, bool check_fogged, bool force)
1876 {
1877  if(context().map().on_board(loc) == false) {
1878  ERR_DP << "Tile at " << loc << " isn't on the map, can't scroll to the tile.";
1879  return;
1880  }
1881 
1882  scroll_to_tiles({loc}, scroll_type, check_fogged, false, 0.0, force);
1883 }
1884 
1886  SCROLL_TYPE scroll_type, bool check_fogged,
1887  double add_spacing, bool force)
1888 {
1889  scroll_to_tiles({loc1, loc2}, scroll_type, check_fogged, false, add_spacing, force);
1890 }
1891 
1892 void display::scroll_to_tiles(const std::vector<map_location>& locs,
1893  SCROLL_TYPE scroll_type, bool check_fogged,
1894  bool only_if_possible, double add_spacing, bool force)
1895 {
1896  // basically we calculate the min/max coordinates we want to have on-screen
1897  int minx = 0;
1898  int maxx = 0;
1899  int miny = 0;
1900  int maxy = 0;
1901  bool valid = false;
1902 
1903  for(const map_location& loc : locs) {
1904  if(context().map().on_board(loc) == false) continue;
1905  if(check_fogged && fogged(loc)) continue;
1906 
1907  const auto [x, y] = get_location(loc);
1908 
1909  if (!valid) {
1910  minx = x;
1911  maxx = x;
1912  miny = y;
1913  maxy = y;
1914  valid = true;
1915  } else {
1916  int minx_new = std::min<int>(minx,x);
1917  int miny_new = std::min<int>(miny,y);
1918  int maxx_new = std::max<int>(maxx,x);
1919  int maxy_new = std::max<int>(maxy,y);
1920  rect r = map_area();
1921  r.x = minx_new;
1922  r.y = miny_new;
1923  if(outside_area(r, maxx_new, maxy_new)) {
1924  // we cannot fit all locations to the screen
1925  if (only_if_possible) return;
1926  break;
1927  }
1928  minx = minx_new;
1929  miny = miny_new;
1930  maxx = maxx_new;
1931  maxy = maxy_new;
1932  }
1933  }
1934  //if everything is fogged or the location list is empty
1935  if(!valid) return;
1936 
1937  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
1938  int spacing = std::round(add_spacing * hex_size());
1939  rect r = map_area().padded_by(-spacing); // Shrink
1940  if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) {
1941  return;
1942  }
1943  }
1944 
1945  // let's do "normal" rectangle math from now on
1946  rect locs_bbox;
1947  locs_bbox.x = minx;
1948  locs_bbox.y = miny;
1949  locs_bbox.w = maxx - minx + hex_size();
1950  locs_bbox.h = maxy - miny + hex_size();
1951 
1952  // target the center
1953  point target = locs_bbox.center();
1954 
1955  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
1956  // when doing an ONSCREEN scroll we do not center the target unless needed
1957  rect r = map_area();
1958  auto [map_center_x, map_center_y] = r.center();
1959 
1960  int h = r.h;
1961  int w = r.w;
1962 
1963  // we do not want to be only inside the screen rect, but center a bit more
1964  double inside_frac = 0.5; // 0.0 = always center the target, 1.0 = scroll the minimum distance
1965  w = static_cast<int>(w * inside_frac);
1966  h = static_cast<int>(h * inside_frac);
1967 
1968  // shrink the rectangle by the size of the locations rectangle we found
1969  // such that the new task to fit a point into a rectangle instead of rectangle into rectangle
1970  w -= locs_bbox.w;
1971  h -= locs_bbox.h;
1972 
1973  if (w < 1) w = 1;
1974  if (h < 1) h = 1;
1975 
1976  r.x = target.x - w/2;
1977  r.y = target.y - h/2;
1978  r.w = w;
1979  r.h = h;
1980 
1981  // now any point within r is a possible target to scroll to
1982  // we take the one with the minimum distance to map_center
1983  // which will always be at the border of r
1984 
1985  if (map_center_x < r.x) {
1986  target.x = r.x;
1987  target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
1988  } else if (map_center_x > r.x+r.w-1) {
1989  target.x = r.x + r.w - 1;
1990  target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
1991  } else if (map_center_y < r.y) {
1992  target.y = r.y;
1993  target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
1994  } else if (map_center_y > r.y+r.h-1) {
1995  target.y = r.y + r.h - 1;
1996  target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
1997  } else {
1998  ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all...";
1999  // keep the target at the center
2000  }
2001  }
2002 
2003  scroll_to_xy(target, scroll_type, force);
2004 }
2005 
2006 
2008 {
2009  zoom_ = std::clamp(zoom_, MinZoom, MaxZoom);
2011 }
2012 
2013 void display::bounds_check_position(int& xpos, int& ypos) const
2014 {
2015  const int tile_width = hex_width();
2016 
2017  // Adjust for the border 2 times
2018  const int xend = static_cast<int>(tile_width * (context().map().w() + 2 * theme_.border().size) + tile_width / 3);
2019  const int yend = static_cast<int>(zoom_ * (context().map().h() + 2 * theme_.border().size) + zoom_ / 2);
2020 
2021  xpos = std::clamp(xpos, 0, xend - map_area().w);
2022  ypos = std::clamp(ypos, 0, yend - map_area().h);
2023 }
2024 
2025 double display::turbo_speed() const
2026 {
2027  bool res = prefs::get().turbo();
2028  if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT]) {
2029  res = !res;
2030  }
2031 
2032  res |= video::headless();
2033  if(res)
2034  return prefs::get().turbo_speed();
2035  else
2036  return 1.0;
2037 }
2038 
2040 {
2041  prevent_draw_ = pd;
2042  if (!pd) {
2043  // ensure buttons are visible
2044  unhide_buttons();
2045  }
2046 }
2047 
2049 {
2050  return prevent_draw_;
2051 }
2052 
2053 submerge_data display::get_submerge_data(const rect& dest, double submerge, const point& size, uint8_t alpha, bool hreverse, bool vreverse)
2054 {
2056  if(submerge <= 0.0) {
2057  return data;
2058  }
2059 
2060  // Set up blit destinations
2061  data.unsub_dest = dest;
2062  const int dest_sub_h = dest.h * submerge;
2063  data.unsub_dest.h -= dest_sub_h;
2064  const int dest_y_mid = dest.y + data.unsub_dest.h;
2065 
2066  // Set up blit src regions
2067  const int submersion_line = size.y * (1.0 - submerge);
2068  data.unsub_src = {0, 0, size.x, submersion_line};
2069 
2070  // Set up shader vertices
2071  const color_t c_mid(255, 255, 255, 0.3 * alpha);
2072  const int pixels_submerged = size.y * submerge;
2073  const int bot_alpha = std::max(0.3 - pixels_submerged * 0.015, 0.0) * alpha;
2074  const color_t c_bot(255, 255, 255, bot_alpha);
2075  const SDL_FPoint pML{float(dest.x), float(dest_y_mid)};
2076  const SDL_FPoint pMR{float(dest.x + dest.w), float(dest_y_mid)};
2077  const SDL_FPoint pBL{float(dest.x), float(dest.y + dest.h)};
2078  const SDL_FPoint pBR{float(dest.x + dest.w), float(dest.y + dest.h)};
2079  data.alpha_verts = {
2080  SDL_Vertex{pML, c_mid, {0.0, float(1.0 - submerge)}},
2081  SDL_Vertex{pMR, c_mid, {1.0, float(1.0 - submerge)}},
2082  SDL_Vertex{pBL, c_bot, {0.0, 1.0}},
2083  SDL_Vertex{pBR, c_bot, {1.0, 1.0}},
2084  };
2085 
2086  if(hreverse) {
2087  for(SDL_Vertex& v : data.alpha_verts) {
2088  v.tex_coord.x = 1.0 - v.tex_coord.x;
2089  }
2090  }
2091  if(vreverse) {
2092  for(SDL_Vertex& v : data.alpha_verts) {
2093  v.tex_coord.y = 1.0 - v.tex_coord.y;
2094  }
2095  }
2096 
2097  return data;
2098 }
2099 
2101  const std::string& old_mask,
2102  const std::string& new_mask)
2103 {
2104  // TODO: hwaccel - this needs testing as it's not used in mainline
2107 
2108  auto duration = 300ms / turbo_speed();
2109  auto start = std::chrono::steady_clock::now();
2110  for(auto now = start; now < start + duration; now = std::chrono::steady_clock::now()) {
2111  uint8_t p = float_to_color(chrono::normalize_progress(now - start, duration));
2112  tod_hex_alpha2 = p;
2113  tod_hex_alpha1 = ~p;
2116  }
2117 
2118  tod_hex_mask1.reset();
2119  tod_hex_mask2.reset();
2120 }
2121 
2122 void display::fade_to(const color_t& c, const std::chrono::milliseconds& duration)
2123 {
2124  auto start = std::chrono::steady_clock::now();
2125  color_t fade_start = fade_color_;
2126  color_t fade_end = c;
2127 
2128  // If we started transparent, assume the same colour
2129  if(fade_start.a == 0) {
2130  fade_start.r = fade_end.r;
2131  fade_start.g = fade_end.g;
2132  fade_start.b = fade_end.b;
2133  }
2134 
2135  // If we are ending transparent, assume the same colour
2136  if(fade_end.a == 0) {
2137  fade_end.r = fade_start.r;
2138  fade_end.g = fade_start.g;
2139  fade_end.b = fade_start.b;
2140  }
2141 
2142  // Smoothly blend and display
2143  for(auto now = start; now < start + duration; now = std::chrono::steady_clock::now()) {
2144  uint8_t p = float_to_color(chrono::normalize_progress(now - start, duration));
2145  fade_color_ = fade_start.smooth_blend(fade_end, p);
2148  }
2149  fade_color_ = fade_end;
2151  events::draw();
2152 }
2153 
2155 {
2156  fade_color_ = c;
2157 }
2158 
2160 {
2161  if(video::headless())
2162  return;
2163 
2164  DBG_DP << "redrawing everything";
2165 
2166  // This is specifically for game_display.
2167  // It would probably be better to simply make this function virtual,
2168  // if game_display needs to do special processing.
2169  invalidateGameStatus_ = true;
2170 
2171  reportLocations_.clear();
2172  reportSurfaces_.clear();
2173  reports_.clear();
2174 
2176 
2178 
2180 
2181  if(!menu_buttons_.empty() || !action_buttons_.empty()) {
2182  create_buttons();
2183  }
2184 
2185  if(resources::controller) {
2187  if(command_executor != nullptr) {
2188  // This function adds button overlays,
2189  // it needs to be run after recreating the buttons.
2190  command_executor->set_button_state();
2191  }
2192  }
2193 
2194  if(!gui2::is_in_dialog()) {
2196  }
2197 
2198  redraw_background_ = true;
2199 
2200  // This is only for one specific use, which is by the editor controller.
2201  // It would be vastly better if this didn't exist.
2202  for(std::function<void(display&)> f : redraw_observers_) {
2203  f(*this);
2204  }
2205 
2206  invalidate_all();
2207 
2209 }
2210 
2212 {
2213  // Could redraw a smaller region if the display doesn't use it all,
2214  // but when does that ever happen?
2216 }
2217 
2218 void display::add_redraw_observer(const std::function<void(display&)>& f)
2219 {
2220  redraw_observers_.push_back(f);
2221 }
2222 
2224 {
2225  redraw_observers_.clear();
2226 }
2227 
2229 {
2230  if(video::headless()) {
2231  DBG_DP << "display::draw denied";
2232  return;
2233  }
2234  //DBG_DP << "display::draw";
2235 
2236  // I have no idea why this is messing with sync context,
2237  // but i'm not going to touch it.
2239 
2240  // This isn't the best, but also isn't important enough to do better.
2242  DBG_DP << "display::draw redraw background";
2245  redraw_background_ = false;
2246  }
2247 
2248  if(!context().map().empty()) {
2249  if(!invalidated_.empty()) {
2250  draw_invalidated();
2251  invalidated_.clear();
2252  }
2254  }
2255 }
2256 
2258 {
2259  //DBG_DP << "display::update";
2260  // Ensure render textures are correctly sized and up-to-date.
2262 
2263  // Trigger cache rebuild if animated water preference has changed.
2264  if(animate_water_ != prefs::get().animate_water()) {
2265  animate_water_ = prefs::get().animate_water();
2266  builder_->rebuild_cache_all();
2267  }
2268 
2270  invalidate_all();
2271  }
2272 }
2273 
2275 {
2276  //DBG_DP << "display::layout";
2277 
2278  // There's nothing that actually does layout here, it all happens in
2279  // response to events. This isn't ideal, but neither is changing that.
2280 
2281  // Post-layout / Pre-render
2282 
2283  if (!context().map().empty()) {
2284  if(redraw_background_) {
2285  invalidateAll_ = true;
2286  }
2287  if(invalidateAll_) {
2288  DBG_DP << "draw() with invalidateAll";
2289 
2290  // toggle invalidateAll_ first to allow regular invalidations
2291  invalidateAll_ = false;
2293 
2294  redraw_minimap();
2295  }
2296  }
2297 
2298  // invalidate animated terrain, units and haloes
2300 
2301  // Update and invalidate floating labels as necessary
2303 }
2304 
2306 {
2307  // This should render the game map and units.
2308  // It is not responsible for halos and floating labels.
2309  //DBG_DP << "display::render";
2310 
2311  // No need to render if we aren't going to draw anything.
2312  if(prevent_draw_) {
2313  DBG_DP << "render prevented";
2314  return;
2315  }
2316 
2317  // Update our frametime values
2318  tracked_drawable::update_count();
2319 
2320  // render to the offscreen buffer
2321  auto target_setter = draw::set_render_target(front_);
2322  draw();
2323 
2324  // update the minimap texture, if necessary
2325  // TODO: highdpi - high DPI minimap
2326  const rect& area = minimap_area();
2327  if(!area.empty() && !minimap_renderer_) {
2329  }
2330 }
2331 
2332 bool display::expose(const rect& region)
2333 {
2334  if(prevent_draw_) {
2335  DBG_DP << "draw prevented";
2336  return false;
2337  }
2338 
2339  rect clipped_region = draw::get_clip().intersect(region);
2340 
2341  // Blit from the pre-rendered front buffer.
2342  if(clipped_region.overlaps(map_outside_area())) {
2343  front_.set_src(clipped_region);
2344  draw::blit(front_, clipped_region);
2345  front_.clear_src();
2346  }
2347 
2348  // Render halos.
2349  halo_man_.render(clipped_region);
2350 
2351  // Render UI elements.
2352  // Ideally buttons would be drawn as part of panels,
2353  // but they are currently TLDs so they draw themselves.
2354  // This also means they draw over tooltips...
2355  draw_all_panels(clipped_region);
2356  draw_reports(clipped_region);
2357  if(clipped_region.overlaps(minimap_area())) {
2358  draw_minimap();
2359  }
2360 
2361  // Floating labels should probably be separated by type,
2362  // but they aren't so they all get drawn here.
2364 
2365  // If there's a fade, apply it over everything
2366  if(fade_color_.a) {
2367  draw::fill(map_outside_area().intersect(region), fade_color_);
2368  }
2369 
2370  DBG_DP << "display::expose " << region;
2371 
2372  // The display covers the entire screen.
2373  // We will always be drawing something.
2374  return true;
2375 }
2376 
2378 {
2379  assert(!map_screenshot_);
2380  // There's no good way to determine this, as themes can put things
2381  // anywhere. Just return the entire game canvas.
2382  return video::game_canvas();
2383 }
2384 
2386 {
2387  if(video::headless()) {
2388  return;
2389  }
2390 
2391  // We ignore any logical offset on the underlying window buffer.
2392  // Render buffer size is always a simple multiple of the draw area.
2393  rect darea = video::game_canvas();
2394  rect oarea = darea * video::get_pixel_scale();
2395 
2396  // Check that the front buffer size is correct.
2397  // Buffers are always resized together, so we only need to check one.
2399  point dsize = front_.draw_size();
2400  bool raw_size_changed = size.x != oarea.w || size.y != oarea.h;
2401  bool draw_size_changed = dsize.x != darea.w || dsize.y != darea.h;
2402  if (!raw_size_changed && !draw_size_changed) {
2403  // buffers are fine
2404  return;
2405  }
2406 
2407  if(raw_size_changed) {
2408  LOG_DP << "regenerating render buffers as " << oarea;
2409  front_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2410  back_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2411  }
2412  if(raw_size_changed || draw_size_changed) {
2413  LOG_DP << "updating render buffer draw size to " << darea;
2414  front_.set_draw_size(darea.w, darea.h);
2415  back_.set_draw_size(darea.w, darea.h);
2416  }
2417 
2418  // Fill entire texture with black, just in case
2419  for(int i = 0; i < 2; ++i) {
2421  draw::fill(0,0,0);
2422  }
2423 
2424  // Fill in the background area on both textures.
2426 
2427  queue_rerender();
2428 }
2429 
2431 {
2432  // This could be optimized to avoid the map area,
2433  // but it's only called on game creation or zoom anyway.
2434  const rect clip_rect = map_outside_area();
2436  for(int i = 0; i < 2; ++i) {
2438  if(bgtex) {
2439  draw::tiled(bgtex, clip_rect);
2440  } else {
2441  draw::fill(clip_rect, 0, 0, 0);
2442  }
2443  }
2444 }
2445 
2447 {
2448  return *map_labels_;
2449 }
2450 
2452 {
2453  return *map_labels_;
2454 }
2455 
2457 {
2458  return map_area();
2459 }
2460 
2462 {
2463  // log_scope("display::draw_invalidated");
2464  rect clip_rect = get_clip_rect();
2465  const auto clipper = draw::reduce_clip(clip_rect);
2466 
2467  DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes with clip " << clip_rect;
2468 
2469  // The unit drawer can't function without teams
2470  utils::optional<unit_drawer> drawer{};
2471  if(!context().teams().empty()) {
2472  drawer.emplace(*this);
2473  }
2474 
2475  for(const map_location& loc : invalidated_) {
2476  rect hex_rect = get_location_rect(loc);
2477  if(!hex_rect.overlaps(clip_rect)) {
2478  continue;
2479  }
2480 
2481  draw_hex(loc);
2482  drawn_hexes_ += 1;
2483 
2484  if(drawer) {
2485  const auto u_it = context().units().find(loc);
2486  if(u_it != context().units().end() && unit_can_draw_here(loc, *u_it)) {
2487  drawer->redraw_unit(*u_it);
2488  }
2489  }
2490 
2491  draw_manager::invalidate_region(hex_rect.intersect(clip_rect));
2492  }
2493 
2494  invalidated_hexes_ += invalidated_.size();
2495 }
2496 
2498 {
2499  const bool on_map = context().map().on_board(loc);
2500  const time_of_day& tod = get_time_of_day(loc);
2501 
2502  int num_images_fg = 0;
2503  int num_images_bg = 0;
2504 
2505  const bool is_shrouded = shrouded(loc);
2506 
2507  // unshrouded terrain (the normal case)
2508  if(!is_shrouded) {
2509  get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
2510  num_images_bg = terrain_image_vector_.size();
2511 
2512  drawing_buffer_add(drawing_layer::terrain_bg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2513  for(const texture& t : images) {
2514  draw::blit(t, dest);
2515  }
2516  });
2517 
2518  get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
2519  num_images_fg = terrain_image_vector_.size();
2520 
2521  drawing_buffer_add(drawing_layer::terrain_fg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2522  for(const texture& t : images) {
2523  draw::blit(t, dest);
2524  }
2525  });
2526 
2527  // Draw the grid, if that's been enabled
2528  if(prefs::get().grid()) {
2531 
2533  [tex = image::get_texture(grid_top, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2534 
2536  [tex = image::get_texture(grid_bottom, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2537  }
2538 
2539  // overlays (TODO: can we just draw all the overlays in one pass instead of per-hex?)
2541 
2542  // village-control flags.
2543  if(context().map().is_village(loc)) {
2545  [tex = get_flag(loc)](const rect& dest) { draw::blit(tex, dest); });
2546  }
2547  }
2548 
2549  // Draw the time-of-day mask on top of the terrain in the hex.
2550  // tod may differ from tod if hex is illuminated.
2551  const std::string& tod_hex_mask = tod.image_mask;
2552  if(tod_hex_mask1 || tod_hex_mask2) {
2553  drawing_buffer_add(drawing_layer::terrain_fg, loc, [this](const rect& dest) mutable {
2555  draw::blit(tod_hex_mask1, dest);
2556 
2558  draw::blit(tod_hex_mask2, dest);
2559  });
2560  } else if(!tod_hex_mask.empty()) {
2562  [tex = image::get_texture(tod_hex_mask, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
2563  }
2564 
2565  // Paint arrows
2566  if(auto arrows_in_hex = arrows_map_.find(loc); arrows_in_hex != arrows_map_.end()) {
2567  std::vector<texture> to_draw;
2568  for(const arrow* a : arrows_in_hex->second) {
2569  to_draw.push_back(image::get_texture(a->get_image_for_loc(loc)));
2570  }
2571 
2572  drawing_buffer_add(drawing_layer::arrows, loc, [to_draw = std::move(to_draw)](const rect& dest) {
2573  for(const texture& t : to_draw) {
2574  draw::blit(t, dest);
2575  }
2576  });
2577  }
2578 
2579  // Apply shroud, fog and linger overlay
2580 
2581  if(is_shrouded || fogged(loc)) {
2582  // TODO: better noise function
2583  const auto get_variant = [&loc](const std::vector<std::string>& variants) -> const auto& {
2584  return variants[std::abs(loc.x + loc.y) % variants.size()];
2585  };
2586 
2587  const std::string& img = get_variant(is_shrouded ? shroud_images_ : fog_images_);
2589  [tex = image::get_texture(img, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2590  }
2591 
2592  if(!is_shrouded) {
2594  for(const texture& t : images) {
2595  draw::blit(t, dest);
2596  }
2597  });
2598  }
2599 
2601  using namespace std::string_literals;
2603  [tex = image::get_texture("terrain/foreground.png"s)](const rect& dest) { draw::blit(tex, dest); });
2604  }
2605 
2606  if(on_map) {
2607  // This might be slight overkill. Basically, we want to check that none of the
2608  // first three bits in the debug flag bitset are set so we can avoid creating
2609  // a stringstream, a temp string, and attempting to trim it for every hex even
2610  // when none of these flags are set. This gives us a temp object with all bits
2611  // past the first three zeroed out.
2612  if((std::as_const(debug_flags_) << (__NUM_DEBUG_FLAGS - DEBUG_FOREGROUND)).none()) {
2613  return;
2614  }
2615 
2616  std::ostringstream ss;
2618  ss << loc << '\n';
2619  }
2620 
2622  ss << context().map().get_terrain(loc) << '\n';
2623  }
2624 
2626  ss << (num_images_bg + num_images_fg) << '\n';
2627  }
2628 
2629  std::string output = ss.str();
2631 
2632  if(output.empty()) {
2633  return;
2634  }
2635 
2637  renderer.set_text(output, false);
2638  renderer.set_font_size(font::SIZE_TINY);
2639  renderer.set_alignment(PANGO_ALIGN_CENTER);
2640  renderer.set_foreground_color(font::NORMAL_COLOR);
2641  renderer.set_maximum_height(-1, false);
2642  renderer.set_maximum_width(-1);
2643 
2644  drawing_buffer_add(drawing_layer::fog_shroud, loc, [tex = renderer.render_and_get_texture()](const rect& dest) {
2645  // Center text in dest rect
2646  const rect text_dest { dest.center() - tex.draw_size() / 2, tex.draw_size() };
2647 
2648  // Add a little padding to the bg
2649  const rect bg_dest = text_dest.padded_by(3);
2650 
2651  draw::fill(bg_dest, 0, 0, 0, 170);
2652  draw::blit(tex, text_dest);
2653  });
2654  }
2655 }
2656 
2658 {
2659  auto it = get_overlays().find(loc);
2660  if(it == get_overlays().end()) {
2661  return;
2662  }
2663 
2664  std::vector<overlay>& overlays = it->second;
2665  if(overlays.empty()) {
2666  return;
2667  }
2668 
2669  const time_of_day& tod = get_time_of_day(loc);
2670  tod_color tod_col = tod.color + color_adjust_;
2671 
2672  auto lt = image::light_adjust{-1, tod_col.r, tod_col.g, tod_col.b};
2673 
2674  for(const overlay& ov : overlays) {
2675  if(fogged(loc) && !ov.visible_in_fog) {
2676  continue;
2677  }
2678 
2679  if(dont_show_all_ && !ov.team_name.empty()) {
2680  const auto current_team_names = utils::split_view(viewing_team().team_name());
2681  const auto team_names = utils::split_view(ov.team_name);
2682 
2683  bool item_visible_for_team = std::find_first_of(team_names.begin(), team_names.end(),
2684  current_team_names.begin(), current_team_names.end()) != team_names.end();
2685 
2686  if(!item_visible_for_team) {
2687  continue;
2688  }
2689  }
2690 
2691  texture tex = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos
2692  ? image::get_lighted_texture(ov.image, utils::span{&lt, 1})
2693  : image::get_texture(ov.image, image::HEXED);
2694 
2695  // Base submerge value for the terrain at this location
2696  const double ter_sub = context().map().get_terrain_info(loc).unit_submerge();
2697 
2699  drawing_layer::terrain_bg, loc, [tex, ter_sub, ovr_sub = ov.submerge](const rect& dest) mutable {
2700  if(ovr_sub > 0.0 && ter_sub > 0.0) {
2701  // Adjust submerge appropriately
2702  double submerge = ter_sub * ovr_sub;
2703 
2704  submerge_data data
2705  = display::get_submerge_data(dest, submerge, tex.draw_size(), ALPHA_OPAQUE, false, false);
2706 
2707  // set clip for dry part
2708  // smooth_shaded doesn't use the clip information so it's fine to set it up front
2709  // TODO: do we need to unset this?
2710  tex.set_src(data.unsub_src);
2711 
2712  // draw underwater part
2713  draw::smooth_shaded(tex, data.alpha_verts);
2714 
2715  // draw dry part
2716  draw::blit(tex, data.unsub_dest);
2717  } else {
2718  // draw whole texture
2719  draw::blit(tex, dest);
2720  }
2721  });
2722  }
2723 }
2724 
2725 /**
2726  * Redraws the specified report (if anything has changed).
2727  * If a config is not supplied, it will be generated via
2728  * reports::generate_report().
2729  */
2730 void display::refresh_report(const std::string& report_name, const config * new_cfg)
2731 {
2732  const theme::status_item *item = theme_.get_status_item(report_name);
2733  if (!item) {
2734  // This should be a warning, but unfortunately there are too many
2735  // unused reports to easily deal with.
2736  //WRN_DP << "no report '" << report_name << "' in theme";
2737  return;
2738  }
2739 
2740  // Now we will need the config. Generate one if needed.
2741 
2743 
2744  if (resources::controller) {
2746  }
2747 
2748  reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
2749 
2750  const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
2751  if ( new_cfg == nullptr )
2752  new_cfg = &generated_cfg;
2753 
2754  rect& loc = reportLocations_[report_name];
2755  const rect& new_loc = item->location(video::game_canvas());
2756  config &report = reports_[report_name];
2757 
2758  // Report and its location is unchanged since last time. Do nothing.
2759  if (loc == new_loc && report == *new_cfg) {
2760  return;
2761  }
2762 
2763  DBG_DP << "updating report: " << report_name;
2764 
2765  // Mark both old and new locations for redraw.
2768 
2769  // Update the config and current location.
2770  report = *new_cfg;
2771  loc = new_loc;
2772 
2773  // Not 100% sure this is okay
2774  // but it seems to be working so i'm not changing it.
2776 
2777  if (report.empty()) return;
2778 
2779  // Add prefix, postfix elements.
2780  // Make sure that they get the same tooltip
2781  // as the guys around them.
2782  std::string str = item->prefix();
2783  if (!str.empty()) {
2784  config &e = report.add_child_at("element", config(), 0);
2785  e["text"] = str;
2786  e["tooltip"] = report.mandatory_child("element")["tooltip"];
2787  }
2788  str = item->postfix();
2789  if (!str.empty()) {
2790  config &e = report.add_child("element");
2791  e["text"] = str;
2792  e["tooltip"] = report.mandatory_child("element", -1)["tooltip"];
2793  }
2794 
2795  // Do a fake run of drawing the report, so tooltips can be determined.
2796  // TODO: this is horrible, refactor reports to actually make sense
2797  draw_report(report_name, true);
2798 }
2799 
2800 void display::draw_report(const std::string& report_name, bool tooltip_test)
2801 {
2802  const theme::status_item *item = theme_.get_status_item(report_name);
2803  if (!item) {
2804  // This should be a warning, but unfortunately there are too many
2805  // unused reports to easily deal with.
2806  //WRN_DP << "no report '" << report_name << "' in theme";
2807  return;
2808  }
2809 
2810  const rect& loc = reportLocations_[report_name];
2811  const config& report = reports_[report_name];
2812 
2813  int x = loc.x, y = loc.y;
2814 
2815  // Loop through and display each report element.
2816  int tallest = 0;
2817  int image_count = 0;
2818  bool used_ellipsis = false;
2819  std::ostringstream ellipsis_tooltip;
2820  rect ellipsis_area = loc;
2821 
2822  for (config::const_child_itors elements = report.child_range("element");
2823  elements.begin() != elements.end(); elements.pop_front())
2824  {
2825  rect area {x, y, loc.w + loc.x - x, loc.h + loc.y - y};
2826  if (area.h <= 0) break;
2827 
2828  std::string t = elements.front()["text"];
2829  if (!t.empty())
2830  {
2831  if (used_ellipsis) goto skip_element;
2832 
2833  // Draw a text element.
2835  bool eol = false;
2836  if (t[t.size() - 1] == '\n') {
2837  eol = true;
2838  t = t.substr(0, t.size() - 1);
2839  }
2840  // If stripping left the text empty, skip it.
2841  if (t.empty()) {
2842  // Blank text has a null size when rendered.
2843  // It does not, however, have a null size when the size
2844  // is requested with get_size(). Hence this check.
2845  continue;
2846  }
2847  text.set_link_aware(false)
2848  .set_text(t, true);
2850  .set_font_size(item->font_size())
2852  .set_alignment(PANGO_ALIGN_LEFT)
2854  .set_maximum_width(area.w)
2855  .set_maximum_height(area.h, false)
2856  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
2858 
2859  point tsize = text.get_size();
2860 
2861  // check if next element is text with almost no space to show it
2862  const int minimal_text = 12; // width in pixels
2863  config::const_child_iterator ee = elements.begin();
2864  if (!eol && loc.w - (x - loc.x + tsize.x) < minimal_text &&
2865  ++ee != elements.end() && !(*ee)["text"].empty())
2866  {
2867  // make this element longer to trigger rendering of ellipsis
2868  // (to indicate that next elements have not enough space)
2869  //NOTE this space should be longer than minimal_text pixels
2870  t = t + " ";
2871  text.set_text(t, true);
2872  tsize = text.get_size();
2873  // use the area of this element for next tooltips
2874  used_ellipsis = true;
2875  ellipsis_area.x = x;
2876  ellipsis_area.y = y;
2877  ellipsis_area.w = tsize.x;
2878  ellipsis_area.h = tsize.y;
2879  }
2880 
2881  area.w = tsize.x;
2882  area.h = tsize.y;
2883  if (!tooltip_test) {
2884  draw::blit(text.render_and_get_texture(), area);
2885  }
2886  if (area.h > tallest) {
2887  tallest = area.h;
2888  }
2889  if (eol) {
2890  x = loc.x;
2891  y += tallest;
2892  tallest = 0;
2893  } else {
2894  x += area.w;
2895  }
2896  }
2897  else if (!(t = elements.front()["image"].str()).empty())
2898  {
2899  if (used_ellipsis) goto skip_element;
2900 
2901  // Draw an image element.
2903 
2904  if (!img) {
2905  ERR_DP << "could not find image for report: '" << t << "'";
2906  continue;
2907  }
2908 
2909  if (area.w < img.w() && image_count) {
2910  // We have more than one image, and this one doesn't fit.
2912  used_ellipsis = true;
2913  }
2914 
2915  if (img.w() < area.w) area.w = img.w();
2916  if (img.h() < area.h) area.h = img.h();
2917  if (!tooltip_test) {
2918  draw::blit(img, area);
2919  }
2920 
2921  ++image_count;
2922  if (area.h > tallest) {
2923  tallest = area.h;
2924  }
2925 
2926  if (!used_ellipsis) {
2927  x += area.w;
2928  } else {
2929  ellipsis_area = area;
2930  }
2931  }
2932  else
2933  {
2934  // No text nor image, skip this element
2935  continue;
2936  }
2937 
2938  skip_element:
2939  t = elements.front()["tooltip"].t_str().c_str();
2940  if (!t.empty()) {
2941  if (tooltip_test && !used_ellipsis) {
2942  tooltips::add_tooltip(area, t, elements.front()["help"].t_str().c_str());
2943  } else {
2944  // Collect all tooltips for the ellipsis.
2945  // TODO: need a better separator
2946  // TODO: assign an action
2947  ellipsis_tooltip << t;
2948  config::const_child_iterator ee = elements.begin();
2949  if (++ee != elements.end())
2950  ellipsis_tooltip << "\n _________\n\n";
2951  }
2952  }
2953  }
2954 
2955  if (tooltip_test && used_ellipsis) {
2956  tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str());
2957  }
2958 }
2959 
2960 bool display::draw_reports(const rect& region)
2961 {
2962  bool drew = false;
2963  for(const auto& it : reports_) {
2964  const std::string& name = it.first;
2965  const rect& loc = reportLocations_[name];
2966  if(loc.overlaps(region)) {
2967  draw_report(name);
2968  drew = true;
2969  }
2970  }
2971  return drew;
2972 }
2973 
2975 {
2976  DBG_DP << "invalidate_all()";
2977  invalidateAll_ = true;
2978  invalidated_.clear();
2979 }
2980 
2982 {
2984  return false;
2985 
2986  bool tmp;
2987  tmp = invalidated_.insert(loc).second;
2988  return tmp;
2989 }
2990 
2991 bool display::invalidate(const std::set<map_location>& locs)
2992 {
2994  return false;
2995  bool ret = false;
2996  for (const map_location& loc : locs) {
2997  ret = invalidated_.insert(loc).second || ret;
2998  }
2999  return ret;
3000 }
3001 
3002 bool display::propagate_invalidation(const std::set<map_location>& locs)
3003 {
3004  if(invalidateAll_)
3005  return false;
3006 
3007  if(locs.size()<=1)
3008  return false; // propagation never needed
3009 
3010  bool result = false;
3011  {
3012  // search the first hex invalidated (if any)
3013  std::set<map_location>::const_iterator i = locs.begin();
3014  for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {}
3015 
3016  if (i != locs.end()) {
3017 
3018  // propagate invalidation
3019  // 'i' is already in, but I suspect that splitting the range is bad
3020  // especially because locs are often adjacents
3021  size_t previous_size = invalidated_.size();
3022  invalidated_.insert(locs.begin(), locs.end());
3023  result = previous_size < invalidated_.size();
3024  }
3025  }
3026  return result;
3027 }
3028 
3030 {
3031  return invalidate_locations_in_rect(map_area().intersect(rect));
3032 }
3033 
3035 {
3037  return false;
3038 
3039  DBG_DP << "invalidating locations in " << rect;
3040 
3041  bool result = false;
3042  for(const map_location& loc : hexes_under_rect(rect)) {
3043  //DBG_DP << "invalidating " << loc.x << ',' << loc.y;
3044  result |= invalidate(loc);
3045  }
3046  return result;
3047 }
3048 
3050 {
3051  if(context().map().is_village(loc)) {
3052  const int owner = context().village_owner(loc) - 1;
3053  if(owner >= 0 && flags_[owner].need_update()
3054  && (!fogged(loc) || !viewing_team().is_enemy(owner + 1))) {
3055  invalidate(loc);
3056  }
3057  }
3058 }
3059 
3061 {
3062  // There are timing issues with this, but i'm not touching it.
3064  animate_map_ = prefs::get().animate_map();
3065  if(animate_map_) {
3066  for(const map_location& loc : get_visible_hexes()) {
3067  if(shrouded(loc))
3068  continue;
3069  if(builder_->update_animation(loc)) {
3070  invalidate(loc);
3071  } else {
3073  }
3074  }
3075  }
3076 
3077  for(const unit& u : context().units()) {
3078  u.anim_comp().refresh();
3079  }
3080  for(const unit* u : *fake_unit_man_) {
3081  u->anim_comp().refresh();
3082  }
3083 
3084  bool new_inval;
3085  do {
3086  new_inval = false;
3087  for(const unit& u : context().units()) {
3088  new_inval |= u.anim_comp().invalidate(*this);
3089  }
3090  for(const unit* u : *fake_unit_man_) {
3091  new_inval |= u->anim_comp().invalidate(*this);
3092  }
3093  } while(new_inval);
3094 
3095  halo_man_.update();
3096 }
3097 
3099 {
3100  for(const unit & u : context().units()) {
3101  u.anim_comp().set_standing();
3102  }
3103 }
3104 
3106 {
3107  for(const map_location& loc : arrow.get_path()) {
3108  arrows_map_[loc].push_back(&arrow);
3109  }
3110 }
3111 
3113 {
3114  for(const map_location& loc : arrow.get_path()) {
3115  arrows_map_[loc].remove(&arrow);
3116  }
3117 }
3118 
3120 {
3121  for(const map_location& loc : arrow.get_previous_path()) {
3122  arrows_map_[loc].remove(&arrow);
3123  }
3124 
3125  for(const map_location& loc : arrow.get_path()) {
3126  arrows_map_[loc].push_back(&arrow);
3127  }
3128 }
3129 
3131 {
3132  auto [center_x, center_y] = viewport_origin_ + map_area().center();
3133  return pixel_position_to_hex(center_x, center_y);
3134 }
3135 
3136 void display::write(config& cfg) const
3137 {
3138  cfg["view_locked"] = view_locked_;
3139  cfg["color_adjust_red"] = color_adjust_.r;
3140  cfg["color_adjust_green"] = color_adjust_.g;
3141  cfg["color_adjust_blue"] = color_adjust_.b;
3142  get_middle_location().write(cfg.add_child("location"));
3143 }
3144 
3145 void display::read(const config& cfg)
3146 {
3147  view_locked_ = cfg["view_locked"].to_bool(false);
3148  color_adjust_.r = cfg["color_adjust_red"].to_int(0);
3149  color_adjust_.g = cfg["color_adjust_green"].to_int(0);
3150  color_adjust_.b = cfg["color_adjust_blue"].to_int(0);
3151 }
3152 
3154 {
3155  if (!reach_map_changed_) return;
3156  if (reach_map_.empty() != reach_map_old_.empty()) {
3157  // Invalidate everything except the non-darkened tiles
3158  reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
3159 
3160  for (const auto& hex : get_visible_hexes()) {
3161  reach_map::iterator reach = full.find(hex);
3162  if (reach != full.end()) {
3163  // Location needs to be darkened or brightened
3164  invalidate(hex);
3165  }
3166  }
3167  } else if (!reach_map_.empty()) {
3168  // Invalidate new and old reach
3169  reach_map::iterator reach, reach_old;
3170  for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
3171  invalidate(reach->first);
3172  }
3173  for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
3174  invalidate(reach_old->first);
3175  }
3176  }
3178  reach_map_changed_ = false;
3179 
3180  // Make sure there are teams before trying to access units.
3181  if(!context().teams().empty()){
3182  // Update the reachmap-context team, the selected unit's team shall override the displayed unit's.
3183  if(context().units().count(selectedHex_)) {
3185  } else if(context().get_visible_unit(mouseoverHex_, viewing_team()) != nullptr){
3187  } else {
3188  /**
3189  * If no unit is selected or displayed, the reachmap-context team should failsafe to
3190  * the viewing team index, this makes sure the team is invalid when getting the reachmap
3191  * images in game_display::get_reachmap_images().
3192  */
3194  }
3195  DBG_DP << "Updated reachmap context team index to " << std::to_string(reach_map_team_index_);
3196  }
3197 }
3198 
3199 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:96
const team & viewing_team() const
Definition: display.cpp:335
void unhide_buttons()
Unhide theme buttons so they draw again.
Definition: display.cpp:935
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:871
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:1369
void layout_buttons()
Definition: display.cpp:783
bool redraw_background_
Definition: display.hpp:745
void update_render_textures()
Ensure render textures are valid and correct.
Definition: display.cpp:2385
static unsigned int last_zoom_
The previous value of zoom_.
Definition: display.hpp:740
std::size_t viewing_team_index_
Definition: display.hpp:722
void write(config &cfg) const
Definition: display.cpp:3136
static bool zoom_at_min()
Definition: display.cpp:1698
void get_terrain_images(const map_location &loc, const std::string &timeid, TERRAIN_TYPE terrain_type)
Definition: display.cpp:1032
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:770
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1476
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:1497
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:3034
virtual void render() override
Update offscreen render buffers.
Definition: display.cpp:2305
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:2100
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:2981
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:765
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:1460
bool view_locked_
Definition: display.hpp:731
double turbo_speed() const
Definition: display.cpp:2025
@ ONSCREEN
Definition: display.hpp:504
@ ONSCREEN_WARP
Definition: display.hpp:504
void scroll_to_xy(const point &screen_coordinates, SCROLL_TYPE scroll_type, bool force=true)
Definition: display.cpp:1797
int invalidated_hexes_
Count work done for the debug info displayed under fps.
Definition: display.hpp:899
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:2154
void queue_repaint()
Queues repainting to the screen, but doesn't rerender.
Definition: display.cpp:2211
bool reach_map_changed_
Definition: display.hpp:888
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:266
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:269
const rect & unit_image_area() const
Definition: display.cpp:466
TERRAIN_TYPE
Definition: display.hpp:710
@ FOREGROUND
Definition: display.hpp:710
@ BACKGROUND
Definition: display.hpp:710
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:3002
void clear_redraw_observers()
Clear the redraw observers.
Definition: display.cpp:2223
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:315
const theme::action * action_pressed()
Definition: display.cpp:1428
static submerge_data get_submerge_data(const rect &dest, double submerge, const point &size, uint8_t alpha, bool hreverse, bool vreverse)
Definition: display.cpp:2053
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:2039
virtual overlay_map & get_overlays()=0
theme theme_
Definition: display.hpp:732
tod_color color_adjust_
Definition: display.hpp:947
bool get_prevent_draw()
Definition: display.cpp:2048
virtual void layout() override
Finalize screen layout.
Definition: display.cpp:2274
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1400
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:2218
static bool zoom_at_max()
Definition: display.cpp:1693
static display * singleton_
Definition: display.hpp:952
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:2430
map_labels & labels()
Definition: display.cpp:2446
std::size_t playing_team_index() const
The playing team is the team whose turn it is.
Definition: display.hpp:115
void remove_arrow(arrow &)
Definition: display.cpp:3112
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:907
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:919
@ __NUM_DEBUG_FLAGS
Dummy entry to size the bitmask.
Definition: display.hpp:922
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:913
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:916
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:910
void process_reachmap_changes()
Definition: display.cpp:3153
void draw_minimap_units()
Definition: display.cpp:1553
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:1875
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:3049
map_location mouseoverHex_
Definition: display.hpp:771
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:1703
std::map< std::string, rect > reportLocations_
Definition: display.hpp:756
const rect_of_hexes get_visible_hexes() const
Returns the rectangular area of visible hexes.
Definition: display.hpp:364
bool invalidateAll_
Definition: display.hpp:746
int drawn_hexes_
Definition: display.hpp:900
void draw_overlays_at(const map_location &loc)
Definition: display.cpp:2657
texture get_flag(const map_location &loc)
Definition: display.cpp:312
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2974
std::map< std::string, config > reports_
Definition: display.hpp:758
std::bitset< __NUM_DEBUG_FLAGS > debug_flags_
Currently set debug flags.
Definition: display.hpp:942
SDL_Rect minimap_location_
Definition: display.hpp:744
point viewport_origin_
Position of the top-left corner of the viewport, in pixels.
Definition: display.hpp:730
map_location get_middle_location() const
Definition: display.cpp:3130
void bounds_check_position()
Definition: display.cpp:2007
std::function< rect(rect)> minimap_renderer_
Definition: display.hpp:743
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:759
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:1252
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:890
bool prevent_draw_
Definition: display.hpp:557
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:685
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:1788
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:1783
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:1885
bool is_blindfolded() const
Definition: display.cpp:451
std::vector< texture > terrain_image_vector_
Definition: display.hpp:789
std::vector< std::string > fog_images_
Definition: display.hpp:767
void fade_to(const color_t &color, const std::chrono::milliseconds &duration)
Screen fade.
Definition: display.cpp:2122
texture tod_hex_mask2
Definition: display.hpp:764
const display_context & context() const
Definition: display.hpp:192
bool invalidate_visible_locations_in_rect(const SDL_Rect &rect)
Definition: display.cpp:3029
std::map< map_location, std::list< arrow * > > arrows_map_
Maps the list of arrows for each location.
Definition: display.hpp:945
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:902
void read(const config &cfg)
Definition: display.cpp:3145
const theme::menu * menu_pressed()
Definition: display.cpp:1444
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:742
std::vector< animated< image::locator > > flags_
Animated flags for each team.
Definition: display.hpp:785
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:2800
std::set< map_location > invalidated_
Definition: display.hpp:760
color_t fade_color_
Definition: display.hpp:568
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:945
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2159
void create_buttons()
Definition: display.cpp:838
int blindfold_ctr_
Definition: display.hpp:675
std::string remove_exclusive_draw(const map_location &loc)
Cancels an exclusive draw request.
Definition: display.cpp:371
bool invalidateGameStatus_
Definition: display.hpp:748
reach_map reach_map_old_
Definition: display.hpp:887
virtual void draw_invalidated()
Only called when there's actual redrawing to do.
Definition: display.cpp:2461
void drawing_buffer_commit()
Draws the drawing_buffer_ and clears it.
Definition: display.cpp:1257
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:488
int zoom_index_
Definition: display.hpp:738
void draw_panel(const theme::panel &panel)
Definition: display.cpp:1286
void draw_buttons()
Definition: display.cpp:903
static int hex_width()
Function which returns the width of a hex in pixels, up to where the next hex starts.
Definition: display.hpp:260
void reset_standing_animations()
Definition: display.cpp:3098
bool animate_water_
Local version of prefs::get().animate_water, used to detect when it's changed.
Definition: display.hpp:778
bool debug_flag_set(DEBUG_FLAG flag) const
Definition: display.hpp:925
void toggle_default_zoom()
Sets the zoom amount to the default.
Definition: display.cpp:1771
virtual rect get_clip_rect() const
Get the clipping rectangle for drawing.
Definition: display.cpp:2456
CKey keys_
Definition: display.hpp:772
reports * reports_object_
Definition: display.hpp:750
bool draw_reports(const rect &region)
Draw all reports in the given region.
Definition: display.cpp:2960
void invalidate_animations()
Function to invalidate animated terrains and units which may have changed.
Definition: display.cpp:3060
virtual rect screen_location() override
Return the current draw location of the display, on the screen.
Definition: display.cpp:2377
void hide_buttons()
Hide theme buttons so they don't draw.
Definition: display.cpp:925
virtual bool expose(const rect &region) override
Paint the indicated region to the screen.
Definition: display.cpp:2332
std::list< draw_helper > drawing_buffer_
Definition: display.hpp:844
uint8_t tod_hex_alpha2
Definition: display.hpp:766
std::vector< std::shared_ptr< gui::button > > menu_buttons_
Definition: display.hpp:759
virtual void update() override
Update animations and internal state.
Definition: display.cpp:2257
void draw()
Perform rendering of invalidated items.
Definition: display.cpp:2228
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:593
events::generic_event scroll_event_
Event raised when the map is being scrolled.
Definition: display.hpp:753
bool show_everything() const
Definition: display.hpp:112
virtual void draw_hex(const map_location &loc)
Redraws a single gamemap location.
Definition: display.cpp:2497
texture tod_hex_mask1
Definition: display.hpp:763
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:885
std::size_t playing_team_index_
Definition: display.hpp:803
halo::manager halo_man_
Definition: display.hpp:680
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:1502
texture front_
Render textures, for intermediate rendering.
Definition: display.hpp:592
std::size_t viewing_team_index() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:125
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:3119
void draw_label(const theme::label &label)
Definition: display.cpp:1311
const std::unique_ptr< fake_unit_manager > fake_unit_man_
Definition: display.hpp:741
const display_context * dc_
Definition: display.hpp:679
bool animate_map_
Local cache for prefs::get().animate_map, since it is constantly queried.
Definition: display.hpp:775
std::vector< std::string > shroud_images_
Definition: display.hpp:768
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1600
static unsigned int zoom_
The current zoom, in pixels (on screen) per 72 pixels (in the graphic assets), i.e....
Definition: display.hpp:737
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:723
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:2730
std::weak_ptr< wb::manager > wb_
Definition: display.hpp:681
void add_arrow(arrow &)
Definition: display.cpp:3105
const std::unique_ptr< map_labels > map_labels_
Definition: display.hpp:749
void set_diagnostic(const std::string &msg)
Definition: display.cpp:1410
virtual void select_hex(map_location hex)
Definition: display.cpp:1392
int diagnostic_label_
Definition: display.hpp:747
std::map< std::string, texture > reportSurfaces_
Definition: display.hpp:757
bool draw_all_panels(const rect &region)
Redraws all panels intersecting the given region.
Definition: display.cpp:1347
reach_map reach_map_
Definition: display.hpp:886
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:385
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:153
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:420
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:395
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:363
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:456
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:476
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:373
pango_text & set_link_aware(bool b)
Definition: text.cpp:499
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:337
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:431
pango_text & set_maximum_width(int width)
Definition: text.cpp:404
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:121
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:270
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:353
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:97
@ DEFAULT_SPACE
Definition: button.hpp:32
@ TYPE_TURBO
Definition: button.hpp:29
@ TYPE_PRESS
Definition: button.hpp:29
@ TYPE_IMAGE
Definition: button.hpp:29
@ TYPE_CHECK
Definition: button.hpp:29
@ TYPE_RADIO
Definition: button.hpp:29
void update()
Process animations, remove deleted halos, and invalidate screen regions now requiring redraw.
Definition: halo.cpp: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:60
A RAII object to temporary leave the synced context like in wesnoth.synchronize_choice.
void recalculate_shroud()
Definition: label.cpp:278
void set_team(const team *)
Definition: label.cpp:139
void recalculate_labels()
Definition: label.cpp:245
events::mouse_handler & get_mouse_handler_base() override
Get a reference to a mouse handler member a derived class uses.
hotkey::command_executor * get_hotkey_command_executor() override
Optionally get a command executor to handle context menu events.
static prefs & get()
int scroll_speed()
bool turbo()
static rng & default_instance()
Definition: random.cpp:73
int get_random_int(int min, int max)
Definition: random.hpp:51
config generate_report(const std::string &name, const context &ct, bool only_static=false)
Definition: reports.cpp:1828
An object to leave the synced context during draw or unsynced wml items when we don’t know whether we...
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
static color_t get_minimap_color(int side)
Definition: team.cpp: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:1003
const rect & unit_image_location(const SDL_Rect &screen) const
Definition: theme.hpp:278
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:917
const std::vector< action > & actions() const
Definition: theme.hpp:259
const std::vector< menu > & menus() const
Definition: theme.hpp:257
const rect & mini_map_location(const SDL_Rect &screen) const
Definition: theme.hpp:276
const std::vector< panel > & panels() const
Definition: theme.hpp:255
const rect & main_map_location(const SDL_Rect &screen) const
Definition: theme.hpp:274
bool set_resolution(const SDL_Rect &screen)
Definition: theme.cpp:604
const rect & palette_location(const SDL_Rect &screen) const
Definition: theme.hpp:280
const status_item * get_status_item(const std::string &item) const
Definition: theme.cpp:908
const std::vector< label > & labels() const
Definition: theme.hpp:256
unit_iterator find(std::size_t id)
Definition: map.cpp:302
This class represents a single unit of a specific type.
Definition: unit.hpp:133
constexpr uint8_t float_to_color(double n)
Convert a double in the range [0.0,1.0] to an 8-bit colour value.
Definition: color.hpp: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:479
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
const std::string & get_direction(std::size_t n)
Definition: display.cpp:830
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:964
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.
texture get_lighted_texture(const image::locator &i_locator, utils::span< const light_adjust > ls)
Definition: picture.cpp:759
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:832
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:164
@ HEXED
Standard hexagonal tile mask applied, removing portions that don't fit.
Definition: picture.hpp:168
@ TOD_COLORED
Same as HEXED, but with Time of Day color tint applied.
Definition: picture.hpp:170
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:934
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:589
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:31
std::string get_orb_color(orb_status os)
Wrapper for the various prefs::get().unmoved_color(), moved_color(), etc methods, using the enum inst...
Definition: orb_status.cpp:40
Unit and team statistics.
::tod_manager * tod_manager
Definition: resources.cpp:29
fake_unit_manager * fake_units
Definition: resources.cpp:30
play_controller * controller
Definition: resources.cpp:21
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:32
int add_tooltip(const SDL_Rect &origin, const std::string &message, const std::string &action)
Definition: tooltips.cpp:299
void clear_tooltips()
Definition: tooltips.cpp:241
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::vector< std::string_view > split_view(std::string_view s, const char sep, const int flags)
void trim(std::string_view &s)
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:106
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::vector< std::string > split(const config_attribute_value &val)
bool headless()
The game is running headless.
Definition: video.cpp: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:178
static const unit * get_visible_unit(const reports::context &rc)
Definition: reports.cpp:133
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:611
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:620
std::chrono::milliseconds lifetime
Lifetime measured in milliseconds.
Definition: display.hpp:613
Helper for rendering the map by ordering draw operations.
Definition: display.hpp:828
std::function< void(const rect &)> do_draw
Handles the actual drawing at this location.
Definition: display.hpp:833
very simple iterator to walk into the rect_of_hexes
Definition: display.hpp:334
iterator & operator++()
increment y first, then when reaching bottom, increment x
Definition: display.cpp:597
const rect_of_hexes & rect_
Definition: display.hpp:352
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:327
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:125
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
void write(config &cfg) const
Definition: location.cpp:225
std::string image
Definition: overlay.hpp:55
std::string id
Definition: overlay.hpp:59
std::string halo
Definition: overlay.hpp:56
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
constexpr point center() const
The center point of the rectangle, accounting for origin.
Definition: rect.hpp:106
bool empty() const
False if both w and h are > 0, true otherwise.
Definition: rect.cpp:49
constexpr point origin() const
Definition: rect.hpp:66
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:54
constexpr rect padded_by(int dx, int dy) const
Returns a new rectangle with dx horizontal padding and dy vertical padding.
Definition: rect.hpp:158
void clip(const SDL_Rect &r)
Clip this rectangle by the given rectangle.
Definition: rect.cpp:101
constexpr point size() const
Definition: rect.hpp:67
void shift(const point &p)
Shift the rectangle by the given relative position.
Definition: rect.cpp:106
rect intersect(const SDL_Rect &r) const
Calculates the intersection of this rectangle and another; that is, the maximal rectangle that is con...
Definition: rect.cpp:92
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:74
double size
Definition: theme.hpp:91
std::string tile_image
Definition: theme.hpp:94
std::string background_image
Definition: theme.hpp:93
bool show_border
Definition: theme.hpp:96
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
std::string id
Definition: time_of_day.hpp:90
tod_color color
The color modifications that should be made to the game board to reflect the time of day.
std::string image_mask
The image that is to be laid over all images while this time of day lasts.
Definition: time_of_day.hpp:96
Small struct to store and manipulate ToD color adjusts.
Definition: time_of_day.hpp:27
bool is_zero() const
Definition: time_of_day.hpp:36
mock_char c
mock_party p
static map_location::direction n
static map_location::direction s
#define d
#define e
#define h
#define f
#define b