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