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