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