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 
1504 void display::add_submerge_ipf_mod(std::string& image_path, int image_height, double submersion_amount, int shift)
1505 {
1506  // We may also want to shift the position so that the waterline matches.
1507  // Note: This currently has blending problems (see the note on sdl_blit),
1508  // but if that blending problem is fixed it should work.
1509  if (shift) {
1510  image_path = "misc/blank-hex.png~BLIT(" + image_path;
1511  }
1512 
1513  // general formula for submerge alpha:
1514  // if (y > WATERLINE)
1515  // then min(max(alpha_mod, 0), 1) * alpha
1516  // else alpha
1517  // where alpha_mod = alpha_base - (y - WATERLINE) * alpha_delta
1518  // formula variables: x, y, red, green, blue, alpha, width, height
1519  // full WFL string:
1520  // "~ADJUST_ALPHA(if(y>DL,clamp((AB-(y-DL)*AD),0,1)*alpha,alpha))"
1521  // where DL = submersion line in pixels from top of image
1522  // AB = base alpha proportion at submersion line (30%)
1523  // AD = proportional alpha delta per pixel (1.5%)
1524  if (submersion_amount > 0.0) {
1525  int submersion_line = image_height * (1.0 - submersion_amount);
1526  const std::string sl_string = std::to_string(submersion_line);
1527  image_path += "~ADJUST_ALPHA(if(y>";
1528  image_path += sl_string;
1529  image_path += ",clamp((0.3-(y-";
1530  image_path += sl_string;
1531  image_path += ")*0.015),0,1)*alpha,alpha))";
1532  }
1533  // FUTURE: this submerge function could be done using SDL_RenderGeometry,
1534  // but that's not available below SDL 2.0.18.
1535  // Alternately it could also be done fairly easily using shaders,
1536  // if a graphics system supporting them (such as openGL) is moved to.
1537 
1538  if (shift) {
1539  // Finish the shifting blit. This assumes a hex-sized image.
1540  image_path += ",0,";
1541  image_path += std::to_string(shift);
1542  image_path += ')';
1543  }
1544 }
1545 
1547 {
1549  selectedHex_ = hex;
1552 }
1553 
1555 {
1556  if(mouseoverHex_ == hex) {
1557  return;
1558  }
1560  mouseoverHex_ = hex;
1562 }
1563 
1564 void display::set_diagnostic(const std::string& msg)
1565 {
1566  if(diagnostic_label_ != 0) {
1568  diagnostic_label_ = 0;
1569  }
1570 
1571  if(!msg.empty()) {
1572  font::floating_label flabel(msg);
1574  flabel.set_color(font::YELLOW_COLOR);
1575  flabel.set_position(300, 50);
1576  flabel.set_clip_rect(map_outside_area());
1577 
1579  }
1580 }
1581 
1583 {
1584  static int time_between_draws = preferences::draw_delay();
1585  if(time_between_draws < 0) {
1586  time_between_draws = 1000 / video::current_refresh_rate();
1587  }
1588 
1589  frametimes_.push_back(SDL_GetTicks() - last_frame_finished_);
1590  fps_counter_++;
1591  using std::chrono::duration_cast;
1592  using std::chrono::seconds;
1593  using std::chrono::steady_clock;
1594  const seconds current_second = duration_cast<seconds>(steady_clock::now().time_since_epoch());
1595  if(current_second != fps_start_) {
1596  fps_start_ = current_second;
1598  fps_counter_ = 0;
1599  }
1600 
1601  last_frame_finished_ = SDL_GetTicks();
1602 }
1603 
1605 {
1606  for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
1607  if((*i)->pressed()) {
1608  const std::size_t index = std::distance(action_buttons_.begin(), i);
1609  if(index >= theme_.actions().size()) {
1610  assert(false);
1611  return nullptr;
1612  }
1613  return &theme_.actions()[index];
1614  }
1615  }
1616 
1617  return nullptr;
1618 }
1619 
1621 {
1622  for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) {
1623  if((*i)->pressed()) {
1624  const std::size_t index = std::distance(menu_buttons_.begin(), i);
1625  if(index >= theme_.menus().size()) {
1626  assert(false);
1627  return nullptr;
1628  }
1629  return theme_.get_menu_item((*i)->id());
1630  }
1631  }
1632 
1633  return nullptr;
1634 }
1635 
1636 void display::announce(const std::string& message, const color_t& color, const announce_options& options)
1637 {
1638  if(options.discard_previous) {
1639  font::remove_floating_label(prevLabel);
1640  }
1641  font::floating_label flabel(message);
1643  flabel.set_color(color);
1644  flabel.set_position(
1646  flabel.set_lifetime(options.lifetime);
1647  flabel.set_clip_rect(map_outside_area());
1648 
1649  prevLabel = font::add_floating_label(flabel);
1650 }
1651 
1653 {
1654  if(video::headless()) {
1655  return;
1656  }
1657 
1658  const rect& area = minimap_area();
1659  if(area.empty()){
1660  return;
1661  }
1662 
1664  get_map(),
1665  dc_->teams().empty() ? nullptr : &dc_->teams()[currentTeam_],
1666  nullptr,
1667  (selectedHex_.valid() && !is_blindfolded()) ? &reach_map_ : nullptr
1668  );
1669 
1670  redraw_minimap();
1671 }
1672 
1674 {
1676 }
1677 
1679 {
1680  const rect& area = minimap_area();
1681 
1682  if(area.empty() || !area.overlaps(draw::get_clip())) {
1683  return;
1684  }
1685 
1686  if(!minimap_renderer_) {
1687  ERR_DP << "trying to draw null minimap";
1688  return;
1689  }
1690 
1691  const auto clipper = draw::reduce_clip(area);
1692 
1693  // Draw the minimap background.
1694  draw::fill(area, 31, 31, 23);
1695 
1696  // Draw the minimap and update its location for mouse and units functions
1697  minimap_location_ = std::invoke(minimap_renderer_, area);
1698 
1700 
1701  // calculate the visible portion of the map:
1702  // scaling between minimap and full map images
1703  double xscaling = 1.0 * minimap_location_.w / (get_map().w() * hex_width());
1704  double yscaling = 1.0 * minimap_location_.h / (get_map().h() * hex_size());
1705 
1706  // we need to shift with the border size
1707  // and the 0.25 from the minimap balanced drawing
1708  // and the possible difference between real map and outside off-map
1709  SDL_Rect map_rect = map_area();
1710  SDL_Rect map_out_rect = map_outside_area();
1711  double border = theme_.border().size;
1712  double shift_x = -border * hex_width() - (map_out_rect.w - map_rect.w) / 2;
1713  double shift_y = -(border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2;
1714 
1715  int view_x = static_cast<int>((xpos_ + shift_x) * xscaling);
1716  int view_y = static_cast<int>((ypos_ + shift_y) * yscaling);
1717  int view_w = static_cast<int>(map_out_rect.w * xscaling);
1718  int view_h = static_cast<int>(map_out_rect.h * yscaling);
1719 
1720  SDL_Rect outline_rect {
1721  minimap_location_.x + view_x - 1,
1722  minimap_location_.y + view_y - 1,
1723  view_w + 2,
1724  view_h + 2
1725  };
1726 
1727  draw::rect(outline_rect, 255, 255, 255);
1728 }
1729 
1731 {
1732  if (!preferences::minimap_draw_units() || is_blindfolded()) return;
1733 
1734  double xscaling = 1.0 * minimap_location_.w / get_map().w();
1735  double yscaling = 1.0 * minimap_location_.h / get_map().h();
1736 
1737  for(const auto& u : dc_->units()) {
1738  if (fogged(u.get_location()) ||
1739  (dc_->teams()[currentTeam_].is_enemy(u.side()) &&
1740  u.invisible(u.get_location())) ||
1741  u.get_hidden()) {
1742  continue;
1743  }
1744 
1745  int side = u.side();
1746  color_t col = team::get_minimap_color(side);
1747 
1749  auto status = orb_status::allied;
1750  if(dc_->teams()[currentTeam_].is_enemy(side)) {
1751  status = orb_status::enemy;
1752  } else if(currentTeam_ + 1 == static_cast<unsigned>(side)) {
1753  status = dc_->unit_orb_status(u);
1754  } else {
1755  // no-op, status is already set to orb_status::allied;
1756  }
1758  }
1759 
1760  double u_x = u.get_location().x * xscaling;
1761  double u_y = (u.get_location().y + (is_odd(u.get_location().x) ? 1 : -1)/4.0) * yscaling;
1762  // use 4/3 to compensate the horizontal hexes imbrication
1763  double u_w = 4.0 / 3.0 * xscaling;
1764  double u_h = yscaling;
1765 
1766  SDL_Rect r {
1767  minimap_location_.x + int(std::round(u_x))
1768  , minimap_location_.y + int(std::round(u_y))
1769  , int(std::round(u_w))
1770  , int(std::round(u_h))
1771  };
1772 
1773  draw::fill(r, col.r, col.g, col.b, col.a);
1774  }
1775 }
1776 
1777 bool display::scroll(int xmove, int ymove, bool force)
1778 {
1779  if(view_locked_ && !force) {
1780  return false;
1781  }
1782 
1783  // No move offset, do nothing.
1784  if(xmove == 0 && ymove == 0) {
1785  return false;
1786  }
1787 
1788  int new_x = xpos_ + xmove;
1789  int new_y = ypos_ + ymove;
1790 
1791  bounds_check_position(new_x, new_y);
1792 
1793  // Camera position doesn't change, exit.
1794  if(xpos_ == new_x && ypos_ == new_y) {
1795  return false;
1796  }
1797 
1798  const int diff_x = xpos_ - new_x;
1799  const int diff_y = ypos_ - new_y;
1800 
1801  xpos_ = new_x;
1802  ypos_ = new_y;
1803 
1804  /* Adjust floating label positions. This only affects labels whose position is anchored
1805  * to the map instead of the screen. In order to do that, we want to adjust their drawing
1806  * coordinates in the opposite direction of the screen scroll.
1807  *
1808  * The check a few lines up prevents any scrolling from happening if the camera position
1809  * doesn't change. Without that, the label still scroll even when the map edge is reached.
1810  * If that's removed, the following formula should work instead:
1811  *
1812  * const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
1813  */
1814  font::scroll_floating_labels(diff_x, diff_y);
1815 
1817 
1818  //
1819  // NOTE: the next three blocks can be removed once we switch to accelerated rendering.
1820  //
1821 
1822  if(!video::headless()) {
1823  rect dst = map_area();
1824  dst.x += diff_x;
1825  dst.y += diff_y;
1826  dst.clip(map_area());
1827 
1828  rect src = dst;
1829  src.x -= diff_x;
1830  src.y -= diff_y;
1831 
1832  // swap buffers
1834 
1835  // Set the source region to blit from
1836  back_.set_src(src);
1837 
1838  // copy from the back to the front buffer
1839  auto rts = draw::set_render_target(front_);
1840  draw::blit(back_, dst);
1841 
1842  back_.clear_src();
1843 
1844  // queue repaint
1846  }
1847 
1848  if(diff_y != 0) {
1849  SDL_Rect r = map_area();
1850 
1851  if(diff_y < 0) {
1852  r.y = r.y + r.h + diff_y;
1853  }
1854 
1855  r.h = std::abs(diff_y);
1857  }
1858 
1859  if(diff_x != 0) {
1860  SDL_Rect r = map_area();
1861 
1862  if(diff_x < 0) {
1863  r.x = r.x + r.w + diff_x;
1864  }
1865 
1866  r.w = std::abs(diff_x);
1868  }
1869 
1871 
1872  redraw_minimap();
1873 
1874  return true;
1875 }
1876 
1878 {
1879  return zoom_ == MaxZoom;
1880 }
1881 
1883 {
1884  return zoom_ == MinZoom;
1885 }
1886 
1887 bool display::set_zoom(bool increase)
1888 {
1889  // Ensure we don't try to access nonexistent vector indices.
1890  zoom_index_ = std::clamp(increase ? zoom_index_ + 1 : zoom_index_ - 1, 0, final_zoom_index);
1891 
1892  // No validation check is needed in the next step since we've already set the index here and
1893  // know the new zoom value is indeed valid.
1894  return set_zoom(zoom_levels[zoom_index_], false);
1895 }
1896 
1897 bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_index)
1898 {
1899  unsigned int new_zoom = std::clamp(amount, MinZoom, MaxZoom);
1900 
1901  LOG_DP << "new_zoom = " << new_zoom;
1902 
1903  if(new_zoom == zoom_) {
1904  return false;
1905  }
1906 
1907  if(validate_value_and_set_index) {
1908  zoom_index_ = get_zoom_levels_index (new_zoom);
1909  new_zoom = zoom_levels[zoom_index_];
1910  }
1911 
1912  if((new_zoom / 4) * 4 != new_zoom) {
1913  WRN_DP << "set_zoom forcing zoom " << new_zoom
1914  << " which is not a multiple of 4."
1915  << " This will likely cause graphical glitches.";
1916  }
1917 
1918  const SDL_Rect& outside_area = map_outside_area();
1919  const SDL_Rect& area = map_area();
1920 
1921  // Turn the zoom factor to a double in order to avoid rounding errors.
1922  double zoom_factor = static_cast<double>(new_zoom) / static_cast<double>(zoom_);
1923 
1924  // INVARIANT: xpos_ + area.w == xend where xend is as in bounds_check_position()
1925  //
1926  // xpos_: Position of the leftmost visible map pixel of the viewport, in pixels.
1927  // Affected by the current zoom: this->zoom_ pixels to the hex.
1928  //
1929  // xpos_ + area.w/2: Position of the center of the viewport, in pixels.
1930  //
1931  // (xpos_ + area.w/2) * new_zoom/zoom_: Position of the center of the
1932  // viewport, as it would be under new_zoom.
1933  //
1934  // (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
1935  // leftmost visible map pixel, as it would be under new_zoom.
1936  xpos_ = std::round(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2));
1937  ypos_ = std::round(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2));
1938  xpos_ -= (outside_area.w - area.w) / 2;
1939  ypos_ -= (outside_area.h - area.h) / 2;
1940 
1941  zoom_ = new_zoom;
1943  if(zoom_ != DefaultZoom) {
1944  last_zoom_ = zoom_;
1945  }
1946 
1948 
1950  redraw_background_ = true;
1951  invalidate_all();
1952 
1953  return true;
1954 }
1955 
1957 {
1958  if (zoom_ != DefaultZoom) {
1959  last_zoom_ = zoom_;
1961  } else {
1962  // When we are already at the default zoom,
1963  // switch to the last zoom used
1965  }
1966 }
1967 
1969 {
1970  int x = get_location_x(loc);
1971  int y = get_location_y(loc);
1972  return !outside_area(map_area(), x, y);
1973 }
1974 
1976 {
1977  int x = get_location_x(loc);
1978  int y = get_location_y(loc);
1979  const SDL_Rect &area = map_area();
1980  int hw = hex_width(), hs = hex_size();
1981  return x + hs >= area.x - hw && x < area.x + area.w + hw &&
1982  y + hs >= area.y - hs && y < area.y + area.h + hs;
1983 }
1984 
1985 void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force)
1986 {
1987  if(!force && (view_locked_ || !preferences::scroll_to_action())) return;
1988  if(video::headless()) {
1989  return;
1990  }
1991  const SDL_Rect area = map_area();
1992  const int xmove_expected = screenxpos - (area.x + area.w/2);
1993  const int ymove_expected = screenypos - (area.y + area.h/2);
1994 
1995  int xpos = xpos_ + xmove_expected;
1996  int ypos = ypos_ + ymove_expected;
1997  bounds_check_position(xpos, ypos);
1998  int xmove = xpos - xpos_;
1999  int ymove = ypos - ypos_;
2000 
2001  if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || preferences::scroll_speed() > 99) {
2002  scroll(xmove,ymove,true);
2003  redraw_minimap();
2004  events::draw();
2005  return;
2006  }
2007 
2008  // Doing an animated scroll, with acceleration etc.
2009 
2010  int x_old = 0;
2011  int y_old = 0;
2012 
2013  const double dist_total = std::hypot(xmove, ymove);
2014  double dist_moved = 0.0;
2015 
2016  int t_prev = SDL_GetTicks();
2017 
2018  double velocity = 0.0;
2019  while (dist_moved < dist_total) {
2020  events::pump();
2021 
2022  int t = SDL_GetTicks();
2023  double dt = (t - t_prev) / 1000.0;
2024  if (dt > 0.200) {
2025  // Do not skip too many frames on slow PCs
2026  dt = 0.200;
2027  }
2028  t_prev = t;
2029 
2030  const double accel_time = 0.3 / turbo_speed(); // seconds until full speed is reached
2031  const double decel_time = 0.4 / turbo_speed(); // seconds from full speed to stop
2032 
2033  double velocity_max = preferences::scroll_speed() * 60.0;
2034  velocity_max *= turbo_speed();
2035  double accel = velocity_max / accel_time;
2036  double decel = velocity_max / decel_time;
2037 
2038  // If we started to decelerate now, where would we stop?
2039  double stop_time = velocity / decel;
2040  double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time;
2041  if (dist_stop > dist_total || velocity > velocity_max) {
2042  velocity -= decel * dt;
2043  if (velocity < 1.0) velocity = 1.0;
2044  } else {
2045  velocity += accel * dt;
2046  if (velocity > velocity_max) velocity = velocity_max;
2047  }
2048 
2049  dist_moved += velocity * dt;
2050  if (dist_moved > dist_total) dist_moved = dist_total;
2051 
2052  int x_new = std::round(xmove * dist_moved / dist_total);
2053  int y_new = std::round(ymove * dist_moved / dist_total);
2054 
2055  int dx = x_new - x_old;
2056  int dy = y_new - y_old;
2057 
2058  scroll(dx,dy,true);
2059  x_old += dx;
2060  y_old += dy;
2061 
2062  redraw_minimap();
2063  events::draw();
2064  }
2065 }
2066 
2067 void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, bool check_fogged, bool force)
2068 {
2069  if(get_map().on_board(loc) == false) {
2070  ERR_DP << "Tile at " << loc << " isn't on the map, can't scroll to the tile.";
2071  return;
2072  }
2073 
2074  std::vector<map_location> locs;
2075  locs.push_back(loc);
2076  scroll_to_tiles(locs, scroll_type, check_fogged,false,0.0,force);
2077 }
2078 
2080  SCROLL_TYPE scroll_type, bool check_fogged,
2081  double add_spacing, bool force)
2082 {
2083  std::vector<map_location> locs;
2084  locs.push_back(loc1);
2085  locs.push_back(loc2);
2086  scroll_to_tiles(locs, scroll_type, check_fogged, false, add_spacing,force);
2087 }
2088 
2089 void display::scroll_to_tiles(const std::vector<map_location>::const_iterator & begin,
2090  const std::vector<map_location>::const_iterator & end,
2091  SCROLL_TYPE scroll_type, bool check_fogged,
2092  bool only_if_possible, double add_spacing, bool force)
2093 {
2094  // basically we calculate the min/max coordinates we want to have on-screen
2095  int minx = 0;
2096  int maxx = 0;
2097  int miny = 0;
2098  int maxy = 0;
2099  bool valid = false;
2100 
2101  for(std::vector<map_location>::const_iterator itor = begin; itor != end ; ++itor) {
2102  if(get_map().on_board(*itor) == false) continue;
2103  if(check_fogged && fogged(*itor)) continue;
2104 
2105  int x = get_location_x(*itor);
2106  int y = get_location_y(*itor);
2107 
2108  if (!valid) {
2109  minx = x;
2110  maxx = x;
2111  miny = y;
2112  maxy = y;
2113  valid = true;
2114  } else {
2115  int minx_new = std::min<int>(minx,x);
2116  int miny_new = std::min<int>(miny,y);
2117  int maxx_new = std::max<int>(maxx,x);
2118  int maxy_new = std::max<int>(maxy,y);
2119  SDL_Rect r = map_area();
2120  r.x = minx_new;
2121  r.y = miny_new;
2122  if(outside_area(r, maxx_new, maxy_new)) {
2123  // we cannot fit all locations to the screen
2124  if (only_if_possible) return;
2125  break;
2126  }
2127  minx = minx_new;
2128  miny = miny_new;
2129  maxx = maxx_new;
2130  maxy = maxy_new;
2131  }
2132  }
2133  //if everything is fogged or the location list is empty
2134  if(!valid) return;
2135 
2136  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2137  SDL_Rect r = map_area();
2138  int spacing = std::round(add_spacing * hex_size());
2139  r.x += spacing;
2140  r.y += spacing;
2141  r.w -= 2*spacing;
2142  r.h -= 2*spacing;
2143  if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) {
2144  return;
2145  }
2146  }
2147 
2148  // let's do "normal" rectangle math from now on
2149  SDL_Rect locs_bbox;
2150  locs_bbox.x = minx;
2151  locs_bbox.y = miny;
2152  locs_bbox.w = maxx - minx + hex_size();
2153  locs_bbox.h = maxy - miny + hex_size();
2154 
2155  // target the center
2156  int target_x = locs_bbox.x + locs_bbox.w/2;
2157  int target_y = locs_bbox.y + locs_bbox.h/2;
2158 
2159  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
2160  // when doing an ONSCREEN scroll we do not center the target unless needed
2161  SDL_Rect r = map_area();
2162  int map_center_x = r.x + r.w/2;
2163  int map_center_y = r.y + r.h/2;
2164 
2165  int h = r.h;
2166  int w = r.w;
2167 
2168  // we do not want to be only inside the screen rect, but center a bit more
2169  double inside_frac = 0.5; // 0.0 = always center the target, 1.0 = scroll the minimum distance
2170  w = static_cast<int>(w * inside_frac);
2171  h = static_cast<int>(h * inside_frac);
2172 
2173  // shrink the rectangle by the size of the locations rectangle we found
2174  // such that the new task to fit a point into a rectangle instead of rectangle into rectangle
2175  w -= locs_bbox.w;
2176  h -= locs_bbox.h;
2177 
2178  if (w < 1) w = 1;
2179  if (h < 1) h = 1;
2180 
2181  r.x = target_x - w/2;
2182  r.y = target_y - h/2;
2183  r.w = w;
2184  r.h = h;
2185 
2186  // now any point within r is a possible target to scroll to
2187  // we take the one with the minimum distance to map_center
2188  // which will always be at the border of r
2189 
2190  if (map_center_x < r.x) {
2191  target_x = r.x;
2192  target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
2193  } else if (map_center_x > r.x+r.w-1) {
2194  target_x = r.x + r.w - 1;
2195  target_y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
2196  } else if (map_center_y < r.y) {
2197  target_y = r.y;
2198  target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
2199  } else if (map_center_y > r.y+r.h-1) {
2200  target_y = r.y + r.h - 1;
2201  target_x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
2202  } else {
2203  ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all...";
2204  // keep the target at the center
2205  }
2206  }
2207 
2208  scroll_to_xy(target_x, target_y,scroll_type,force);
2209 }
2210 
2211 
2213 {
2214  zoom_ = std::clamp(zoom_, MinZoom, MaxZoom);
2216 }
2217 
2218 void display::bounds_check_position(int& xpos, int& ypos) const
2219 {
2220  const int tile_width = hex_width();
2221 
2222  // Adjust for the border 2 times
2223  const int xend = static_cast<int>(tile_width * (get_map().w() + 2 * theme_.border().size) + tile_width / 3);
2224  const int yend = static_cast<int>(zoom_ * (get_map().h() + 2 * theme_.border().size) + zoom_ / 2);
2225 
2226  xpos = std::clamp(xpos, 0, xend - map_area().w);
2227  ypos = std::clamp(ypos, 0, yend - map_area().h);
2228 }
2229 
2230 double display::turbo_speed() const
2231 {
2232  bool res = preferences::turbo();
2233  if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT]) {
2234  res = !res;
2235  }
2236 
2237  res |= video::headless();
2238  if(res)
2239  return preferences::turbo_speed();
2240  else
2241  return 1.0;
2242 }
2243 
2245 {
2246  prevent_draw_ = pd;
2247  if (!pd) {
2248  // ensure buttons are visible
2249  unhide_buttons();
2250  }
2251 }
2252 
2254 {
2255  return prevent_draw_;
2256 }
2257 
2258 
2260  const std::string& old_mask,
2261  const std::string& new_mask)
2262 {
2263  // TODO: hwaccel - this needs testing as it's not used in mainline
2266 
2267  int duration = 300 / turbo_speed();
2268  int start = SDL_GetTicks();
2269  for(int now = start; now < start + duration; now = SDL_GetTicks()) {
2270  float prop_f = float(now - start) / float(duration);
2271  uint8_t p = float_to_color(prop_f);
2272  tod_hex_alpha2 = p;
2273  tod_hex_alpha1 = ~p;
2276  }
2277 
2278  tod_hex_mask1.reset();
2279  tod_hex_mask2.reset();
2280 }
2281 
2282 void display::fade_to(const color_t& c, int duration)
2283 {
2284  uint32_t start = SDL_GetTicks();
2285  color_t fade_start = fade_color_;
2286  color_t fade_end = c;
2287 
2288  // If we started transparent, assume the same colour
2289  if(fade_start.a == 0) {
2290  fade_start.r = fade_end.r;
2291  fade_start.g = fade_end.g;
2292  fade_start.b = fade_end.b;
2293  }
2294 
2295  // If we are ending transparent, assume the same colour
2296  if(fade_end.a == 0) {
2297  fade_end.r = fade_start.r;
2298  fade_end.g = fade_start.g;
2299  fade_end.b = fade_start.b;
2300  }
2301 
2302  // Smoothly blend and display
2303  for(uint32_t now = start; now < start + duration; now = SDL_GetTicks()) {
2304  float prop_f = float(now - start) / float(duration);
2305  uint8_t p = float_to_color(prop_f);
2306  fade_color_ = fade_start.smooth_blend(fade_end, p);
2309  }
2310  fade_color_ = fade_end;
2312  events::draw();
2313 }
2314 
2316 {
2317  fade_color_ = c;
2318 }
2319 
2321 {
2322  if(video::headless())
2323  return;
2324 
2325  DBG_DP << "redrawing everything";
2326 
2327  // This is specifically for game_display.
2328  // It would probably be better to simply make this function virtual,
2329  // if game_display needs to do special processing.
2330  invalidateGameStatus_ = true;
2331 
2332  reportLocations_.clear();
2333  reportSurfaces_.clear();
2334  reports_.clear();
2335 
2337 
2339 
2341 
2342  if(!menu_buttons_.empty() || !action_buttons_.empty()) {
2343  create_buttons();
2344  }
2345 
2346  if(resources::controller) {
2348  if(command_executor != nullptr) {
2349  // This function adds button overlays,
2350  // it needs to be run after recreating the buttons.
2351  command_executor->set_button_state();
2352  }
2353  }
2354 
2355  if (!gui::in_dialog()) {
2357  }
2358 
2359  redraw_background_ = true;
2360 
2361  // This is only for one specific use, which is by the editor controller.
2362  // It would be vastly better if this didn't exist.
2363  for(std::function<void(display&)> f : redraw_observers_) {
2364  f(*this);
2365  }
2366 
2367  invalidate_all();
2368 
2370 }
2371 
2373 {
2374  // Could redraw a smaller region if the display doesn't use it all,
2375  // but when does that ever happen?
2377 }
2378 
2379 void display::add_redraw_observer(std::function<void(display&)> f)
2380 {
2381  redraw_observers_.push_back(f);
2382 }
2383 
2385 {
2386  redraw_observers_.clear();
2387 }
2388 
2390 {
2391  if(video::headless()) {
2392  DBG_DP << "display::draw denied";
2393  return;
2394  }
2395  //DBG_DP << "display::draw";
2396 
2397  // I have no idea why this is messing with sync context,
2398  // but i'm not going to touch it.
2400 
2401  // This isn't the best, but also isn't important enough to do better.
2403  DBG_DP << "display::draw redraw background";
2406  redraw_background_ = false;
2407  }
2408 
2409  if(!get_map().empty()) {
2410  if(!invalidated_.empty()) {
2411  draw_invalidated();
2412  invalidated_.clear();
2413  }
2415  }
2416 
2418  update_fps_label();
2419  update_fps_count();
2420  } else if(fps_handle_ != 0) {
2421  clear_fps_label();
2422  }
2423 }
2424 
2426 {
2427  //DBG_DP << "display::update";
2428  // Ensure render textures are correctly sized and up-to-date.
2430 
2431  // Trigger cache rebuild if animated water preference has changed.
2434  builder_->rebuild_cache_all();
2435  }
2436 
2438  invalidate_all();
2439  }
2440 }
2441 
2443 {
2444  //DBG_DP << "display::layout";
2445 
2446  // There's nothing that actually does layout here, it all happens in
2447  // response to events. This isn't ideal, but neither is changing that.
2448 
2449  // Post-layout / Pre-render
2450 
2451  if (!get_map().empty()) {
2452  if(redraw_background_) {
2453  invalidateAll_ = true;
2454  }
2455  if(invalidateAll_) {
2456  DBG_DP << "draw() with invalidateAll";
2457 
2458  // toggle invalidateAll_ first to allow regular invalidations
2459  invalidateAll_ = false;
2461 
2462  redraw_minimap();
2463  }
2464  }
2465 
2466  // invalidate animated terrain, units and haloes
2468 
2469  // Update and invalidate floating labels as necessary
2471 }
2472 
2474 {
2475  // This should render the game map and units.
2476  // It is not responsible for halos and floating labels.
2477  //DBG_DP << "display::render";
2478 
2479  // No need to render if we aren't going to draw anything.
2480  if(prevent_draw_) {
2481  DBG_DP << "render prevented";
2482  return;
2483  }
2484 
2485  // render to the offscreen buffer
2486  auto target_setter = draw::set_render_target(front_);
2487  draw();
2488 
2489  // update the minimap texture, if necessary
2490  // TODO: highdpi - high DPI minimap
2491  const rect& area = minimap_area();
2492  if(!area.empty() && !minimap_renderer_) {
2494  if(!minimap_renderer_) {
2495  ERR_DP << "error creating minimap";
2496  return;
2497  }
2498  }
2499 }
2500 
2501 bool display::expose(const rect& region)
2502 {
2503  if(prevent_draw_) {
2504  DBG_DP << "draw prevented";
2505  return false;
2506  }
2507 
2508  rect clipped_region = draw::get_clip().intersect(region);
2509 
2510  // Blit from the pre-rendered front buffer.
2511  if(clipped_region.overlaps(map_outside_area())) {
2512  front_.set_src(clipped_region);
2513  draw::blit(front_, clipped_region);
2514  front_.clear_src();
2515  }
2516 
2517  // Render halos.
2518  halo_man_.render(clipped_region);
2519 
2520  // Render UI elements.
2521  // Ideally buttons would be drawn as part of panels,
2522  // but they are currently TLDs so they draw themselves.
2523  // This also means they draw over tooltips...
2524  draw_all_panels(clipped_region);
2525  draw_reports(clipped_region);
2526  if(clipped_region.overlaps(minimap_area())) {
2527  draw_minimap();
2528  }
2529 
2530  // Floating labels should probably be separated by type,
2531  // but they aren't so they all get drawn here.
2533 
2534  // If there's a fade, apply it over everything
2535  if(fade_color_.a) {
2536  draw::fill(map_outside_area().intersect(region), fade_color_);
2537  }
2538 
2539  DBG_DP << "display::expose " << region;
2540 
2541  // The display covers the entire screen.
2542  // We will always be drawing something.
2543  return true;
2544 }
2545 
2547 {
2548  assert(!map_screenshot_);
2549  // There's no good way to determine this, as themes can put things
2550  // anywhere. Just return the entire game canvas.
2551  return video::game_canvas();
2552 }
2553 
2555 {
2556  if(video::headless()) {
2557  return;
2558  }
2559 
2560  // We ignore any logical offset on the underlying window buffer.
2561  // Render buffer size is always a simple multiple of the draw area.
2562  rect darea = video::game_canvas();
2563  rect oarea = darea * video::get_pixel_scale();
2564 
2565  // Check that the front buffer size is correct.
2566  // Buffers are always resized together, so we only need to check one.
2568  point dsize = front_.draw_size();
2569  bool raw_size_changed = size.x != oarea.w || size.y != oarea.h;
2570  bool draw_size_changed = dsize.x != darea.w || dsize.y != darea.h;
2571  if (!raw_size_changed && !draw_size_changed) {
2572  // buffers are fine
2573  return;
2574  }
2575 
2576  if(raw_size_changed) {
2577  LOG_DP << "regenerating render buffers as " << oarea;
2578  front_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2579  back_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2580  }
2581  if(raw_size_changed || draw_size_changed) {
2582  LOG_DP << "updating render buffer draw size to " << darea;
2583  front_.set_draw_size(darea.w, darea.h);
2584  back_.set_draw_size(darea.w, darea.h);
2585  }
2586 
2587  // Fill entire texture with black, just in case
2588  for(int i = 0; i < 2; ++i) {
2589  auto setter = draw::set_render_target(i ? back_ : front_);
2590  draw::fill(0,0,0);
2591  }
2592 
2593  // Fill in the background area on both textures.
2595 
2596  queue_rerender();
2597 }
2598 
2600 {
2601  // This could be optimized to avoid the map area,
2602  // but it's only called on game creation or zoom anyway.
2603  const SDL_Rect clip_rect = map_outside_area();
2605  for(int i = 0; i < 2; ++i) {
2606  auto setter = draw::set_render_target(i ? back_ : front_);
2607  if(bgtex) {
2608  draw::tiled(bgtex, clip_rect);
2609  } else {
2610  draw::fill(clip_rect, 0, 0, 0);
2611  }
2612  }
2613 }
2614 
2616 {
2617  return *map_labels_;
2618 }
2619 
2621 {
2622  return *map_labels_;
2623 }
2624 
2626 {
2627  return map_area();
2628 }
2629 
2631 {
2632  // log_scope("display::draw_invalidated");
2633  SDL_Rect clip_rect = get_clip_rect();
2634  const auto clipper = draw::reduce_clip(clip_rect);
2635 
2636  DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes with clip " << clip_rect;
2637 
2638  // The unit drawer can't function without teams
2639  std::optional<unit_drawer> drawer{};
2640  if(!dc_->teams().empty()) {
2641  drawer.emplace(*this);
2642  }
2643 
2644  for(const map_location& loc : invalidated_) {
2645  int xpos = get_location_x(loc);
2646  int ypos = get_location_y(loc);
2647 
2648  rect hex_rect(xpos, ypos, zoom_, zoom_);
2649  if(!hex_rect.overlaps(clip_rect)) {
2650  continue;
2651  }
2652 
2653  draw_hex(loc);
2654  drawn_hexes_ += 1;
2655 
2656  if(drawer) {
2657  const auto u_it = dc_->units().find(loc);
2658  const auto request = exclusive_unit_draw_requests_.find(loc);
2659 
2660  if(u_it != dc_->units().end() && (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id())) {
2661  drawer->redraw_unit(*u_it);
2662  }
2663  }
2664 
2665  draw_manager::invalidate_region(hex_rect.intersect(clip_rect));
2666  }
2667 
2668  invalidated_hexes_ += invalidated_.size();
2669 }
2670 
2672 {
2673  const bool on_map = get_map().on_board(loc);
2674  const time_of_day& tod = get_time_of_day(loc);
2675 
2676  int num_images_fg = 0;
2677  int num_images_bg = 0;
2678 
2679  const bool is_shrouded = shrouded(loc);
2680 
2681  // unshrouded terrain (the normal case)
2682  if(!is_shrouded) {
2683  get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
2684  num_images_bg = terrain_image_vector_.size();
2685 
2686  drawing_buffer_add(LAYER_TERRAIN_BG, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2687  for(const texture& t : images) {
2688  draw::blit(t, dest);
2689  }
2690  });
2691 
2692  get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
2693  num_images_fg = terrain_image_vector_.size();
2694 
2695  drawing_buffer_add(LAYER_TERRAIN_FG, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2696  for(const texture& t : images) {
2697  draw::blit(t, dest);
2698  }
2699  });
2700 
2701  // Draw the grid, if that's been enabled
2702  if(preferences::grid()) {
2705 
2707  [tex = image::get_texture(grid_top, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2708 
2710  [tex = image::get_texture(grid_bottom, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2711  }
2712  }
2713 
2714  const t_translation::terrain_code terrain = get_map().get_terrain(loc);
2715  const terrain_type& terrain_info = get_map().get_terrain_info(terrain);
2716  const double submerge = terrain_info.unit_submerge();
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  if(ov.submerge) {
2743  // Adjust submerge appropriately
2744  double sub = submerge * ov.submerge;
2745  // Shift the image so the waterline remains static.
2746  // This is so that units swimming there look okay.
2747  int shift = isize.y * (sub - submerge);
2748  add_submerge_ipf_mod(ipf, isize.y, sub, shift);
2749  }
2750 
2751  const texture tex = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos
2752  ? image::get_lighted_texture(ipf, lt)
2754 
2755  drawing_buffer_add(LAYER_TERRAIN_BG, loc, [tex](const rect& dest) { draw::blit(tex, dest); });
2756  }
2757  }
2758  }
2759  }
2760  }
2761 
2762  // village-control flags.
2763  if(!is_shrouded) {
2764  drawing_buffer_add(LAYER_TERRAIN_BG, loc, [tex = get_flag(loc)](const rect& dest) { draw::blit(tex, dest); });
2765  }
2766 
2767  // Draw the time-of-day mask on top of the terrain in the hex.
2768  // tod may differ from tod if hex is illuminated.
2769  const std::string& tod_hex_mask = tod.image_mask;
2770  if(tod_hex_mask1 || tod_hex_mask2) {
2771  drawing_buffer_add(LAYER_TERRAIN_FG, loc, [=](const rect& dest) mutable {
2773  draw::blit(tod_hex_mask1, dest);
2774 
2776  draw::blit(tod_hex_mask2, dest);
2777  });
2778  } else if(!tod_hex_mask.empty()) {
2780  [tex = image::get_texture(tod_hex_mask, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
2781  }
2782 
2783  // Paint arrows
2784  arrows_map_t::const_iterator arrows_in_hex = arrows_map_.find(loc);
2785  if(arrows_in_hex != arrows_map_.end()) {
2786  for (arrow* const a : arrows_in_hex->second) {
2787  a->draw_hex(loc);
2788  }
2789  }
2790 
2791  // Apply shroud, fog and linger overlay
2792 
2793  if(is_shrouded) {
2794  // We apply void also on off-map tiles to shroud the half-hexes too
2797  draw::blit(tex, dest);
2798  });
2799  } else if(fogged(loc)) {
2801  [tex = image::get_texture(get_variant(fog_images_, loc), image::TOD_COLORED)](const rect& dest) {
2802  draw::blit(tex, dest);
2803  });
2804  }
2805 
2806  if(!is_shrouded) {
2808  for(const texture& t : images) {
2809  draw::blit(t, dest);
2810  }
2811  });
2812  }
2813 
2816  [tex = image::get_texture(image::locator{"terrain/foreground.png"}, image::TOD_COLORED)](const rect& dest) {
2817  draw::blit(tex, dest);
2818  });
2819  }
2820 
2821  if(on_map) {
2822  // This might be slight overkill. Basically, we want to check that none of the
2823  // first three bits in the debug flag bitset are set so we can avoid creating
2824  // a stringstream, a temp string, and attempting to trim it for every hex even
2825  // when none of these flags are set. This gives us a temp object with all bits
2826  // past the first three zeroed out.
2827  if((std::as_const(debug_flags_) << (__NUM_DEBUG_FLAGS - DEBUG_FOREGROUND)).none()) {
2828  return;
2829  }
2830 
2831  std::ostringstream ss;
2833  ss << loc << '\n';
2834  }
2835 
2837  ss << get_map().get_terrain(loc) << '\n';
2838  }
2839 
2841  ss << (num_images_bg + num_images_fg) << '\n';
2842  }
2843 
2844  std::string output = ss.str();
2846 
2847  if(output.empty()) {
2848  return;
2849  }
2850 
2852  renderer.set_text(output, false);
2853  renderer.set_font_size(font::SIZE_TINY);
2854  renderer.set_alignment(PANGO_ALIGN_CENTER);
2855  renderer.set_foreground_color(font::NORMAL_COLOR);
2856  renderer.set_maximum_height(-1, false);
2857  renderer.set_maximum_width(-1);
2858 
2859  drawing_buffer_add(LAYER_FOG_SHROUD, loc, [tex = renderer.render_and_get_texture()](const rect& dest) {
2860  const rect text_dest {
2861  (dest.x + dest.w / 2) - (tex.w() / 2),
2862  (dest.y + dest.h / 2) - (tex.h() / 2),
2863  tex.w(),
2864  tex.h()
2865  };
2866 
2867  // Add a little horizontal padding to the bg
2868  const rect bg_dest {
2869  text_dest.x - 3,
2870  text_dest.y - 3,
2871  text_dest.w + 6,
2872  text_dest.h + 6
2873  };
2874 
2875  draw::fill(bg_dest, {0, 0, 0, 0xaa});
2876  draw::blit(tex, text_dest);
2877  });
2878  }
2879 }
2880 
2881 /**
2882  * Redraws the specified report (if anything has changed).
2883  * If a config is not supplied, it will be generated via
2884  * reports::generate_report().
2885  */
2886 void display::refresh_report(const std::string& report_name, const config * new_cfg)
2887 {
2888  const theme::status_item *item = theme_.get_status_item(report_name);
2889  if (!item) {
2890  // This should be a warning, but unfortunately there are too many
2891  // unused reports to easily deal with.
2892  //WRN_DP << "no report '" << report_name << "' in theme";
2893  return;
2894  }
2895 
2896  // Now we will need the config. Generate one if needed.
2897 
2899 
2900  if (resources::controller) {
2902  }
2903 
2904  reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
2905 
2906  const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
2907  if ( new_cfg == nullptr )
2908  new_cfg = &generated_cfg;
2909 
2910  rect& loc = reportLocations_[report_name];
2911  const rect& new_loc = item->location(video::game_canvas());
2912  config &report = reports_[report_name];
2913 
2914  // Report and its location is unchanged since last time. Do nothing.
2915  if (loc == new_loc && report == *new_cfg) {
2916  return;
2917  }
2918 
2919  DBG_DP << "updating report: " << report_name;
2920 
2921  // Mark both old and new locations for redraw.
2924 
2925  // Update the config and current location.
2926  report = *new_cfg;
2927  loc = new_loc;
2928 
2929  // Not 100% sure this is okay
2930  // but it seems to be working so i'm not changing it.
2932 
2933  if (report.empty()) return;
2934 
2935  // Add prefix, postfix elements.
2936  // Make sure that they get the same tooltip
2937  // as the guys around them.
2938  std::string str = item->prefix();
2939  if (!str.empty()) {
2940  config &e = report.add_child_at("element", config(), 0);
2941  e["text"] = str;
2942  e["tooltip"] = report.mandatory_child("element")["tooltip"];
2943  }
2944  str = item->postfix();
2945  if (!str.empty()) {
2946  config &e = report.add_child("element");
2947  e["text"] = str;
2948  e["tooltip"] = report.mandatory_child("element", -1)["tooltip"];
2949  }
2950 
2951  // Do a fake run of drawing the report, so tooltips can be determined.
2952  // TODO: this is horrible, refactor reports to actually make sense
2953  draw_report(report_name, true);
2954 }
2955 
2956 void display::draw_report(const std::string& report_name, bool tooltip_test)
2957 {
2958  const theme::status_item *item = theme_.get_status_item(report_name);
2959  if (!item) {
2960  // This should be a warning, but unfortunately there are too many
2961  // unused reports to easily deal with.
2962  //WRN_DP << "no report '" << report_name << "' in theme";
2963  return;
2964  }
2965 
2966  const rect& loc = reportLocations_[report_name];
2967  const config& report = reports_[report_name];
2968 
2969  int x = loc.x, y = loc.y;
2970 
2971  // Loop through and display each report element.
2972  int tallest = 0;
2973  int image_count = 0;
2974  bool used_ellipsis = false;
2975  std::ostringstream ellipsis_tooltip;
2976  SDL_Rect ellipsis_area = loc;
2977 
2978  for (config::const_child_itors elements = report.child_range("element");
2979  elements.begin() != elements.end(); elements.pop_front())
2980  {
2981  SDL_Rect area {x, y, loc.w + loc.x - x, loc.h + loc.y - y};
2982  if (area.h <= 0) break;
2983 
2984  std::string t = elements.front()["text"];
2985  if (!t.empty())
2986  {
2987  if (used_ellipsis) goto skip_element;
2988 
2989  // Draw a text element.
2991  bool eol = false;
2992  if (t[t.size() - 1] == '\n') {
2993  eol = true;
2994  t = t.substr(0, t.size() - 1);
2995  }
2996  // If stripping left the text empty, skip it.
2997  if (t.empty()) {
2998  // Blank text has a null size when rendered.
2999  // It does not, however, have a null size when the size
3000  // is requested with get_size(). Hence this check.
3001  continue;
3002  }
3003  text.set_link_aware(false)
3004  .set_text(t, true);
3006  .set_font_size(item->font_size())
3008  .set_alignment(PANGO_ALIGN_LEFT)
3009  .set_foreground_color(item->font_rgb_set() ? item->font_rgb() : font::NORMAL_COLOR)
3010  .set_maximum_width(area.w)
3011  .set_maximum_height(area.h, false)
3012  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
3014 
3015  point tsize = text.get_size();
3016 
3017  // check if next element is text with almost no space to show it
3018  const int minimal_text = 12; // width in pixels
3019  config::const_child_iterator ee = elements.begin();
3020  if (!eol && loc.w - (x - loc.x + tsize.x) < minimal_text &&
3021  ++ee != elements.end() && !(*ee)["text"].empty())
3022  {
3023  // make this element longer to trigger rendering of ellipsis
3024  // (to indicate that next elements have not enough space)
3025  //NOTE this space should be longer than minimal_text pixels
3026  t = t + " ";
3027  text.set_text(t, true);
3028  tsize = text.get_size();
3029  // use the area of this element for next tooltips
3030  used_ellipsis = true;
3031  ellipsis_area.x = x;
3032  ellipsis_area.y = y;
3033  ellipsis_area.w = tsize.x;
3034  ellipsis_area.h = tsize.y;
3035  }
3036 
3037  area.w = tsize.x;
3038  area.h = tsize.y;
3039  if (!tooltip_test) {
3040  draw::blit(text.render_and_get_texture(), area);
3041  }
3042  if (area.h > tallest) {
3043  tallest = area.h;
3044  }
3045  if (eol) {
3046  x = loc.x;
3047  y += tallest;
3048  tallest = 0;
3049  } else {
3050  x += area.w;
3051  }
3052  }
3053  else if (!(t = elements.front()["image"].str()).empty())
3054  {
3055  if (used_ellipsis) goto skip_element;
3056 
3057  // Draw an image element.
3059 
3060  if (!img) {
3061  ERR_DP << "could not find image for report: '" << t << "'";
3062  continue;
3063  }
3064 
3065  if (area.w < img.w() && image_count) {
3066  // We have more than one image, and this one doesn't fit.
3068  used_ellipsis = true;
3069  }
3070 
3071  if (img.w() < area.w) area.w = img.w();
3072  if (img.h() < area.h) area.h = img.h();
3073  if (!tooltip_test) {
3074  draw::blit(img, area);
3075  }
3076 
3077  ++image_count;
3078  if (area.h > tallest) {
3079  tallest = area.h;
3080  }
3081 
3082  if (!used_ellipsis) {
3083  x += area.w;
3084  } else {
3085  ellipsis_area = area;
3086  }
3087  }
3088  else
3089  {
3090  // No text nor image, skip this element
3091  continue;
3092  }
3093 
3094  skip_element:
3095  t = elements.front()["tooltip"].t_str().c_str();
3096  if (!t.empty()) {
3097  if (tooltip_test && !used_ellipsis) {
3098  tooltips::add_tooltip(area, t, elements.front()["help"].t_str().c_str());
3099  } else {
3100  // Collect all tooltips for the ellipsis.
3101  // TODO: need a better separator
3102  // TODO: assign an action
3103  ellipsis_tooltip << t;
3104  config::const_child_iterator ee = elements.begin();
3105  if (++ee != elements.end())
3106  ellipsis_tooltip << "\n _________\n\n";
3107  }
3108  }
3109  }
3110 
3111  if (tooltip_test && used_ellipsis) {
3112  tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str());
3113  }
3114 }
3115 
3116 bool display::draw_reports(const rect& region)
3117 {
3118  bool drew = false;
3119  for(const auto& it : reports_) {
3120  const std::string& name = it.first;
3121  const rect& loc = reportLocations_[name];
3122  if(loc.overlaps(region)) {
3123  draw_report(name);
3124  drew = true;
3125  }
3126  }
3127  return drew;
3128 }
3129 
3131 {
3132  DBG_DP << "invalidate_all()";
3133  invalidateAll_ = true;
3134  invalidated_.clear();
3135 }
3136 
3138 {
3140  return false;
3141 
3142  bool tmp;
3143  tmp = invalidated_.insert(loc).second;
3144  return tmp;
3145 }
3146 
3147 bool display::invalidate(const std::set<map_location>& locs)
3148 {
3150  return false;
3151  bool ret = false;
3152  for (const map_location& loc : locs) {
3153  ret = invalidated_.insert(loc).second || ret;
3154  }
3155  return ret;
3156 }
3157 
3158 bool display::propagate_invalidation(const std::set<map_location>& locs)
3159 {
3160  if(invalidateAll_)
3161  return false;
3162 
3163  if(locs.size()<=1)
3164  return false; // propagation never needed
3165 
3166  bool result = false;
3167  {
3168  // search the first hex invalidated (if any)
3169  std::set<map_location>::const_iterator i = locs.begin();
3170  for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {}
3171 
3172  if (i != locs.end()) {
3173 
3174  // propagate invalidation
3175  // 'i' is already in, but I suspect that splitting the range is bad
3176  // especially because locs are often adjacents
3177  size_t previous_size = invalidated_.size();
3178  invalidated_.insert(locs.begin(), locs.end());
3179  result = previous_size < invalidated_.size();
3180  }
3181  }
3182  return result;
3183 }
3184 
3186 {
3187  return invalidate_locations_in_rect(map_area().intersect(rect));
3188 }
3189 
3191 {
3193  return false;
3194 
3195  DBG_DP << "invalidating locations in " << rect;
3196 
3197  bool result = false;
3198  for(const map_location& loc : hexes_under_rect(rect)) {
3199  //DBG_DP << "invalidating " << loc.x << ',' << loc.y;
3200  result |= invalidate(loc);
3201  }
3202  return result;
3203 }
3204 
3206 {
3207  if(get_map().is_village(loc)) {
3208  const int owner = dc_->village_owner(loc) - 1;
3209  if(owner >= 0 && flags_[owner].need_update()
3210  && (!fogged(loc) || !dc_->teams()[currentTeam_].is_enemy(owner + 1))) {
3211  invalidate(loc);
3212  }
3213  }
3214 }
3215 
3217 {
3218  // There are timing issues with this, but i'm not touching it.
3221  if(animate_map_) {
3222  for(const map_location& loc : get_visible_hexes()) {
3223  if(shrouded(loc))
3224  continue;
3225  if(builder_->update_animation(loc)) {
3226  invalidate(loc);
3227  } else {
3229  }
3230  }
3231  }
3232 
3233  for(const unit& u : dc_->units()) {
3234  u.anim_comp().refresh();
3235  }
3236  for(const unit* u : *fake_unit_man_) {
3237  u->anim_comp().refresh();
3238  }
3239 
3240  bool new_inval;
3241  do {
3242  new_inval = false;
3243  for(const unit& u : dc_->units()) {
3244  new_inval |= u.anim_comp().invalidate(*this);
3245  }
3246  for(const unit* u : *fake_unit_man_) {
3247  new_inval |= u->anim_comp().invalidate(*this);
3248  }
3249  } while(new_inval);
3250 
3251  halo_man_.update();
3252 }
3253 
3255 {
3256  for(const unit & u : dc_->units()) {
3257  u.anim_comp().set_standing();
3258  }
3259 }
3260 
3262 {
3263  for(const map_location& loc : arrow.get_path()) {
3264  arrows_map_[loc].push_back(&arrow);
3265  }
3266 }
3267 
3269 {
3270  for(const map_location& loc : arrow.get_path()) {
3271  arrows_map_[loc].remove(&arrow);
3272  }
3273 }
3274 
3276 {
3277  for(const map_location& loc : arrow.get_previous_path()) {
3278  arrows_map_[loc].remove(&arrow);
3279  }
3280 
3281  for(const map_location& loc : arrow.get_path()) {
3282  arrows_map_[loc].push_back(&arrow);
3283  }
3284 }
3285 
3287 {
3288  const SDL_Rect& rect = map_area();
3289  return pixel_position_to_hex(xpos_ + rect.x + rect.w / 2 , ypos_ + rect.y + rect.h / 2 );
3290 }
3291 
3292 void display::write(config& cfg) const
3293 {
3294  cfg["view_locked"] = view_locked_;
3295  cfg["color_adjust_red"] = color_adjust_.r;
3296  cfg["color_adjust_green"] = color_adjust_.g;
3297  cfg["color_adjust_blue"] = color_adjust_.b;
3298  get_middle_location().write(cfg.add_child("location"));
3299 }
3300 
3301 void display::read(const config& cfg)
3302 {
3303  view_locked_ = cfg["view_locked"].to_bool(false);
3304  color_adjust_.r = cfg["color_adjust_red"].to_int(0);
3305  color_adjust_.g = cfg["color_adjust_green"].to_int(0);
3306  color_adjust_.b = cfg["color_adjust_blue"].to_int(0);
3307 }
3308 
3310 {
3311  if (!reach_map_changed_) return;
3312  if (reach_map_.empty() != reach_map_old_.empty()) {
3313  // Invalidate everything except the non-darkened tiles
3314  reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
3315 
3316  for (const auto& hex : get_visible_hexes()) {
3317  reach_map::iterator reach = full.find(hex);
3318  if (reach == full.end()) {
3319  // Location needs to be darkened or brightened
3320  invalidate(hex);
3321  } else if (reach->second != 1) {
3322  // Number needs to be displayed or cleared
3323  invalidate(hex);
3324  }
3325  }
3326  } else if (!reach_map_.empty()) {
3327  // Invalidate only changes
3328  reach_map::iterator reach, reach_old;
3329  for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
3330  reach_old = reach_map_old_.find(reach->first);
3331  if (reach_old == reach_map_old_.end()) {
3332  invalidate(reach->first);
3333  } else {
3334  if (reach_old->second != reach->second) {
3335  invalidate(reach->first);
3336  }
3337  reach_map_old_.erase(reach_old);
3338  }
3339  }
3340  for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
3341  invalidate(reach_old->first);
3342  }
3343  }
3345  reach_map_changed_ = false;
3346 }
3347 
3348 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:66
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:81
arrows_map_t arrows_map_
Maps the list of arrows for each location.
Definition: display.hpp:1002
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:727
bool map_screenshot_
Used to indicate to drawing functions that we are doing a map screenshot.
Definition: display.hpp:926
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
static void add_submerge_ipf_mod(std::string &image_path, int image_height, double submersion_amount, int shift=0)
Definition: display.cpp:1504
int viewing_side() const
The 1-based equivalent of the 0-based viewing_team() function.
Definition: display.hpp:124
bool redraw_background_
Definition: display.hpp:742
void update_render_textures()
Ensure render textures are valid and correct.
Definition: display.cpp:2554
static unsigned int last_zoom_
The previous value of zoom_.
Definition: display.hpp:737
void write(config &cfg) const
Definition: display.cpp:3292
static bool zoom_at_min()
Definition: display.cpp:1882
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:774
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:116
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1652
void clear_fps_label()
Definition: display.cpp:1392
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1673
point get_location(const map_location &loc) const
Definition: display.cpp:717
unsigned int fps_counter_
Definition: display.hpp:754
bool invalidate_locations_in_rect(const SDL_Rect &rect)
invalidate all hexes under the rectangle rect (in screen coordinates)
Definition: display.cpp:3190
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:2473
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:2259
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3137
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:769
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:1636
bool view_locked_
Definition: display.hpp:728
drawing_layer
The layers to render something on.
Definition: display.hpp:802
@ LAYER_UNIT_FIRST
Reserve layers to be selected for WML.
Definition: display.hpp:811
@ LAYER_TERRAIN_BG
Layer for the terrain drawn behind the unit.
Definition: display.hpp:803
@ LAYER_UNIT_DEFAULT
default layer for drawing units
Definition: display.hpp:813
@ LAYER_FOG_SHROUD
Fog and shroud.
Definition: display.hpp:831
@ LAYER_UNIT_MOVE_DEFAULT
default layer for drawing moving units
Definition: display.hpp:822
@ LAYER_GRID_BOTTOM
Used for the bottom half part of grid image.
Definition: display.hpp:818
@ LAYER_GRID_TOP
Top half part of grid image.
Definition: display.hpp:807
@ LAYER_TERRAIN_FG
Layer for the terrain drawn in front of the unit.
Definition: display.hpp:814
@ LAYER_REACHMAP
"black stripes" on unreachable hexes.
Definition: display.hpp:829
double turbo_speed() const
Definition: display.cpp:2230
@ ONSCREEN
Definition: display.hpp:499
@ ONSCREEN_WARP
Definition: display.hpp:499
int current_frame_sample_
Definition: display.hpp:753
int invalidated_hexes_
Count work done for the debug info displayed under fps.
Definition: display.hpp:954
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:2315
void queue_repaint()
Queues repainting to the screen, but doesn't rerender.
Definition: display.cpp:2372
bool reach_map_changed_
Definition: display.hpp:943
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:258
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:261
const rect & unit_image_area() const
Definition: display.cpp:492
TERRAIN_TYPE
Definition: display.hpp:705
@ FOREGROUND
Definition: display.hpp:705
@ BACKGROUND
Definition: display.hpp:705
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1777
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:3158
void clear_redraw_observers()
Clear the redraw observers.
Definition: display.cpp:2384
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:307
const theme::action * action_pressed()
Definition: display.cpp:1604
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:2379
void set_prevent_draw(bool pd=true)
Prevent the game display from drawing.
Definition: display.cpp:2244
unsigned int fps_actual_
Definition: display.hpp:756
virtual overlay_map & get_overlays()=0
theme theme_
Definition: display.hpp:729
tod_color color_adjust_
Definition: display.hpp:1004
bool get_prevent_draw()
Definition: display.cpp:2253
virtual void layout() override
Finalize screen layout.
Definition: display.cpp:2442
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1554
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:1877
static display * singleton_
Definition: display.hpp:1009
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:2599
map_labels & labels()
Definition: display.cpp:2615
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:3268
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:962
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:974
@ __NUM_DEBUG_FLAGS
Dummy entry to size the bitmask.
Definition: display.hpp:977
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:968
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:971
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:965
void process_reachmap_changes()
Definition: display.cpp:3309
void draw_minimap_units()
Definition: display.cpp:1730
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:2067
void invalidate_animations_location(const map_location &loc)
Per-location invalidation called by invalidate_animations() Extra game per-location invalidation (vil...
Definition: display.cpp:3205
map_location mouseoverHex_
Definition: display.hpp:775
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:99
bool set_zoom(bool increase)
Zooms the display in (true) or out (false).
Definition: display.cpp:1887
std::map< std::string, rect > reportLocations_
Definition: display.hpp:760
const rect_of_hexes get_visible_hexes() const
Returns the rectangular area of visible hexes.
Definition: display.hpp:355
bool invalidateAll_
Definition: display.hpp:743
int drawn_hexes_
Definition: display.hpp:955
texture get_flag(const map_location &loc)
Definition: display.cpp:333
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3130
std::map< std::string, config > reports_
Definition: display.hpp:762
std::bitset< __NUM_DEBUG_FLAGS > debug_flags_
Currently set debug flags.
Definition: display.hpp:997
SDL_Rect minimap_location_
Definition: display.hpp:741
map_location get_middle_location() const
Definition: display.cpp:3286
void bounds_check_position()
Definition: display.cpp:2212
std::function< rect(rect)> minimap_renderer_
Definition: display.hpp:740
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:763
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:1985
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:551
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:682
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:1975
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:1582
bool tile_fully_on_screen(const map_location &loc) const
Check if a tile is fully visible on screen.
Definition: display.cpp:1968
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:2079
bool is_blindfolded() const
Definition: display.cpp:477
std::vector< texture > terrain_image_vector_
Definition: display.hpp:793
std::vector< std::string > fog_images_
Definition: display.hpp:771
int fps_handle_
Handle for the label which displays frames per second.
Definition: display.hpp:952
texture tod_hex_mask2
Definition: display.hpp:768
bool invalidate_visible_locations_in_rect(const SDL_Rect &rect)
Definition: display.cpp:3185
std::vector< std::function< void(display &)> > redraw_observers_
Definition: display.hpp:957
void read(const config &cfg)
Definition: display.cpp:3301
const theme::menu * menu_pressed()
Definition: display.cpp:1620
const std::unique_ptr< terrain_builder > builder_
Definition: display.hpp:739
std::vector< animated< image::locator > > flags_
Animated flags for each team.
Definition: display.hpp:789
static void fill_images_list(const std::string &prefix, std::vector< std::string > &images)
Definition: display.cpp:424
int ypos_
Definition: display.hpp:727
void draw_report(const std::string &report_name, bool test_run=false)
Draw the specified report.
Definition: display.cpp:2956
std::set< map_location > invalidated_
Definition: display.hpp:764
color_t fade_color_
Definition: display.hpp:562
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:2282
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2320
std::size_t activeTeam_
Definition: display.hpp:858
void create_buttons()
Definition: display.cpp:875
int blindfold_ctr_
Definition: display.hpp:672
std::string remove_exclusive_draw(const map_location &loc)
Cancels an exclusive draw request.
Definition: display.cpp:387
bool invalidateGameStatus_
Definition: display.hpp:745
reach_map reach_map_old_
Definition: display.hpp:942
virtual void draw_invalidated()
Only called when there's actual redrawing to do.
Definition: display.cpp:2630
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:735
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:755
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:252
void reset_standing_animations()
Definition: display.cpp:3254
bool animate_water_
Local version of preferences::animate_water, used to detect when it's changed.
Definition: display.hpp:782
bool debug_flag_set(DEBUG_FLAG flag) const
Definition: display.hpp:980
void toggle_default_zoom()
Sets the zoom amount to the default.
Definition: display.cpp:1956
virtual rect get_clip_rect() const
Get the clipping rectangle for drawing.
Definition: display.cpp:2625
CKey keys_
Definition: display.hpp:776
reports * reports_object_
Definition: display.hpp:747
bool draw_reports(const rect &region)
Draw all reports in the given region.
Definition: display.cpp:3116
void invalidate_animations()
Function to invalidate animated terrains and units which may have changed.
Definition: display.cpp:3216
virtual rect screen_location() override
Return the current draw location of the display, on the screen.
Definition: display.cpp:2546
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:2501
std::list< draw_helper > drawing_buffer_
Definition: display.hpp:899
uint8_t tod_hex_alpha2
Definition: display.hpp:770
std::vector< std::shared_ptr< gui::button > > menu_buttons_
Definition: display.hpp:763
virtual void update() override
Update animations and internal state.
Definition: display.cpp:2425
void draw()
Perform rendering of invalidated items.
Definition: display.cpp:2389
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:587
events::generic_event scroll_event_
Event raised when the map is being scrolled.
Definition: display.hpp:750
bool show_everything() const
Definition: display.hpp:97
virtual void draw_hex(const map_location &loc)
Redraws a single gamemap location.
Definition: display.cpp:2671
std::size_t currentTeam_
Definition: display.hpp:719
texture tod_hex_mask1
Definition: display.hpp:767
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:940
std::vector< std::tuple< int, int, int > > fps_history_
Definition: display.hpp:1006
halo::manager halo_man_
Definition: display.hpp:677
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:1678
texture front_
Render textures, for intermediate rendering.
Definition: display.hpp:586
void update_arrow(arrow &a)
Called by arrow objects when they change.
Definition: display.cpp:3275
void draw_label(const theme::label &label)
Definition: display.cpp:1427
const std::unique_ptr< fake_unit_manager > fake_unit_man_
Definition: display.hpp:738
const display_context * dc_
Definition: display.hpp:676
bool animate_map_
Local cache for preferences::animate_map, since it is constantly queried.
Definition: display.hpp:779
std::vector< std::string > shroud_images_
Definition: display.hpp:772
static unsigned int zoom_
The current zoom, in pixels (on screen) per 72 pixels (in the graphic assets), i.e....
Definition: display.hpp:734
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:720
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:2886
std::weak_ptr< wb::manager > wb_
Definition: display.hpp:678
void add_arrow(arrow &)
Definition: display.cpp:3261
const std::unique_ptr< map_labels > map_labels_
Definition: display.hpp:746
boost::circular_buffer< unsigned > frametimes_
Definition: display.hpp:752
uint32_t last_frame_finished_
Definition: display.hpp:757
void set_diagnostic(const std::string &msg)
Definition: display.cpp:1564
virtual void select_hex(map_location hex)
Definition: display.cpp:1546
int diagnostic_label_
Definition: display.hpp:744
const std::vector< team > & get_teams() const
Definition: display.hpp:101
std::map< std::string, texture > reportSurfaces_
Definition: display.hpp:761
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:941
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:63
@ TYPE_TURBO
Definition: button.hpp:60
@ TYPE_PRESS
Definition: button.hpp:60
@ TYPE_IMAGE
Definition: button.hpp:60
@ TYPE_CHECK
Definition: button.hpp:60
@ TYPE_RADIO
Definition: button.hpp:60
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:1801
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 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:616
clip_setter override_clip(const SDL_Rect &clip)
Override the clipping area.
Definition: draw.cpp:452
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:457
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:477
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:150
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:870
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:853
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:771
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:959
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:813
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:595
light_string get_light_string(int op, int r, int g, int b)
Returns the light_string for one light operation.
Definition: picture.cpp:497
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:827
unsigned int tile_size()
Definition: general.cpp:665
bool scroll_to_action()
Definition: general.cpp:382
bool turbo()
Definition: general.cpp:465
double turbo_speed()
Definition: general.cpp:479
bool animate_water()
Definition: general.cpp:822
int draw_delay()
Definition: general.cpp:897
void set_tile_size(const unsigned int size)
Definition: general.cpp:670
bool minimap_draw_units()
Definition: general.cpp:847
bool grid()
Definition: general.cpp:565
bool animate_map()
Definition: general.cpp:817
bool show_fps()
Definition: general.cpp:887
const config & options()
Definition: game.cpp:552
int scroll_speed()
Definition: general.cpp:787
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.
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:605
Helper for rendering the map by ordering draw operations.
Definition: display.hpp:883
std::function< void(const rect &)> do_draw
Handles the actual drawing at this location.
Definition: display.hpp:888
very simple iterator to walk into the rect_of_hexes
Definition: display.hpp:325
iterator & operator++()
increment y first, then when reaching bottom, increment x
Definition: display.cpp:623
const rect_of_hexes & rect_
Definition: display.hpp:343
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:318
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