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