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