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