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