The Battle for Wesnoth  1.19.18+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  return {
671  // Round hex multiplication to ensure consistent spacing at fractional zoom levels.
672  map_area().x - viewport_origin_.x + static_cast<int>(std::round((loc.x + theme_.border().size) * hex_width())),
673  map_area().y - viewport_origin_.y + static_cast<int>(std::round((loc.y + theme_.border().size) * zoom_ + (is_odd(loc.x) ? zoom_ / 2.0 : 0)))
674  };
675 }
676 
678 {
679  // TODO: evaluate how these functions should be defined in terms of each other
680  return { get_location(loc), point{hex_size(), hex_size()} };
681 }
682 
684 {
685  // TODO: don't return location for this,
686  // instead directly scroll to the clicked pixel position
687 
688  if(!minimap_area().contains(x, y)) {
689  return map_location();
690  }
691 
692  // we transform the coordinates from minimap to the full map image
693  // probably more adjustments to do (border, minimap shift...)
694  // but the mouse and human capacity to evaluate the rectangle center
695  // is not pixel precise.
696  int px = (x - minimap_location_.x) * context().map().w() * hex_width() / std::max(minimap_location_.w, 1);
697  int py = (y - minimap_location_.y) * context().map().h() * hex_size() / std::max(minimap_location_.h, 1);
698 
700  if(loc.x < 0) {
701  loc.x = 0;
702  } else if(loc.x >= context().map().w()) {
703  loc.x = context().map().w() - 1;
704  }
705 
706  if(loc.y < 0) {
707  loc.y = 0;
708  } else if(loc.y >= context().map().h()) {
709  loc.y = context().map().h() - 1;
710  }
711 
712  return loc;
713 }
714 
715 surface display::screenshot(bool map_screenshot)
716 {
717  if (!map_screenshot) {
718  LOG_DP << "taking ordinary screenshot";
719  return video::read_pixels();
720  }
721 
722  if (context().map().empty()) {
723  ERR_DP << "No map loaded, cannot create a map screenshot.";
724  return nullptr;
725  }
726 
727  // back up the current map view position and move to top-left
728  point old_pos = viewport_origin_;
729  viewport_origin_ = {0, 0};
730 
731  // Reroute render output to a separate texture until the end of scope.
732  rect area = max_map_area();
733  if (area.w > 1 << 16 || area.h > 1 << 16) {
734  WRN_DP << "Excessively large map screenshot area";
735  }
736  LOG_DP << "creating " << area.w << " by " << area.h
737  << " texture for map screenshot";
738  texture output_texture(area.w, area.h, SDL_TEXTUREACCESS_TARGET);
739  auto target_setter = draw::set_render_target(output_texture);
740  auto clipper = draw::override_clip(area);
741 
742  map_screenshot_ = true;
743 
744  DBG_DP << "invalidating region for map screenshot";
746 
747  DBG_DP << "drawing map screenshot";
748  draw();
749 
750  map_screenshot_ = false;
751 
752  // Restore map viewport position
753  viewport_origin_ = old_pos;
754 
755  // Read rendered pixels back as an SDL surface.
756  LOG_DP << "reading pixels for map screenshot";
757  return video::read_pixels();
758 }
759 
760 std::shared_ptr<gui::button> display::find_action_button(const std::string& id)
761 {
762  for(auto& b : action_buttons_) {
763  if(b->id() == id) {
764  return b;
765  }
766  }
767  return nullptr;
768 }
769 
770 std::shared_ptr<gui::button> display::find_menu_button(const std::string& id)
771 {
772  for(auto& b : menu_buttons_) {
773  if(b->id() == id) {
774  return b;
775  }
776  }
777  return nullptr;
778 }
779 
781 {
782  DBG_DP << "positioning menu buttons...";
783  for(const auto& menu : theme_.menus()) {
784  if(auto b = find_menu_button(menu.get_id())) {
785  const rect& loc = menu.location(video::game_canvas());
786  b->set_location(loc);
787  b->set_measurements(0,0);
788  b->set_label(menu.title());
789  b->set_image(menu.image());
790  }
791  }
792 
793  DBG_DP << "positioning action buttons...";
794  for(const auto& action : theme_.actions()) {
795  if(auto b = find_action_button(action.get_id())) {
796  const rect& loc = action.location(video::game_canvas());
797  b->set_location(loc);
798  b->set_measurements(0,0);
799  b->set_label(action.title());
800  b->set_image(action.image());
801  }
802  }
803 }
804 
805 namespace
806 {
807 gui::button::TYPE string_to_button_type(const std::string& type)
808 {
809  if(type == "checkbox") {
811  } else if(type == "image") {
813  } else if(type == "radiobox") {
815  } else if(type == "turbo") {
817  } else {
819  }
820 }
821 } // namespace
822 
824 {
825  if(video::headless()) {
826  return;
827  }
828 
829  // Keep the old buttons around until we're done so we can check the previous state.
830  std::vector<std::shared_ptr<gui::button>> menu_work;
831  std::vector<std::shared_ptr<gui::button>> action_work;
832 
833  DBG_DP << "creating menu buttons...";
834  for(const auto& menu : theme_.menus()) {
835  if(!menu.is_button()) {
836  continue;
837  }
838 
839  auto b = std::make_shared<gui::button>(menu.title(), gui::button::TYPE_PRESS, menu.image(),
840  gui::button::DEFAULT_SPACE, true, menu.overlay(), font::SIZE_BUTTON_SMALL);
841 
842  DBG_DP << "drawing button " << menu.get_id();
843  b->set_id(menu.get_id());
844  if(!menu.tooltip().empty()) {
845  b->set_tooltip_string(menu.tooltip());
846  }
847 
848  if(auto b_prev = find_menu_button(b->id())) {
849  b->enable(b_prev->enabled());
850  }
851 
852  menu_work.push_back(std::move(b));
853  }
854 
855  DBG_DP << "creating action buttons...";
856  for(const auto& action : theme_.actions()) {
857  auto b = std::make_shared<gui::button>(action.title(), string_to_button_type(action.type()),
858  action.image(), gui::button::DEFAULT_SPACE, true, action.overlay(), font::SIZE_BUTTON_SMALL);
859 
860  DBG_DP << "drawing button " << action.get_id();
861  b->set_id(action.get_id());
862  if(!action.tooltip(0).empty()) {
863  b->set_tooltip_string(action.tooltip(0));
864  }
865 
866  if(auto b_prev = find_action_button(b->id())) {
867  b->enable(b_prev->enabled());
868  if(b_prev->get_type() == gui::button::TYPE_CHECK) {
869  b->set_check(b_prev->checked());
870  }
871  }
872 
873  action_work.push_back(std::move(b));
874  }
875 
876  menu_buttons_ = std::move(menu_work);
877  action_buttons_ = std::move(action_work);
878 
879  if (prevent_draw_) {
880  // buttons start hidden in this case
881  hide_buttons();
882  }
883 
884  layout_buttons();
885  DBG_DP << "buttons created";
886 }
887 
889 {
890  // This is currently unnecessary because every GUI1 widget is a TLD.
891  // They will draw themselves. Keeping code in case this changes.
892  return;
893 
894  //const rect clip = draw::get_clip();
895  //for(auto& btn : menu_buttons_) {
896  // if(clip.overlaps(btn->location())) {
897  // btn->set_dirty(true);
898  // btn->draw();
899  // }
900  //}
901 
902  //for(auto& btn : action_buttons_) {
903  // if(clip.overlaps(btn->location())) {
904  // btn->set_dirty(true);
905  // btn->draw();
906  // }
907  //}
908 }
909 
911 {
912  for (auto& button : menu_buttons_) {
913  button->hide();
914  }
915  for (auto& button : action_buttons_) {
916  button->hide();
917  }
918 }
919 
921 {
922  for (auto& button : menu_buttons_) {
923  button->hide(false);
924  }
925  for (auto& button : action_buttons_) {
926  button->hide(false);
927  }
928 }
929 
930 std::vector<texture> display::get_fog_shroud_images(const map_location& loc, image::TYPE image_type)
931 {
932  std::vector<std::string> names;
933  const auto adjacent = get_adjacent_tiles(loc);
934 
935  enum visibility { FOG = 0, SHROUD = 1, CLEAR = 2 };
936  std::array<visibility, 6> tiles;
937 
938  const std::array image_prefix{&game_config::fog_prefix, &game_config::shroud_prefix};
939 
940  for(int i = 0; i < 6; ++i) {
941  if(shrouded(adjacent[i])) {
942  tiles[i] = SHROUD;
943  } else if(!fogged(loc) && fogged(adjacent[i])) {
944  tiles[i] = FOG;
945  } else {
946  tiles[i] = CLEAR;
947  }
948  }
949 
950  for(int v = FOG; v != CLEAR; ++v) {
951  // Find somewhere that doesn't have overlap to use as a starting point
952  int start{0};
953  while(start < 6 && tiles[start] == v) {
954  ++start;
955  }
956 
957  if(start == 6) {
958  // Completely surrounded by fog or shroud. This might have
959  // a special graphic.
960  const std::string name = *image_prefix[v] + "-all.png";
961  if(image::exists(name)) {
962  names.push_back(name);
963  // Proceed to the next visibility (fog -> shroud -> clear).
964  continue;
965  }
966  // No special graphic found. We'll just combine some other images
967  // and hope it works out.
968  start = 0;
969  }
970 
971  // Find all the directions overlap occurs from
972  for(int i = (start + 1) % 6, cap1 = 0; i != start && cap1 != 6; ++cap1) {
973  if(tiles[i] == v) {
974  std::ostringstream stream;
975  std::string name;
976  stream << *image_prefix[v];
977 
978  for(int cap2 = 0; v == tiles[i] && cap2 != 6; i = (i + 1) % 6, ++cap2) {
980 
981  if(!image::exists(stream.str() + ".png")) {
982  // If we don't have any surface at all,
983  // then move onto the next overlapped area
984  if(name.empty()) {
985  i = (i + 1) % 6;
986  }
987  break;
988  } else {
989  name = stream.str();
990  }
991  }
992 
993  if(!name.empty()) {
994  names.push_back(name + ".png");
995  }
996  } else {
997  i = (i + 1) % 6;
998  }
999  }
1000  }
1001 
1002  // now get the textures
1003  std::vector<texture> res;
1004 
1005  for(const std::string& name : names) {
1006  if(texture tex = image::get_texture(name, image_type)) {
1007  res.push_back(std::move(tex));
1008  }
1009  }
1010 
1011  return res;
1012 }
1013 
1015 {
1016  terrain_image_vector_.clear();
1017 
1018  std::vector<image::light_adjust> lighting;
1019  const time_of_day& tod = get_time_of_day(loc);
1020 
1021  // get all the light transitions
1022  const auto adjs = get_adjacent_tiles(loc);
1023  std::array<const time_of_day*, adjs.size()> atods;
1024 
1025  for(std::size_t d = 0; d < adjs.size(); ++d) {
1026  atods[d] = &get_time_of_day(adjs[d]);
1027  }
1028 
1029  for(int d = 0; d < 6; ++d) {
1030  /*
1031  concave
1032  _____
1033  / \
1034  / atod1 \_____
1035  \ !tod / \
1036  \_____/ atod2 \
1037  / \__\ !tod /
1038  / \_____/
1039  \ tod /
1040  \_____/
1041  */
1042 
1043  const time_of_day& atod1 = *atods[d];
1044  const time_of_day& atod2 = *atods[(d + 1) % 6];
1045 
1046  if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color) {
1047  continue;
1048  }
1049 
1050  if(lighting.empty()) {
1051  // color the full hex before adding transitions
1052  tod_color col = tod.color + color_adjust_;
1053  lighting.emplace_back(0, col.r, col.g, col.b);
1054  }
1055 
1056  // add the directional transitions
1057  tod_color acol = atod1.color + color_adjust_;
1058  lighting.emplace_back(d + 1, acol.r, acol.g, acol.b);
1059  }
1060 
1061  for(int d = 0; d < 6; ++d) {
1062  /*
1063  convex 1
1064  _____
1065  / \
1066  / atod1 \_____
1067  \ !tod / \
1068  \_____/ atod2 \
1069  / \__\ tod /
1070  / \_____/
1071  \ tod /
1072  \_____/
1073  */
1074 
1075  const time_of_day& atod1 = *atods[d];
1076  const time_of_day& atod2 = *atods[(d + 1) % 6];
1077 
1078  if(atod1.color == tod.color || atod1.color == atod2.color) {
1079  continue;
1080  }
1081 
1082  if(lighting.empty()) {
1083  // color the full hex before adding transitions
1084  tod_color col = tod.color + color_adjust_;
1085  lighting.emplace_back(0, col.r, col.g, col.b);
1086  }
1087 
1088  // add the directional transitions
1089  tod_color acol = atod1.color + color_adjust_;
1090  lighting.emplace_back(d + 7, acol.r, acol.g, acol.b);
1091  }
1092 
1093  for(int d = 0; d < 6; ++d) {
1094  /*
1095  convex 2
1096  _____
1097  / \
1098  / atod1 \_____
1099  \ tod / \
1100  \_____/ atod2 \
1101  / \__\ !tod /
1102  / \_____/
1103  \ tod /
1104  \_____/
1105  */
1106 
1107  const time_of_day& atod1 = *atods[d];
1108  const time_of_day& atod2 = *atods[(d + 1) % 6];
1109 
1110  if(atod2.color == tod.color || atod1.color == atod2.color) {
1111  continue;
1112  }
1113 
1114  if(lighting.empty()) {
1115  // color the full hex before adding transitions
1116  tod_color col = tod.color + color_adjust_;
1117  lighting.emplace_back(0, col.r, col.g, col.b);
1118  }
1119 
1120  // add the directional transitions
1121  tod_color acol = atod2.color + color_adjust_;
1122  lighting.emplace_back(d + 13, acol.r, acol.g, acol.b);
1123  }
1124 
1125  if(lighting.empty()){
1126  tod_color col = tod.color + color_adjust_;
1127  if(!col.is_zero()){
1128  // no real lightmap needed but still color the hex
1129  lighting.emplace_back(-1, col.r, col.g, col.b);
1130  }
1131  }
1132 
1133  const terrain_builder::TERRAIN_TYPE builder_terrain_type = terrain_type == FOREGROUND
1136 
1137  if(const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc, timeid, builder_terrain_type)) {
1138  // Cache the offmap name. Since it is themeable it can change, so don't make it static.
1139  const std::string off_map_name = "terrain/" + theme_.border().tile_image;
1140  for(const auto& terrain : *terrains) {
1141  const image::locator& image = animate_map_ ? terrain.get_current_frame() : terrain.get_first_frame();
1142 
1143  // We prevent ToD coloring and brightening of off-map tiles,
1144  // We need to test for the tile to be rendered and
1145  // not the location, since the transitions are rendered
1146  // over the offmap-terrain and these need a ToD coloring.
1147  texture tex;
1148  const bool off_map = (image.get_filename() == off_map_name
1149  || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos);
1150 
1151  if(off_map) {
1153  } else if(lighting.empty()) {
1155  } else {
1156  tex = image::get_lighted_texture(image, lighting);
1157  }
1158 
1159  if(tex) {
1160  terrain_image_vector_.push_back(std::move(tex));
1161  }
1162  }
1163  }
1164 }
1165 
1166 namespace
1167 {
1168 constexpr std::array layer_groups {
1172 };
1173 
1174 enum {
1175  // you may adjust the following when needed:
1176 
1177  // maximum border. 3 should be safe even if a larger border is in use somewhere
1178  MAX_BORDER = 3,
1179 
1180  // store x, y, and layer in one 32 bit integer
1181  // 4 most significant bits == layer group => 16
1182  BITS_FOR_LAYER_GROUP = 4,
1183 
1184  // 10 second most significant bits == y => 1024
1185  BITS_FOR_Y = 10,
1186 
1187  // 1 third most significant bit == x parity => 2
1188  BITS_FOR_X_PARITY = 1,
1189 
1190  // 8 fourth most significant bits == layer => 256
1191  BITS_FOR_LAYER = 8,
1192 
1193  // 9 least significant bits == x / 2 => 512 (really 1024 for x)
1194  BITS_FOR_X_OVER_2 = 9,
1195 
1196  SHIFT_LAYER = BITS_FOR_X_OVER_2,
1197 
1198  SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER,
1199 
1200  SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY,
1201 
1202  SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y
1203 };
1204 
1205 uint32_t generate_hex_key(const drawing_layer layer, const map_location& loc)
1206 {
1207  // Start with the index of last group entry...
1208  uint32_t group_i = layer_groups.size() - 1;
1209 
1210  // ...and works backwards until the group containing the specified layer is found.
1211  while(layer < layer_groups[group_i]) {
1212  --group_i;
1213  }
1214 
1215  // the parity of x must be more significant than the layer but less significant than y.
1216  // Thus basically every row is split in two: First the row containing all the odd x
1217  // then the row containing all the even x. Since thus the least significant bit of x is
1218  // not required for x ordering anymore it can be shifted out to the right.
1219  const uint32_t x_parity = static_cast<uint32_t>(loc.x) & 1;
1220 
1221  uint32_t key = 0;
1222  static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key) * 8, "Bit field too small");
1223 
1224  key = (group_i << SHIFT_LAYER_GROUP) | (static_cast<uint32_t>(loc.y + MAX_BORDER) << SHIFT_Y);
1225  key |= (x_parity << SHIFT_X_PARITY);
1226  key |= (static_cast<uint32_t>(layer) << SHIFT_LAYER) | static_cast<uint32_t>(loc.x + MAX_BORDER) / 2;
1227 
1228  return key;
1229 }
1230 } // namespace
1231 
1233 {
1234  drawing_buffer_.AGGREGATE_EMPLACE(generate_hex_key(layer, loc), std::move(draw_func), get_location_rect(loc));
1235 }
1236 
1238 {
1239  DBG_DP << "committing drawing buffer"
1240  << " with " << drawing_buffer_.size() << " items";
1241 
1242  // std::list::sort() is a stable sort
1243  drawing_buffer_.sort();
1244 
1245  const auto clipper = draw::reduce_clip(map_area());
1246 
1247  /*
1248  * Info regarding the rendering algorithm.
1249  *
1250  * In order to render a hex properly it needs to be rendered per row. On
1251  * this row several layers need to be drawn at the same time. Mainly the
1252  * unit and the background terrain. This is needed since both can spill
1253  * in the next hex. The foreground terrain needs to be drawn before to
1254  * avoid decapitation a unit.
1255  *
1256  * This ended in the following priority order:
1257  * layergroup > location > layer > 'draw_helper' > surface
1258  */
1259  for(const draw_helper& helper : drawing_buffer_) {
1260  std::invoke(helper.do_draw, helper.dest);
1261  }
1262 
1263  drawing_buffer_.clear();
1264 }
1265 
1267 {
1268  // Most panels are transparent.
1269  if (panel.image().empty()) {
1270  return;
1271  }
1272 
1273  const rect& loc = panel.location(video::game_canvas());
1274 
1275  if (!loc.overlaps(draw::get_clip())) {
1276  return;
1277  }
1278 
1279  DBG_DP << "drawing panel " << panel.get_id() << ' ' << loc;
1280 
1281  texture tex(image::get_texture(panel.image()));
1282  if (!tex) {
1283  ERR_DP << "failed to load panel " << panel.get_id()
1284  << " texture: " << panel.image();
1285  return;
1286  }
1287 
1288  draw::tiled(tex, loc);
1289 }
1290 
1292 {
1293  const rect& loc = label.location(video::game_canvas());
1294 
1295  if (!loc.overlaps(draw::get_clip())) {
1296  return;
1297  }
1298 
1299  const std::string& text = label.text();
1300  const color_t text_color = label.font_rgb_set() ? label.font_rgb() : font::NORMAL_COLOR;
1301  const std::string& icon = label.icon();
1302 
1303  DBG_DP << "drawing label " << label.get_id() << ' ' << loc;
1304 
1305  if(icon.empty() == false) {
1307 
1308  if(text.empty() == false) {
1309  tooltips::add_tooltip(loc,text);
1310  }
1311  } else if(text.empty() == false) {
1313  renderer.set_text(text, false);
1314  renderer.set_family_class(font::family_class::sans_serif);
1315  renderer.set_font_size(label.font_size());
1316  renderer.set_font_style(font::pango_text::STYLE_NORMAL);
1317  renderer.set_foreground_color(text_color);
1318  renderer.set_ellipse_mode(PANGO_ELLIPSIZE_END);
1319  renderer.set_maximum_width(loc.w);
1320  renderer.set_maximum_height(loc.h, true);
1321 
1322  auto t = renderer.render_and_get_texture();
1323  draw::blit(t, rect{ loc.origin(), t.draw_size() });
1324  }
1325 }
1326 
1327 bool display::draw_all_panels(const rect& region)
1328 {
1329  bool drew = false;
1331 
1332  for(const auto& panel : theme_.panels()) {
1333  if(region.overlaps(panel.location(game_canvas))) {
1334  draw_panel(panel);
1335  drew = true;
1336  }
1337  }
1338 
1339  for(const auto& label : theme_.labels()) {
1340  if(region.overlaps(label.location(game_canvas))) {
1341  draw_label(label);
1342  drew = true;
1343  }
1344  }
1345 
1346  return drew;
1347 }
1348 
1350  const drawing_layer layer,
1351  const std::string& text,
1352  std::size_t font_size,
1353  color_t color,
1354  double x_in_hex,
1355  double y_in_hex)
1356 {
1357  if (text.empty()) return;
1358 
1360  renderer.set_text(text, false);
1361  renderer.set_font_size(font_size * get_zoom_factor());
1362  renderer.set_maximum_width(-1);
1363  renderer.set_maximum_height(-1, false);
1364  renderer.set_foreground_color(color);
1365  renderer.set_add_outline(true);
1366 
1367  drawing_buffer_add(layer, loc, [x_in_hex, y_in_hex, tex = renderer.render_and_get_texture()](const rect& dest) {
1368  draw::blit(tex, rect{ dest.point_at(x_in_hex, y_in_hex) - tex.draw_size() / 2, tex.draw_size() });
1369  });
1370 }
1371 
1373 {
1375  selectedHex_ = hex;
1378 }
1379 
1381 {
1382  if(mouseoverHex_ == hex) {
1383  return;
1384  }
1386  mouseoverHex_ = hex;
1388 }
1389 
1390 void display::set_diagnostic(const std::string& msg)
1391 {
1392  if(diagnostic_label_ != 0) {
1394  diagnostic_label_ = 0;
1395  }
1396 
1397  if(!msg.empty()) {
1398  font::floating_label flabel(msg);
1400  flabel.set_color(font::YELLOW_COLOR);
1401  flabel.set_position(300, 50);
1402  flabel.set_clip_rect(map_outside_area());
1403 
1405  }
1406 }
1407 
1409 {
1410  for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) {
1411  if((*i)->pressed()) {
1412  const std::size_t index = std::distance(action_buttons_.begin(), i);
1413  if(index >= theme_.actions().size()) {
1414  assert(false);
1415  return nullptr;
1416  }
1417  return &theme_.actions()[index];
1418  }
1419  }
1420 
1421  return nullptr;
1422 }
1423 
1425 {
1426  for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) {
1427  if((*i)->pressed()) {
1428  const std::size_t index = std::distance(menu_buttons_.begin(), i);
1429  if(index >= theme_.menus().size()) {
1430  assert(false);
1431  return nullptr;
1432  }
1433  return theme_.get_menu_item((*i)->id());
1434  }
1435  }
1436 
1437  return nullptr;
1438 }
1439 
1440 void display::announce(const std::string& message, const color_t& color, const announce_options& options)
1441 {
1442  if(options.discard_previous) {
1443  font::remove_floating_label(prevLabel);
1444  }
1445  font::floating_label flabel(message);
1447  flabel.set_color(color);
1448  flabel.set_position(
1450  flabel.set_lifetime(options.lifetime);
1451  flabel.set_clip_rect(map_outside_area());
1452 
1453  prevLabel = font::add_floating_label(flabel);
1454 }
1455 
1457 {
1458  if(video::headless()) {
1459  return;
1460  }
1461 
1462  const rect& area = minimap_area();
1463  if(area.empty()){
1464  return;
1465  }
1466 
1468  context().map(),
1469  context().teams().empty() ? nullptr : &viewing_team(),
1470  nullptr,
1471  (selectedHex_.valid() && !is_blindfolded()) ? &reach_map_ : nullptr
1472  );
1473 
1474  redraw_minimap();
1475 }
1476 
1478 {
1480 }
1481 
1483 {
1484  const rect& area = minimap_area();
1485 
1486  if(area.empty() || !area.overlaps(draw::get_clip())) {
1487  return;
1488  }
1489 
1490  if(!minimap_renderer_) {
1491  return;
1492  }
1493 
1494  const auto clipper = draw::reduce_clip(area);
1495 
1496  // Draw the minimap background.
1497  draw::fill(area, 31, 31, 23);
1498 
1499  // Draw the minimap and update its location for mouse and units functions
1500  minimap_location_ = std::invoke(minimap_renderer_, area);
1501 
1503 
1504  // calculate the visible portion of the map:
1505  // scaling between minimap and full map images
1506  double xscaling = 1.0 * minimap_location_.w / (context().map().w() * hex_width());
1507  double yscaling = 1.0 * minimap_location_.h / (context().map().h() * hex_size());
1508 
1509  // we need to shift with the border size
1510  // and the 0.25 from the minimap balanced drawing
1511  // and the possible difference between real map and outside off-map
1512  rect map_rect = map_area();
1513  rect map_out_rect = map_outside_area();
1514  double border = theme_.border().size;
1515  double shift_x = -border * hex_width() - (map_out_rect.w - map_rect.w) / 2;
1516  double shift_y = -(border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2;
1517 
1518  int view_x = static_cast<int>((viewport_origin_.x + shift_x) * xscaling);
1519  int view_y = static_cast<int>((viewport_origin_.y + shift_y) * yscaling);
1520  int view_w = static_cast<int>(map_out_rect.w * xscaling);
1521  int view_h = static_cast<int>(map_out_rect.h * yscaling);
1522 
1523  rect outline_rect {
1524  minimap_location_.x + view_x - 1,
1525  minimap_location_.y + view_y - 1,
1526  view_w + 2,
1527  view_h + 2
1528  };
1529 
1530  draw::rect(outline_rect, 255, 255, 255);
1531 }
1532 
1534 {
1535  if (!prefs::get().minimap_draw_units() || is_blindfolded()) return;
1536 
1537  double xscaling = 1.0 * minimap_location_.w / context().map().w();
1538  double yscaling = 1.0 * minimap_location_.h / context().map().h();
1539 
1540  for(const auto& u : context().units()) {
1541  if (fogged(u.get_location()) ||
1542  (viewing_team().is_enemy(u.side()) &&
1543  u.invisible(u.get_location())) ||
1544  u.get_hidden()) {
1545  continue;
1546  }
1547 
1548  int side = u.side();
1549  color_t col = team::get_minimap_color(side);
1550 
1551  if(!prefs::get().minimap_movement_coding()) {
1552  auto status = orb_status::allied;
1553  if(viewing_team().is_enemy(side)) {
1554  status = orb_status::enemy;
1555  } else if(viewing_team().side() == side) {
1556  status = context().unit_orb_status(u);
1557  } else {
1558  // no-op, status is already set to orb_status::allied;
1559  }
1561  }
1562 
1563  double u_x = u.get_location().x * xscaling;
1564  double u_y = (u.get_location().y + (is_odd(u.get_location().x) ? 1 : -1)/4.0) * yscaling;
1565  // use 4/3 to compensate the horizontal hexes imbrication
1566  double u_w = 4.0 / 3.0 * xscaling;
1567  double u_h = yscaling;
1568 
1569  rect r {
1570  minimap_location_.x + int(std::round(u_x))
1571  , minimap_location_.y + int(std::round(u_y))
1572  , int(std::round(u_w))
1573  , int(std::round(u_h))
1574  };
1575 
1576  draw::fill(r, col.r, col.g, col.b, col.a);
1577  }
1578 }
1579 
1580 bool display::scroll(const point& amount, bool force)
1581 {
1582  if(view_locked_ && !force) {
1583  return false;
1584  }
1585 
1586  // No move offset, do nothing.
1587  if(amount == point{}) {
1588  return false;
1589  }
1590 
1591  point new_pos = viewport_origin_ + amount;
1592  bounds_check_position(new_pos.x, new_pos.y);
1593 
1594  // Camera position doesn't change, exit.
1595  if(viewport_origin_ == new_pos) {
1596  return false;
1597  }
1598 
1599  point diff = viewport_origin_ - new_pos;
1600  viewport_origin_ = new_pos;
1601 
1602  /* Adjust floating label positions. This only affects labels whose position is anchored
1603  * to the map instead of the screen. In order to do that, we want to adjust their drawing
1604  * coordinates in the opposite direction of the screen scroll.
1605  *
1606  * The check a few lines up prevents any scrolling from happening if the camera position
1607  * doesn't change. Without that, the label still scroll even when the map edge is reached.
1608  * If that's removed, the following formula should work instead:
1609  *
1610  * const int label_[x,y]_adjust = [x,y]pos_ - new_[x,y];
1611  */
1612  font::scroll_floating_labels(diff.x, diff.y);
1613 
1615 
1616  //
1617  // NOTE: the next three blocks can be removed once we switch to accelerated rendering.
1618  //
1619 
1620  if(!video::headless()) {
1621  rect dst = map_area();
1622  dst.shift(diff);
1623  dst.clip(map_area());
1624 
1625  rect src = dst;
1626  src.shift(-diff);
1627 
1628  // swap buffers
1630 
1631  // Set the source region to blit from
1632  back_.set_src(src);
1633 
1634  // copy from the back to the front buffer
1635  auto rts = draw::set_render_target(front_);
1636  draw::blit(back_, dst);
1637 
1638  back_.clear_src();
1639 
1640  // queue repaint
1642  }
1643 
1644  if(diff.y != 0) {
1645  rect r = map_area();
1646 
1647  if(diff.y < 0) {
1648  r.y = r.y + r.h + diff.y;
1649  }
1650 
1651  r.h = std::abs(diff.y);
1653  }
1654 
1655  if(diff.x != 0) {
1656  rect r = map_area();
1657 
1658  if(diff.x < 0) {
1659  r.x = r.x + r.w + diff.x;
1660  }
1661 
1662  r.w = std::abs(diff.x);
1664  }
1665 
1667 
1668  redraw_minimap();
1669 
1670  return true;
1671 }
1672 
1674 {
1675  return zoom_ == MaxZoom;
1676 }
1677 
1679 {
1680  return zoom_ == MinZoom;
1681 }
1682 
1683 bool display::set_zoom(bool increase)
1684 {
1685  // Ensure we don't try to access nonexistent vector indices.
1686  zoom_index_ = std::clamp(increase ? zoom_index_ + 1 : zoom_index_ - 1, 0, final_zoom_index);
1687 
1688  // No validation check is needed in the next step since we've already set the index here and
1689  // know the new zoom value is indeed valid.
1690  return set_zoom(zoom_levels[zoom_index_], false);
1691 }
1692 
1693 bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_index)
1694 {
1695  unsigned int new_zoom = std::clamp(amount, MinZoom, MaxZoom);
1696 
1697  LOG_DP << "new_zoom = " << new_zoom;
1698 
1699  if(new_zoom == zoom_) {
1700  return false;
1701  }
1702 
1703  if(validate_value_and_set_index) {
1704  zoom_index_ = get_zoom_levels_index (new_zoom);
1705  new_zoom = zoom_levels[zoom_index_];
1706  }
1707 
1708  if((new_zoom / 4) * 4 != new_zoom) {
1709  WRN_DP << "set_zoom forcing zoom " << new_zoom
1710  << " which is not a multiple of 4."
1711  << " This will likely cause graphical glitches.";
1712  }
1713 
1715  const rect area = map_area();
1716 
1717  // Turn the zoom factor to a double in order to avoid rounding errors.
1718  double zoom_factor = static_cast<double>(new_zoom) / static_cast<double>(zoom_);
1719 
1720  // INVARIANT: xpos_ + area.w == xend where xend is as in bounds_check_position()
1721  //
1722  // xpos_: Position of the leftmost visible map pixel of the viewport, in pixels.
1723  // Affected by the current zoom: this->zoom_ pixels to the hex.
1724  //
1725  // xpos_ + area.w/2: Position of the center of the viewport, in pixels.
1726  //
1727  // (xpos_ + area.w/2) * new_zoom/zoom_: Position of the center of the
1728  // viewport, as it would be under new_zoom.
1729  //
1730  // (xpos_ + area.w/2) * new_zoom/zoom_ - area.w/2: Position of the
1731  // leftmost visible map pixel, as it would be under new_zoom.
1732  viewport_origin_.x = std::round(((viewport_origin_.x + area.w / 2) * zoom_factor) - (area.w / 2));
1733  viewport_origin_.y = std::round(((viewport_origin_.y + area.h / 2) * zoom_factor) - (area.h / 2));
1734  viewport_origin_ -= (outside_area.size() - area.size()) / 2;
1735 
1736  zoom_ = new_zoom;
1738  if(zoom_ != DefaultZoom) {
1739  last_zoom_ = zoom_;
1740  }
1741 
1742  prefs::get().set_tile_size(zoom_);
1743 
1745  redraw_background_ = true;
1746  invalidate_all();
1747 
1748  return true;
1749 }
1750 
1752 {
1753  if (zoom_ != DefaultZoom) {
1754  last_zoom_ = zoom_;
1756  } else {
1757  // When we are already at the default zoom,
1758  // switch to the last zoom used
1760  }
1761 }
1762 
1764 {
1766 }
1767 
1769 {
1770  const auto [x, y] = get_location(loc);
1771  const rect area = map_area();
1772  int hw = hex_width(), hs = hex_size();
1773  return x + hs >= area.x - hw && x < area.x + area.w + hw &&
1774  y + hs >= area.y - hs && y < area.y + area.h + hs;
1775 }
1776 
1777 void display::scroll_to_xy(const point& screen_coordinates, SCROLL_TYPE scroll_type, bool force)
1778 {
1779  if(!force && (view_locked_ || !prefs::get().scroll_to_action())) return;
1780  if(video::headless()) {
1781  return;
1782  }
1783 
1784  point expected_move = screen_coordinates - map_area().center();
1785 
1786  point new_pos = viewport_origin_ + expected_move;
1787  bounds_check_position(new_pos.x, new_pos.y);
1788 
1789  point move = new_pos - viewport_origin_;
1790 
1791  if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || prefs::get().scroll_speed() > 99) {
1792  scroll(move, true);
1793  redraw_minimap();
1794  events::draw();
1795  return;
1796  }
1797 
1798  // Doing an animated scroll, with acceleration etc.
1799 
1800  point prev_pos;
1801  const double dist_total = std::hypot(move.x, move.y);
1802  double dist_moved = 0.0;
1803 
1804  using fractional_seconds = std::chrono::duration<double>;
1805  auto prev_time = std::chrono::steady_clock::now();
1806 
1807  double velocity = 0.0;
1808  while (dist_moved < dist_total) {
1809  events::pump();
1810 
1811  auto time = std::chrono::steady_clock::now();
1812  auto dt = fractional_seconds{time - prev_time};
1813 
1814  // Do not skip too many frames on slow PCs
1815  dt = std::min<fractional_seconds>(dt, 200ms);
1816  prev_time = time;
1817 
1818  const double dt_as_double = dt.count();
1819  const double accel_time = 0.3 / turbo_speed(); // seconds until full speed is reached
1820  const double decel_time = 0.4 / turbo_speed(); // seconds from full speed to stop
1821 
1822  double velocity_max = prefs::get().scroll_speed() * 60.0;
1823  velocity_max *= turbo_speed();
1824  double accel = velocity_max / accel_time;
1825  double decel = velocity_max / decel_time;
1826 
1827  // If we started to decelerate now, where would we stop?
1828  double stop_time = velocity / decel;
1829  double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time;
1830  if (dist_stop > dist_total || velocity > velocity_max) {
1831  velocity -= decel * dt_as_double;
1832  if (velocity < 1.0) velocity = 1.0;
1833  } else {
1834  velocity += accel * dt_as_double;
1835  if (velocity > velocity_max) velocity = velocity_max;
1836  }
1837 
1838  dist_moved += velocity * dt_as_double;
1839  if (dist_moved > dist_total) dist_moved = dist_total;
1840 
1841  point next_pos(
1842  std::round(move.x * dist_moved / dist_total),
1843  std::round(move.y * dist_moved / dist_total)
1844  );
1845 
1846  point diff = next_pos - prev_pos;
1847  scroll(diff, true);
1848  prev_pos += diff;
1849 
1850  redraw_minimap();
1851  events::draw();
1852  }
1853 }
1854 
1855 void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, bool check_fogged, bool force)
1856 {
1857  if(context().map().on_board(loc) == false) {
1858  ERR_DP << "Tile at " << loc << " isn't on the map, can't scroll to the tile.";
1859  return;
1860  }
1861 
1862  scroll_to_tiles({loc}, scroll_type, check_fogged, false, 0.0, force);
1863 }
1864 
1866  SCROLL_TYPE scroll_type, bool check_fogged,
1867  double add_spacing, bool force)
1868 {
1869  scroll_to_tiles({loc1, loc2}, scroll_type, check_fogged, false, add_spacing, force);
1870 }
1871 
1872 void display::scroll_to_tiles(const std::vector<map_location>& locs,
1873  SCROLL_TYPE scroll_type, bool check_fogged,
1874  bool only_if_possible, double add_spacing, bool force)
1875 {
1876  // basically we calculate the min/max coordinates we want to have on-screen
1877  int minx = 0;
1878  int maxx = 0;
1879  int miny = 0;
1880  int maxy = 0;
1881  bool valid = false;
1882 
1883  for(const map_location& loc : locs) {
1884  if(context().map().on_board(loc) == false) continue;
1885  if(check_fogged && fogged(loc)) continue;
1886 
1887  const auto [x, y] = get_location(loc);
1888 
1889  if (!valid) {
1890  minx = x;
1891  maxx = x;
1892  miny = y;
1893  maxy = y;
1894  valid = true;
1895  } else {
1896  int minx_new = std::min<int>(minx,x);
1897  int miny_new = std::min<int>(miny,y);
1898  int maxx_new = std::max<int>(maxx,x);
1899  int maxy_new = std::max<int>(maxy,y);
1900  rect r = map_area();
1901  r.x = minx_new;
1902  r.y = miny_new;
1903  if(outside_area(r, maxx_new, maxy_new)) {
1904  // we cannot fit all locations to the screen
1905  if (only_if_possible) return;
1906  break;
1907  }
1908  minx = minx_new;
1909  miny = miny_new;
1910  maxx = maxx_new;
1911  maxy = maxy_new;
1912  }
1913  }
1914  //if everything is fogged or the location list is empty
1915  if(!valid) return;
1916 
1917  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
1918  int spacing = std::round(add_spacing * hex_size());
1919  rect r = map_area().padded_by(-spacing); // Shrink
1920  if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) {
1921  return;
1922  }
1923  }
1924 
1925  // let's do "normal" rectangle math from now on
1926  rect locs_bbox;
1927  locs_bbox.x = minx;
1928  locs_bbox.y = miny;
1929  locs_bbox.w = maxx - minx + hex_size();
1930  locs_bbox.h = maxy - miny + hex_size();
1931 
1932  // target the center
1933  point target = locs_bbox.center();
1934 
1935  if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) {
1936  // when doing an ONSCREEN scroll we do not center the target unless needed
1937  rect r = map_area();
1938  auto [map_center_x, map_center_y] = r.center();
1939 
1940  int h = r.h;
1941  int w = r.w;
1942 
1943  // we do not want to be only inside the screen rect, but center a bit more
1944  double inside_frac = 0.5; // 0.0 = always center the target, 1.0 = scroll the minimum distance
1945  w = static_cast<int>(w * inside_frac);
1946  h = static_cast<int>(h * inside_frac);
1947 
1948  // shrink the rectangle by the size of the locations rectangle we found
1949  // such that the new task to fit a point into a rectangle instead of rectangle into rectangle
1950  w -= locs_bbox.w;
1951  h -= locs_bbox.h;
1952 
1953  if (w < 1) w = 1;
1954  if (h < 1) h = 1;
1955 
1956  r.x = target.x - w/2;
1957  r.y = target.y - h/2;
1958  r.w = w;
1959  r.h = h;
1960 
1961  // now any point within r is a possible target to scroll to
1962  // we take the one with the minimum distance to map_center
1963  // which will always be at the border of r
1964 
1965  if (map_center_x < r.x) {
1966  target.x = r.x;
1967  target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
1968  } else if (map_center_x > r.x+r.w-1) {
1969  target.x = r.x + r.w - 1;
1970  target.y = std::clamp(map_center_y, r.y, r.y + r.h - 1);
1971  } else if (map_center_y < r.y) {
1972  target.y = r.y;
1973  target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
1974  } else if (map_center_y > r.y+r.h-1) {
1975  target.y = r.y + r.h - 1;
1976  target.x = std::clamp(map_center_x, r.x, r.x + r.w - 1);
1977  } else {
1978  ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all...";
1979  // keep the target at the center
1980  }
1981  }
1982 
1983  scroll_to_xy(target, scroll_type, force);
1984 }
1985 
1986 
1988 {
1989  zoom_ = std::clamp(zoom_, MinZoom, MaxZoom);
1991 }
1992 
1993 void display::bounds_check_position(int& xpos, int& ypos) const
1994 {
1995  const int tile_width = hex_width();
1996 
1997  // Adjust for the border 2 times
1998  const int xend = static_cast<int>(tile_width * (context().map().w() + 2 * theme_.border().size) + tile_width / 3);
1999  const int yend = static_cast<int>(zoom_ * (context().map().h() + 2 * theme_.border().size) + zoom_ / 2);
2000 
2001  xpos = std::clamp(xpos, 0, xend - map_area().w);
2002  ypos = std::clamp(ypos, 0, yend - map_area().h);
2003 }
2004 
2005 double display::turbo_speed() const
2006 {
2007  bool res = prefs::get().turbo();
2008  if(keys_[SDLK_LSHIFT] || keys_[SDLK_RSHIFT]) {
2009  res = !res;
2010  }
2011 
2012  res |= video::headless();
2013  if(res)
2014  return prefs::get().turbo_speed();
2015  else
2016  return 1.0;
2017 }
2018 
2020 {
2021  prevent_draw_ = pd;
2022  if (!pd) {
2023  // ensure buttons are visible
2024  unhide_buttons();
2025  }
2026 }
2027 
2029 {
2030  return prevent_draw_;
2031 }
2032 
2033 submerge_data display::get_submerge_data(const rect& dest, double submerge, const point& size, uint8_t alpha, bool hreverse, bool vreverse)
2034 {
2036  if(submerge <= 0.0) {
2037  return data;
2038  }
2039 
2040  // Set up blit destinations
2041  data.unsub_dest = dest;
2042  const int dest_sub_h = dest.h * submerge;
2043  data.unsub_dest.h -= dest_sub_h;
2044  const int dest_y_mid = dest.y + data.unsub_dest.h;
2045 
2046  // Set up blit src regions
2047  const int submersion_line = size.y * (1.0 - submerge);
2048  data.unsub_src = {0, 0, size.x, submersion_line};
2049 
2050  // Set up shader vertices
2051  const color_t c_mid(255, 255, 255, 0.3 * alpha);
2052  const int pixels_submerged = size.y * submerge;
2053  const int bot_alpha = std::max(0.3 - pixels_submerged * 0.015, 0.0) * alpha;
2054  const color_t c_bot(255, 255, 255, bot_alpha);
2055  const SDL_FPoint pML{float(dest.x), float(dest_y_mid)};
2056  const SDL_FPoint pMR{float(dest.x + dest.w), float(dest_y_mid)};
2057  const SDL_FPoint pBL{float(dest.x), float(dest.y + dest.h)};
2058  const SDL_FPoint pBR{float(dest.x + dest.w), float(dest.y + dest.h)};
2059  data.alpha_verts = {
2060  SDL_Vertex{pML, c_mid, {0.0, float(1.0 - submerge)}},
2061  SDL_Vertex{pMR, c_mid, {1.0, float(1.0 - submerge)}},
2062  SDL_Vertex{pBL, c_bot, {0.0, 1.0}},
2063  SDL_Vertex{pBR, c_bot, {1.0, 1.0}},
2064  };
2065 
2066  if(hreverse) {
2067  for(SDL_Vertex& v : data.alpha_verts) {
2068  v.tex_coord.x = 1.0 - v.tex_coord.x;
2069  }
2070  }
2071  if(vreverse) {
2072  for(SDL_Vertex& v : data.alpha_verts) {
2073  v.tex_coord.y = 1.0 - v.tex_coord.y;
2074  }
2075  }
2076 
2077  return data;
2078 }
2079 
2081  const std::string& old_mask,
2082  const std::string& new_mask)
2083 {
2084  // TODO: hwaccel - this needs testing as it's not used in mainline
2087 
2088  auto duration = 300ms / turbo_speed();
2089  auto start = std::chrono::steady_clock::now();
2090  for(auto now = start; now < start + duration; now = std::chrono::steady_clock::now()) {
2091  uint8_t p = float_to_color(chrono::normalize_progress(now - start, duration));
2092  tod_hex_alpha2 = p;
2093  tod_hex_alpha1 = ~p;
2096  }
2097 
2098  tod_hex_mask1.reset();
2099  tod_hex_mask2.reset();
2100 }
2101 
2102 void display::fade_to(const color_t& c, const std::chrono::milliseconds& duration)
2103 {
2104  auto start = std::chrono::steady_clock::now();
2105  color_t fade_start = fade_color_;
2106  color_t fade_end = c;
2107 
2108  // If we started transparent, assume the same colour
2109  if(fade_start.a == 0) {
2110  fade_start.r = fade_end.r;
2111  fade_start.g = fade_end.g;
2112  fade_start.b = fade_end.b;
2113  }
2114 
2115  // If we are ending transparent, assume the same colour
2116  if(fade_end.a == 0) {
2117  fade_end.r = fade_start.r;
2118  fade_end.g = fade_start.g;
2119  fade_end.b = fade_start.b;
2120  }
2121 
2122  // Smoothly blend and display
2123  for(auto now = start; now < start + duration; now = std::chrono::steady_clock::now()) {
2124  uint8_t p = float_to_color(chrono::normalize_progress(now - start, duration));
2125  fade_color_ = fade_start.smooth_blend(fade_end, p);
2128  }
2129  fade_color_ = fade_end;
2131  events::draw();
2132 }
2133 
2135 {
2136  fade_color_ = c;
2137 }
2138 
2140 {
2141  if(video::headless())
2142  return;
2143 
2144  DBG_DP << "redrawing everything";
2145 
2146  // This is specifically for game_display.
2147  // It would probably be better to simply make this function virtual,
2148  // if game_display needs to do special processing.
2149  invalidateGameStatus_ = true;
2150 
2151  reportLocations_.clear();
2152  reportSurfaces_.clear();
2153  reports_.clear();
2154 
2156 
2158 
2160 
2161  if(!menu_buttons_.empty() || !action_buttons_.empty()) {
2162  create_buttons();
2163  }
2164 
2165  if(resources::controller) {
2167  if(command_executor != nullptr) {
2168  // This function adds button overlays,
2169  // it needs to be run after recreating the buttons.
2170  command_executor->set_button_state();
2171  }
2172  }
2173 
2174  if(!gui2::is_in_dialog()) {
2176  }
2177 
2178  redraw_background_ = true;
2179 
2180  // This is only for one specific use, which is by the editor controller.
2181  // It would be vastly better if this didn't exist.
2182  for(std::function<void(display&)> f : redraw_observers_) {
2183  f(*this);
2184  }
2185 
2186  invalidate_all();
2187 
2189 }
2190 
2192 {
2193  // Could redraw a smaller region if the display doesn't use it all,
2194  // but when does that ever happen?
2196 }
2197 
2198 void display::add_redraw_observer(const std::function<void(display&)>& f)
2199 {
2200  redraw_observers_.push_back(f);
2201 }
2202 
2204 {
2205  redraw_observers_.clear();
2206 }
2207 
2209 {
2210  if(video::headless()) {
2211  DBG_DP << "display::draw denied";
2212  return;
2213  }
2214  //DBG_DP << "display::draw";
2215 
2216  // I have no idea why this is messing with sync context,
2217  // but i'm not going to touch it.
2219 
2220  // This isn't the best, but also isn't important enough to do better.
2222  DBG_DP << "display::draw redraw background";
2225  redraw_background_ = false;
2226  }
2227 
2228  if(!context().map().empty()) {
2229  if(!invalidated_.empty()) {
2230  draw_invalidated();
2231  invalidated_.clear();
2232  }
2234  }
2235 }
2236 
2238 {
2239  //DBG_DP << "display::update";
2240  // Ensure render textures are correctly sized and up-to-date.
2242 
2243  // Trigger cache rebuild if animated water preference has changed.
2244  if(animate_water_ != prefs::get().animate_water()) {
2245  animate_water_ = prefs::get().animate_water();
2246  builder_->rebuild_cache_all();
2247  }
2248 
2250  invalidate_all();
2251  }
2252 }
2253 
2255 {
2256  //DBG_DP << "display::layout";
2257 
2258  // There's nothing that actually does layout here, it all happens in
2259  // response to events. This isn't ideal, but neither is changing that.
2260 
2261  // Post-layout / Pre-render
2262 
2263  if (!context().map().empty()) {
2264  if(redraw_background_) {
2265  invalidateAll_ = true;
2266  }
2267  if(invalidateAll_) {
2268  DBG_DP << "draw() with invalidateAll";
2269 
2270  // toggle invalidateAll_ first to allow regular invalidations
2271  invalidateAll_ = false;
2273 
2274  redraw_minimap();
2275  }
2276  }
2277 
2278  // invalidate animated terrain, units and haloes
2280 
2281  // Update and invalidate floating labels as necessary
2283 }
2284 
2286 {
2287  // This should render the game map and units.
2288  // It is not responsible for halos and floating labels.
2289  //DBG_DP << "display::render";
2290 
2291  // No need to render if we aren't going to draw anything.
2292  if(prevent_draw_) {
2293  DBG_DP << "render prevented";
2294  return;
2295  }
2296 
2297  // Update our frametime values
2298  tracked_drawable::update_count();
2299 
2300  // render to the offscreen buffer
2301  auto target_setter = draw::set_render_target(front_);
2302  draw();
2303 
2304  // update the minimap texture, if necessary
2305  // TODO: highdpi - high DPI minimap
2306  const rect& area = minimap_area();
2307  if(!area.empty() && !minimap_renderer_) {
2309  }
2310 }
2311 
2312 bool display::expose(const rect& region)
2313 {
2314  if(prevent_draw_) {
2315  DBG_DP << "draw prevented";
2316  return false;
2317  }
2318 
2319  rect clipped_region = draw::get_clip().intersect(region);
2320 
2321  // Blit from the pre-rendered front buffer.
2322  if(clipped_region.overlaps(map_outside_area())) {
2323  front_.set_src(clipped_region);
2324  draw::blit(front_, clipped_region);
2325  front_.clear_src();
2326  }
2327 
2328  // Render halos.
2329  halo_man_.render(clipped_region);
2330 
2331  // Render UI elements.
2332  // Ideally buttons would be drawn as part of panels,
2333  // but they are currently TLDs so they draw themselves.
2334  // This also means they draw over tooltips...
2335  draw_all_panels(clipped_region);
2336  draw_reports(clipped_region);
2337  if(clipped_region.overlaps(minimap_area())) {
2338  draw_minimap();
2339  }
2340 
2341  // Floating labels should probably be separated by type,
2342  // but they aren't so they all get drawn here.
2344 
2345  // If there's a fade, apply it over everything
2346  if(fade_color_.a) {
2347  draw::fill(map_outside_area().intersect(region), fade_color_);
2348  }
2349 
2350  DBG_DP << "display::expose " << region;
2351 
2352  // The display covers the entire screen.
2353  // We will always be drawing something.
2354  return true;
2355 }
2356 
2358 {
2359  assert(!map_screenshot_);
2360  // There's no good way to determine this, as themes can put things
2361  // anywhere. Just return the entire game canvas.
2362  return video::game_canvas();
2363 }
2364 
2366 {
2367  if(video::headless()) {
2368  return;
2369  }
2370 
2371  // We ignore any logical offset on the underlying window buffer.
2372  // Render buffer size is always a simple multiple of the draw area.
2373  rect darea = video::game_canvas();
2374  rect oarea = darea * video::get_pixel_scale();
2375 
2376  // Check that the front buffer size is correct.
2377  // Buffers are always resized together, so we only need to check one.
2379  point dsize = front_.draw_size();
2380  bool raw_size_changed = size.x != oarea.w || size.y != oarea.h;
2381  bool draw_size_changed = dsize.x != darea.w || dsize.y != darea.h;
2382  if (!raw_size_changed && !draw_size_changed) {
2383  // buffers are fine
2384  return;
2385  }
2386 
2387  if(raw_size_changed) {
2388  LOG_DP << "regenerating render buffers as " << oarea;
2389  front_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2390  back_ = texture(oarea.w, oarea.h, SDL_TEXTUREACCESS_TARGET);
2391  }
2392  if(raw_size_changed || draw_size_changed) {
2393  LOG_DP << "updating render buffer draw size to " << darea;
2394  front_.set_draw_size(darea.w, darea.h);
2395  back_.set_draw_size(darea.w, darea.h);
2396  }
2397 
2398  // Fill entire texture with black, just in case
2399  for(int i = 0; i < 2; ++i) {
2401  draw::fill(0,0,0);
2402  }
2403 
2404  // Fill in the background area on both textures.
2406 
2407  queue_rerender();
2408 }
2409 
2411 {
2412  // This could be optimized to avoid the map area,
2413  // but it's only called on game creation or zoom anyway.
2414  const rect clip_rect = map_outside_area();
2416  for(int i = 0; i < 2; ++i) {
2418  if(bgtex) {
2419  draw::tiled(bgtex, clip_rect);
2420  } else {
2421  draw::fill(clip_rect, 0, 0, 0);
2422  }
2423  }
2424 }
2425 
2427 {
2428  return *map_labels_;
2429 }
2430 
2432 {
2433  return *map_labels_;
2434 }
2435 
2437 {
2438  return map_area();
2439 }
2440 
2442 {
2443  // log_scope("display::draw_invalidated");
2444  rect clip_rect = get_clip_rect();
2445  const auto clipper = draw::reduce_clip(clip_rect);
2446 
2447  DBG_DP << "drawing " << invalidated_.size() << " invalidated hexes with clip " << clip_rect;
2448 
2449  // The unit drawer can't function without teams
2450  utils::optional<unit_drawer> drawer{};
2451  if(!context().teams().empty()) {
2452  drawer.emplace(*this);
2453  }
2454 
2455  for(const map_location& loc : invalidated_) {
2456  rect hex_rect = get_location_rect(loc);
2457  if(!hex_rect.overlaps(clip_rect)) {
2458  continue;
2459  }
2460 
2461  draw_hex(loc);
2462  drawn_hexes_ += 1;
2463 
2464  if(drawer) {
2465  const auto u_it = context().units().find(loc);
2466  if(u_it != context().units().end() && unit_can_draw_here(loc, *u_it)) {
2467  drawer->redraw_unit(*u_it);
2468  }
2469  }
2470 
2471  draw_manager::invalidate_region(hex_rect.intersect(clip_rect));
2472  }
2473 
2474  invalidated_hexes_ += invalidated_.size();
2475 }
2476 
2478 {
2479  const bool on_map = context().map().on_board(loc);
2480  const time_of_day& tod = get_time_of_day(loc);
2481 
2482  int num_images_fg = 0;
2483  int num_images_bg = 0;
2484 
2485  const bool is_shrouded = shrouded(loc);
2486 
2487  // unshrouded terrain (the normal case)
2488  if(!is_shrouded) {
2489  get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_
2490  num_images_bg = terrain_image_vector_.size();
2491 
2492  drawing_buffer_add(drawing_layer::terrain_bg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2493  for(const texture& t : images) {
2494  draw::blit(t, dest);
2495  }
2496  });
2497 
2498  get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_
2499  num_images_fg = terrain_image_vector_.size();
2500 
2501  drawing_buffer_add(drawing_layer::terrain_fg, loc, [images = std::exchange(terrain_image_vector_, {})](const rect& dest) {
2502  for(const texture& t : images) {
2503  draw::blit(t, dest);
2504  }
2505  });
2506 
2507  // Draw the grid, if that's been enabled
2508  if(prefs::get().grid()) {
2511 
2513  [tex = image::get_texture(grid_top, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2514 
2516  [tex = image::get_texture(grid_bottom, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2517  }
2518 
2519  // overlays (TODO: can we just draw all the overlays in one pass instead of per-hex?)
2521 
2522  // village-control flags.
2523  if(context().map().is_village(loc)) {
2525  [tex = get_flag(loc)](const rect& dest) { draw::blit(tex, dest); });
2526  }
2527  }
2528 
2529  // Draw the time-of-day mask on top of the terrain in the hex.
2530  // tod may differ from tod if hex is illuminated.
2531  const std::string& tod_hex_mask = tod.image_mask;
2532  if(tod_hex_mask1 || tod_hex_mask2) {
2533  drawing_buffer_add(drawing_layer::terrain_fg, loc, [this](const rect& dest) mutable {
2535  draw::blit(tod_hex_mask1, dest);
2536 
2538  draw::blit(tod_hex_mask2, dest);
2539  });
2540  } else if(!tod_hex_mask.empty()) {
2542  [tex = image::get_texture(tod_hex_mask, image::HEXED)](const rect& dest) { draw::blit(tex, dest); });
2543  }
2544 
2545  // Paint arrows
2546  if(auto arrows_in_hex = arrows_map_.find(loc); arrows_in_hex != arrows_map_.end()) {
2547  std::vector<texture> to_draw;
2548  for(const arrow* a : arrows_in_hex->second) {
2549  to_draw.push_back(image::get_texture(a->get_image_for_loc(loc)));
2550  }
2551 
2552  drawing_buffer_add(drawing_layer::arrows, loc, [to_draw = std::move(to_draw)](const rect& dest) {
2553  for(const texture& t : to_draw) {
2554  draw::blit(t, dest);
2555  }
2556  });
2557  }
2558 
2559  // Apply shroud, fog and linger overlay
2560 
2561  if(is_shrouded || fogged(loc)) {
2562  // TODO: better noise function
2563  const auto get_variant = [&loc](const std::vector<std::string>& variants) -> const auto& {
2564  return variants[std::abs(loc.x + loc.y) % variants.size()];
2565  };
2566 
2567  const std::string& img = get_variant(is_shrouded ? shroud_images_ : fog_images_);
2569  [tex = image::get_texture(img, image::TOD_COLORED)](const rect& dest) { draw::blit(tex, dest); });
2570  }
2571 
2572  if(!is_shrouded) {
2574  for(const texture& t : images) {
2575  draw::blit(t, dest);
2576  }
2577  });
2578  }
2579 
2581  using namespace std::string_literals;
2583  [tex = image::get_texture("terrain/foreground.png"s)](const rect& dest) { draw::blit(tex, dest); });
2584  }
2585 
2586  if(on_map) {
2587  // This might be slight overkill. Basically, we want to check that none of the
2588  // first three bits in the debug flag bitset are set so we can avoid creating
2589  // a stringstream, a temp string, and attempting to trim it for every hex even
2590  // when none of these flags are set. This gives us a temp object with all bits
2591  // past the first three zeroed out.
2592  if((std::as_const(debug_flags_) << (__NUM_DEBUG_FLAGS - DEBUG_FOREGROUND)).none()) {
2593  return;
2594  }
2595 
2596  std::ostringstream ss;
2598  ss << loc << '\n';
2599  }
2600 
2602  ss << context().map().get_terrain(loc) << '\n';
2603  }
2604 
2606  ss << (num_images_bg + num_images_fg) << '\n';
2607  }
2608 
2609  std::string output = ss.str();
2611 
2612  if(output.empty()) {
2613  return;
2614  }
2615 
2617  renderer.set_text(output, false);
2618  renderer.set_font_size(font::SIZE_TINY);
2619  renderer.set_alignment(PANGO_ALIGN_CENTER);
2620  renderer.set_foreground_color(font::NORMAL_COLOR);
2621  renderer.set_maximum_height(-1, false);
2622  renderer.set_maximum_width(-1);
2623 
2624  drawing_buffer_add(drawing_layer::fog_shroud, loc, [tex = renderer.render_and_get_texture()](const rect& dest) {
2625  // Center text in dest rect
2626  const rect text_dest { dest.center() - tex.draw_size() / 2, tex.draw_size() };
2627 
2628  // Add a little padding to the bg
2629  const rect bg_dest = text_dest.padded_by(3);
2630 
2631  draw::fill(bg_dest, 0, 0, 0, 170);
2632  draw::blit(tex, text_dest);
2633  });
2634  }
2635 }
2636 
2638 {
2639  auto it = get_overlays().find(loc);
2640  if(it == get_overlays().end()) {
2641  return;
2642  }
2643 
2644  std::vector<overlay>& overlays = it->second;
2645  if(overlays.empty()) {
2646  return;
2647  }
2648 
2649  const time_of_day& tod = get_time_of_day(loc);
2650  tod_color tod_col = tod.color + color_adjust_;
2651 
2652  std::vector lt{image::light_adjust{-1, tod_col.r, tod_col.g, tod_col.b}};
2653 
2654  for(const overlay& ov : overlays) {
2655  if(fogged(loc) && !ov.visible_in_fog) {
2656  continue;
2657  }
2658 
2659  if(dont_show_all_ && !ov.team_name.empty()) {
2660  const auto current_team_names = utils::split_view(viewing_team().team_name());
2661  const auto team_names = utils::split_view(ov.team_name);
2662 
2663  bool item_visible_for_team = std::find_first_of(team_names.begin(), team_names.end(),
2664  current_team_names.begin(), current_team_names.end()) != team_names.end();
2665 
2666  if(!item_visible_for_team) {
2667  continue;
2668  }
2669  }
2670 
2671  texture tex = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos
2672  ? image::get_lighted_texture(ov.image, lt)
2673  : image::get_texture(ov.image, image::HEXED);
2674 
2675  // Base submerge value for the terrain at this location
2676  const double ter_sub = context().map().get_terrain_info(loc).unit_submerge();
2677 
2679  drawing_layer::terrain_bg, loc, [tex, ter_sub, ovr_sub = ov.submerge](const rect& dest) mutable {
2680  if(ovr_sub > 0.0 && ter_sub > 0.0) {
2681  // Adjust submerge appropriately
2682  double submerge = ter_sub * ovr_sub;
2683 
2684  submerge_data data
2685  = display::get_submerge_data(dest, submerge, tex.draw_size(), ALPHA_OPAQUE, false, false);
2686 
2687  // set clip for dry part
2688  // smooth_shaded doesn't use the clip information so it's fine to set it up front
2689  // TODO: do we need to unset this?
2690  tex.set_src(data.unsub_src);
2691 
2692  // draw underwater part
2693  draw::smooth_shaded(tex, data.alpha_verts);
2694 
2695  // draw dry part
2696  draw::blit(tex, data.unsub_dest);
2697  } else {
2698  // draw whole texture
2699  draw::blit(tex, dest);
2700  }
2701  });
2702  }
2703 }
2704 
2705 /**
2706  * Redraws the specified report (if anything has changed).
2707  * If a config is not supplied, it will be generated via
2708  * reports::generate_report().
2709  */
2710 void display::refresh_report(const std::string& report_name, const config * new_cfg)
2711 {
2712  const theme::status_item *item = theme_.get_status_item(report_name);
2713  if (!item) {
2714  // This should be a warning, but unfortunately there are too many
2715  // unused reports to easily deal with.
2716  //WRN_DP << "no report '" << report_name << "' in theme";
2717  return;
2718  }
2719 
2720  // Now we will need the config. Generate one if needed.
2721 
2723 
2724  if (resources::controller) {
2726  }
2727 
2728  reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb);
2729 
2730  const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context);
2731  if ( new_cfg == nullptr )
2732  new_cfg = &generated_cfg;
2733 
2734  rect& loc = reportLocations_[report_name];
2735  const rect& new_loc = item->location(video::game_canvas());
2736  config &report = reports_[report_name];
2737 
2738  // Report and its location is unchanged since last time. Do nothing.
2739  if (loc == new_loc && report == *new_cfg) {
2740  return;
2741  }
2742 
2743  DBG_DP << "updating report: " << report_name;
2744 
2745  // Mark both old and new locations for redraw.
2748 
2749  // Update the config and current location.
2750  report = *new_cfg;
2751  loc = new_loc;
2752 
2753  // Not 100% sure this is okay
2754  // but it seems to be working so i'm not changing it.
2756 
2757  if (report.empty()) return;
2758 
2759  // Add prefix, postfix elements.
2760  // Make sure that they get the same tooltip
2761  // as the guys around them.
2762  std::string str = item->prefix();
2763  if (!str.empty()) {
2764  config &e = report.add_child_at("element", config(), 0);
2765  e["text"] = str;
2766  e["tooltip"] = report.mandatory_child("element")["tooltip"];
2767  }
2768  str = item->postfix();
2769  if (!str.empty()) {
2770  config &e = report.add_child("element");
2771  e["text"] = str;
2772  e["tooltip"] = report.mandatory_child("element", -1)["tooltip"];
2773  }
2774 
2775  // Do a fake run of drawing the report, so tooltips can be determined.
2776  // TODO: this is horrible, refactor reports to actually make sense
2777  draw_report(report_name, true);
2778 }
2779 
2780 void display::draw_report(const std::string& report_name, bool tooltip_test)
2781 {
2782  const theme::status_item *item = theme_.get_status_item(report_name);
2783  if (!item) {
2784  // This should be a warning, but unfortunately there are too many
2785  // unused reports to easily deal with.
2786  //WRN_DP << "no report '" << report_name << "' in theme";
2787  return;
2788  }
2789 
2790  const rect& loc = reportLocations_[report_name];
2791  const config& report = reports_[report_name];
2792 
2793  int x = loc.x, y = loc.y;
2794 
2795  // Loop through and display each report element.
2796  int tallest = 0;
2797  int image_count = 0;
2798  bool used_ellipsis = false;
2799  std::ostringstream ellipsis_tooltip;
2800  rect ellipsis_area = loc;
2801 
2802  for (config::const_child_itors elements = report.child_range("element");
2803  elements.begin() != elements.end(); elements.pop_front())
2804  {
2805  rect area {x, y, loc.w + loc.x - x, loc.h + loc.y - y};
2806  if (area.h <= 0) break;
2807 
2808  std::string t = elements.front()["text"];
2809  if (!t.empty())
2810  {
2811  if (used_ellipsis) goto skip_element;
2812 
2813  // Draw a text element.
2815  bool eol = false;
2816  if (t[t.size() - 1] == '\n') {
2817  eol = true;
2818  t = t.substr(0, t.size() - 1);
2819  }
2820  // If stripping left the text empty, skip it.
2821  if (t.empty()) {
2822  // Blank text has a null size when rendered.
2823  // It does not, however, have a null size when the size
2824  // is requested with get_size(). Hence this check.
2825  continue;
2826  }
2827  text.set_link_aware(false)
2828  .set_text(t, true);
2830  .set_font_size(item->font_size())
2832  .set_alignment(PANGO_ALIGN_LEFT)
2834  .set_maximum_width(area.w)
2835  .set_maximum_height(area.h, false)
2836  .set_ellipse_mode(PANGO_ELLIPSIZE_END)
2838 
2839  point tsize = text.get_size();
2840 
2841  // check if next element is text with almost no space to show it
2842  const int minimal_text = 12; // width in pixels
2843  config::const_child_iterator ee = elements.begin();
2844  if (!eol && loc.w - (x - loc.x + tsize.x) < minimal_text &&
2845  ++ee != elements.end() && !(*ee)["text"].empty())
2846  {
2847  // make this element longer to trigger rendering of ellipsis
2848  // (to indicate that next elements have not enough space)
2849  //NOTE this space should be longer than minimal_text pixels
2850  t = t + " ";
2851  text.set_text(t, true);
2852  tsize = text.get_size();
2853  // use the area of this element for next tooltips
2854  used_ellipsis = true;
2855  ellipsis_area.x = x;
2856  ellipsis_area.y = y;
2857  ellipsis_area.w = tsize.x;
2858  ellipsis_area.h = tsize.y;
2859  }
2860 
2861  area.w = tsize.x;
2862  area.h = tsize.y;
2863  if (!tooltip_test) {
2864  draw::blit(text.render_and_get_texture(), area);
2865  }
2866  if (area.h > tallest) {
2867  tallest = area.h;
2868  }
2869  if (eol) {
2870  x = loc.x;
2871  y += tallest;
2872  tallest = 0;
2873  } else {
2874  x += area.w;
2875  }
2876  }
2877  else if (!(t = elements.front()["image"].str()).empty())
2878  {
2879  if (used_ellipsis) goto skip_element;
2880 
2881  // Draw an image element.
2883 
2884  if (!img) {
2885  ERR_DP << "could not find image for report: '" << t << "'";
2886  continue;
2887  }
2888 
2889  if (area.w < img.w() && image_count) {
2890  // We have more than one image, and this one doesn't fit.
2892  used_ellipsis = true;
2893  }
2894 
2895  if (img.w() < area.w) area.w = img.w();
2896  if (img.h() < area.h) area.h = img.h();
2897  if (!tooltip_test) {
2898  draw::blit(img, area);
2899  }
2900 
2901  ++image_count;
2902  if (area.h > tallest) {
2903  tallest = area.h;
2904  }
2905 
2906  if (!used_ellipsis) {
2907  x += area.w;
2908  } else {
2909  ellipsis_area = area;
2910  }
2911  }
2912  else
2913  {
2914  // No text nor image, skip this element
2915  continue;
2916  }
2917 
2918  skip_element:
2919  t = elements.front()["tooltip"].t_str().c_str();
2920  if (!t.empty()) {
2921  if (tooltip_test && !used_ellipsis) {
2922  tooltips::add_tooltip(area, t, elements.front()["help"].t_str().c_str());
2923  } else {
2924  // Collect all tooltips for the ellipsis.
2925  // TODO: need a better separator
2926  // TODO: assign an action
2927  ellipsis_tooltip << t;
2928  config::const_child_iterator ee = elements.begin();
2929  if (++ee != elements.end())
2930  ellipsis_tooltip << "\n _________\n\n";
2931  }
2932  }
2933  }
2934 
2935  if (tooltip_test && used_ellipsis) {
2936  tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str());
2937  }
2938 }
2939 
2940 bool display::draw_reports(const rect& region)
2941 {
2942  bool drew = false;
2943  for(const auto& it : reports_) {
2944  const std::string& name = it.first;
2945  const rect& loc = reportLocations_[name];
2946  if(loc.overlaps(region)) {
2947  draw_report(name);
2948  drew = true;
2949  }
2950  }
2951  return drew;
2952 }
2953 
2955 {
2956  DBG_DP << "invalidate_all()";
2957  invalidateAll_ = true;
2958  invalidated_.clear();
2959 }
2960 
2962 {
2964  return false;
2965 
2966  bool tmp;
2967  tmp = invalidated_.insert(loc).second;
2968  return tmp;
2969 }
2970 
2971 bool display::invalidate(const std::set<map_location>& locs)
2972 {
2974  return false;
2975  bool ret = false;
2976  for (const map_location& loc : locs) {
2977  ret = invalidated_.insert(loc).second || ret;
2978  }
2979  return ret;
2980 }
2981 
2982 bool display::propagate_invalidation(const std::set<map_location>& locs)
2983 {
2984  if(invalidateAll_)
2985  return false;
2986 
2987  if(locs.size()<=1)
2988  return false; // propagation never needed
2989 
2990  bool result = false;
2991  {
2992  // search the first hex invalidated (if any)
2993  std::set<map_location>::const_iterator i = locs.begin();
2994  for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {}
2995 
2996  if (i != locs.end()) {
2997 
2998  // propagate invalidation
2999  // 'i' is already in, but I suspect that splitting the range is bad
3000  // especially because locs are often adjacents
3001  std::size_t previous_size = invalidated_.size();
3002  invalidated_.insert(locs.begin(), locs.end());
3003  result = previous_size < invalidated_.size();
3004  }
3005  }
3006  return result;
3007 }
3008 
3010 {
3011  return invalidate_locations_in_rect(map_area().intersect(rect));
3012 }
3013 
3015 {
3017  return false;
3018 
3019  DBG_DP << "invalidating locations in " << rect;
3020 
3021  bool result = false;
3022  for(const map_location& loc : hexes_under_rect(rect)) {
3023  //DBG_DP << "invalidating " << loc.x << ',' << loc.y;
3024  result |= invalidate(loc);
3025  }
3026  return result;
3027 }
3028 
3030 {
3031  if(context().map().is_village(loc)) {
3032  const int owner = context().village_owner(loc) - 1;
3033  if(owner >= 0 && flags_[owner].need_update()
3034  && (!fogged(loc) || !viewing_team().is_enemy(owner + 1))) {
3035  invalidate(loc);
3036  }
3037  }
3038 }
3039 
3041 {
3042  // There are timing issues with this, but i'm not touching it.
3044  animate_map_ = prefs::get().animate_map();
3045  if(animate_map_) {
3046  for(const map_location& loc : get_visible_hexes()) {
3047  if(shrouded(loc))
3048  continue;
3049  if(builder_->update_animation(loc)) {
3050  invalidate(loc);
3051  } else {
3053  }
3054  }
3055  }
3056 
3057  for(const unit& u : context().units()) {
3058  u.anim_comp().refresh();
3059  }
3060  for(const unit* u : *fake_unit_man_) {
3061  u->anim_comp().refresh();
3062  }
3063 
3064  bool new_inval;
3065  do {
3066  new_inval = false;
3067  for(const unit& u : context().units()) {
3068  new_inval |= u.anim_comp().invalidate(*this);
3069  }
3070  for(const unit* u : *fake_unit_man_) {
3071  new_inval |= u->anim_comp().invalidate(*this);
3072  }
3073  } while(new_inval);
3074 
3075  halo_man_.update();
3076 }
3077 
3079 {
3080  for(const unit & u : context().units()) {
3081  u.anim_comp().set_standing();
3082  }
3083 }
3084 
3086 {
3087  for(const map_location& loc : arrow.get_path()) {
3088  arrows_map_[loc].push_back(&arrow);
3089  }
3090 }
3091 
3093 {
3094  for(const map_location& loc : arrow.get_path()) {
3095  arrows_map_[loc].remove(&arrow);
3096  }
3097 }
3098 
3100 {
3101  for(const map_location& loc : arrow.get_previous_path()) {
3102  arrows_map_[loc].remove(&arrow);
3103  }
3104 
3105  for(const map_location& loc : arrow.get_path()) {
3106  arrows_map_[loc].push_back(&arrow);
3107  }
3108 }
3109 
3111 {
3112  auto [center_x, center_y] = viewport_origin_ + map_area().center();
3113  return pixel_position_to_hex(center_x, center_y);
3114 }
3115 
3117 {
3118  cfg["view_locked"] = view_locked_;
3119  cfg["color_adjust_red"] = color_adjust_.r;
3120  cfg["color_adjust_green"] = color_adjust_.g;
3121  cfg["color_adjust_blue"] = color_adjust_.b;
3122  get_middle_location().write(cfg.add_child("location"));
3123 }
3124 
3126 {
3127  view_locked_ = cfg["view_locked"].to_bool(false);
3128  color_adjust_.r = cfg["color_adjust_red"].to_int(0);
3129  color_adjust_.g = cfg["color_adjust_green"].to_int(0);
3130  color_adjust_.b = cfg["color_adjust_blue"].to_int(0);
3131 }
3132 
3134 {
3135  if (!reach_map_changed_) return;
3136  if (reach_map_.empty() != reach_map_old_.empty()) {
3137  // Invalidate everything except the non-darkened tiles
3138  reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_;
3139 
3140  for (const auto& hex : get_visible_hexes()) {
3141  reach_map::iterator reach = full.find(hex);
3142  if (reach != full.end()) {
3143  // Location needs to be darkened or brightened
3144  invalidate(hex);
3145  }
3146  }
3147  } else if (!reach_map_.empty()) {
3148  // Invalidate new and old reach
3149  reach_map::iterator reach, reach_old;
3150  for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) {
3151  invalidate(reach->first);
3152  }
3153  for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) {
3154  invalidate(reach_old->first);
3155  }
3156  }
3158  reach_map_changed_ = false;
3159 
3160  // Make sure there are teams before trying to access units.
3161  if(!context().teams().empty()){
3162  // Update the reachmap-context team, the selected unit's team shall override the displayed unit's.
3163  if(context().units().count(selectedHex_)) {
3165  } else if(context().get_visible_unit(mouseoverHex_, viewing_team()) != nullptr){
3167  } else {
3168  /**
3169  * If no unit is selected or displayed, the reachmap-context team should failsafe to
3170  * the viewing team index, this makes sure the team is invalid when getting the reachmap
3171  * images in game_display::get_reachmap_images().
3172  */
3174  }
3175  DBG_DP << "Updated reachmap context team index to " << std::to_string(reach_map_team_index_);
3176  }
3177 }
3178 
3179 display *display::singleton_ = nullptr;
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:94
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:920
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:863
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:1349
void layout_buttons()
Definition: display.cpp:780
bool redraw_background_
Definition: display.hpp:737
void update_render_textures()
Ensure render textures are valid and correct.
Definition: display.cpp:2365
bool invalidate_locations_in_rect(const rect &rect)
invalidate all hexes under the rectangle rect (in screen coordinates)
Definition: display.cpp:3014
static unsigned int last_zoom_
The previous value of zoom_.
Definition: display.hpp:732
std::size_t viewing_team_index_
Definition: display.hpp:714
void write(config &cfg) const
Definition: display.cpp:3116
static bool zoom_at_min()
Definition: display.cpp:1678
void get_terrain_images(const map_location &loc, const std::string &timeid, TERRAIN_TYPE terrain_type)
Definition: display.cpp:1014
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:762
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1456
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:1477
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:2285
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:2080
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:2961
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:757
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:1440
bool view_locked_
Definition: display.hpp:723
double turbo_speed() const
Definition: display.cpp:2005
@ ONSCREEN
Definition: display.hpp:496
@ ONSCREEN_WARP
Definition: display.hpp:496
void scroll_to_xy(const point &screen_coordinates, SCROLL_TYPE scroll_type, bool force=true)
Definition: display.cpp:1777
int invalidated_hexes_
Count work done for the debug info displayed under fps.
Definition: display.hpp:891
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:2134
void queue_repaint()
Queues repainting to the screen, but doesn't rerender.
Definition: display.cpp:2191
bool reach_map_changed_
Definition: display.hpp:880
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:258
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:261
const rect & unit_image_area() const
Definition: display.cpp:462
TERRAIN_TYPE
Definition: display.hpp:702
@ FOREGROUND
Definition: display.hpp:702
@ BACKGROUND
Definition: display.hpp:702
bool propagate_invalidation(const std::set< map_location > &locs)
If this set is partially invalidated, invalidate all its hexes.
Definition: display.cpp:2982
void clear_redraw_observers()
Clear the redraw observers.
Definition: display.cpp:2203
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:307
const theme::action * action_pressed()
Definition: display.cpp:1408
static submerge_data get_submerge_data(const rect &dest, double submerge, const point &size, uint8_t alpha, bool hreverse, bool vreverse)
Definition: display.cpp:2033
std::shared_ptr< gui::button > find_action_button(const std::string &id)
Retrieves a pointer to a theme UI button.
Definition: display.cpp:760
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:2019
virtual overlay_map & get_overlays()=0
theme theme_
Definition: display.hpp:724
tod_color color_adjust_
Definition: display.hpp:939
bool get_prevent_draw()
Definition: display.cpp:2028
virtual void layout() override
Finalize screen layout.
Definition: display.cpp:2254
virtual void highlight_hex(map_location hex)
Definition: display.cpp:1380
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:2198
bool invalidate_visible_locations_in_rect(const rect &rect)
Definition: display.cpp:3009
static bool zoom_at_max()
Definition: display.cpp:1673
static display * singleton_
Definition: display.hpp:942
std::shared_ptr< gui::button > find_menu_button(const std::string &id)
Definition: display.cpp:770
void render_map_outside_area()
Draw/redraw the off-map background area.
Definition: display.cpp:2410
map_labels & labels()
Definition: display.cpp:2426
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:3092
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:899
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:911
@ __NUM_DEBUG_FLAGS
Dummy entry to size the bitmask.
Definition: display.hpp:914
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:905
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:908
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:902
void process_reachmap_changes()
Definition: display.cpp:3133
void draw_minimap_units()
Definition: display.cpp:1533
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:1855
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:3029
map_location mouseoverHex_
Definition: display.hpp:763
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:1683
std::map< std::string, rect > reportLocations_
Definition: display.hpp:748
const rect_of_hexes get_visible_hexes() const
Returns the rectangular area of visible hexes.
Definition: display.hpp:356
bool invalidateAll_
Definition: display.hpp:738
int drawn_hexes_
Definition: display.hpp:892
void draw_overlays_at(const map_location &loc)
Definition: display.cpp:2637
texture get_flag(const map_location &loc)
Definition: display.cpp:308
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2954
std::map< std::string, config > reports_
Definition: display.hpp:750
std::bitset< __NUM_DEBUG_FLAGS > debug_flags_
Currently set debug flags.
Definition: display.hpp:934
point viewport_origin_
Position of the top-left corner of the viewport, in pixels.
Definition: display.hpp:722
map_location get_middle_location() const
Definition: display.cpp:3110
void bounds_check_position()
Definition: display.cpp:1987
std::function< rect(rect)> minimap_renderer_
Definition: display.hpp:735
surface screenshot(bool map_screenshot=false)
Capture a (map-)screenshot into a surface.
Definition: display.cpp:715
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:751
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:1232
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:882
bool prevent_draw_
Definition: display.hpp:549
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:677
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:1768
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:1763
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:683
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:1865
bool is_blindfolded() const
Definition: display.cpp:447
std::vector< texture > terrain_image_vector_
Definition: display.hpp:781
std::vector< std::string > fog_images_
Definition: display.hpp:759
void fade_to(const color_t &color, const std::chrono::milliseconds &duration)
Screen fade.
Definition: display.cpp:2102
texture tod_hex_mask2
Definition: display.hpp:756
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:937
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:894
void read(const config &cfg)
Definition: display.cpp:3125
const theme::menu * menu_pressed()
Definition: display.cpp:1424
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:734
std::vector< animated< image::locator > > flags_
Animated flags for each team.
Definition: display.hpp:777
rect get_location_rect(const map_location &loc) const
Returns the on-screen rect corresponding to a loc.
Definition: display.cpp:677
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:2780
std::set< map_location > invalidated_
Definition: display.hpp:752
color_t fade_color_
Definition: display.hpp:560
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:930
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2139
rect minimap_location_
Definition: display.hpp:736
void create_buttons()
Definition: display.cpp:823
int blindfold_ctr_
Definition: display.hpp:667
std::string remove_exclusive_draw(const map_location &loc)
Cancels an exclusive draw request.
Definition: display.cpp:367
bool invalidateGameStatus_
Definition: display.hpp:740
reach_map reach_map_old_
Definition: display.hpp:879
virtual void draw_invalidated()
Only called when there's actual redrawing to do.
Definition: display.cpp:2441
void drawing_buffer_commit()
Draws the drawing_buffer_ and clears it.
Definition: display.cpp:1237
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:484
int zoom_index_
Definition: display.hpp:730
void draw_panel(const theme::panel &panel)
Definition: display.cpp:1266
void draw_buttons()
Definition: display.cpp:888
static int hex_width()
Function which returns the width of a hex in pixels, up to where the next hex starts.
Definition: display.hpp:252
void reset_standing_animations()
Definition: display.cpp:3078
bool animate_water_
Local version of prefs::get().animate_water, used to detect when it's changed.
Definition: display.hpp:770
bool debug_flag_set(DEBUG_FLAG flag) const
Definition: display.hpp:917
void toggle_default_zoom()
Sets the zoom amount to the default.
Definition: display.cpp:1751
virtual rect get_clip_rect() const
Get the clipping rectangle for drawing.
Definition: display.cpp:2436
CKey keys_
Definition: display.hpp:764
reports * reports_object_
Definition: display.hpp:742
bool draw_reports(const rect &region)
Draw all reports in the given region.
Definition: display.cpp:2940
void invalidate_animations()
Function to invalidate animated terrains and units which may have changed.
Definition: display.cpp:3040
virtual rect screen_location() override
Return the current draw location of the display, on the screen.
Definition: display.cpp:2357
void hide_buttons()
Hide theme buttons so they don't draw.
Definition: display.cpp:910
virtual bool expose(const rect &region) override
Paint the indicated region to the screen.
Definition: display.cpp:2312
std::list< draw_helper > drawing_buffer_
Definition: display.hpp:836
uint8_t tod_hex_alpha2
Definition: display.hpp:758
std::vector< std::shared_ptr< gui::button > > menu_buttons_
Definition: display.hpp:751
virtual void update() override
Update animations and internal state.
Definition: display.cpp:2237
void draw()
Perform rendering of invalidated items.
Definition: display.cpp:2208
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:585
events::generic_event scroll_event_
Event raised when the map is being scrolled.
Definition: display.hpp:745
bool show_everything() const
Definition: display.hpp:104
virtual void draw_hex(const map_location &loc)
Redraws a single gamemap location.
Definition: display.cpp:2477
texture tod_hex_mask1
Definition: display.hpp:755
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:877
std::size_t playing_team_index_
Definition: display.hpp:795
halo::manager halo_man_
Definition: display.hpp:672
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:1482
texture front_
Render textures, for intermediate rendering.
Definition: display.hpp:584
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:3099
void draw_label(const theme::label &label)
Definition: display.cpp:1291
const std::unique_ptr< fake_unit_manager > fake_unit_man_
Definition: display.hpp:733
const display_context * dc_
Definition: display.hpp:671
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:767
std::vector< std::string > shroud_images_
Definition: display.hpp:760
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1580
static unsigned int zoom_
The current zoom, in pixels (on screen) per 72 pixels (in the graphic assets), i.e....
Definition: display.hpp:729
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:715
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:2710
std::weak_ptr< wb::manager > wb_
Definition: display.hpp:673
void add_arrow(arrow &)
Definition: display.cpp:3085
const std::unique_ptr< map_labels > map_labels_
Definition: display.hpp:741
void set_diagnostic(const std::string &msg)
Definition: display.cpp:1390
virtual void select_hex(map_location hex)
Definition: display.cpp:1372
int diagnostic_label_
Definition: display.hpp:739
std::map< std::string, texture > reportSurfaces_
Definition: display.hpp:749
bool draw_all_panels(const rect &region)
Redraws all panels intersecting the given region.
Definition: display.cpp:1327
reach_map reach_map_
Definition: display.hpp:878
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:271
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:353
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:84
@ 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:422
void render(const rect &r)
Render halos in region.
Definition: halo.cpp:427
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:1857
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:141
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:314
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:1032
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:218
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:844
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:946
texture get_lighted_texture(const image::locator &i_locator, const std::vector< light_adjust > &ls)
Definition: picture.cpp:774
void set_color_adjustment(int r, int g, int b)
Changes Time of Day color tint for all applicable image types.
Definition: picture.cpp:603
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:61
constexpr color_t smooth_blend(const color_t &c, uint8_t p) const
Blend smoothly with another color_t.
Definition: color.hpp:274
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:603
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:612
std::chrono::milliseconds lifetime
Lifetime measured in milliseconds.
Definition: display.hpp:605
Helper for rendering the map by ordering draw operations.
Definition: display.hpp:820
std::function< void(const rect &)> do_draw
Handles the actual drawing at this location.
Definition: display.hpp:825
very simple iterator to walk into the rect_of_hexes
Definition: display.hpp:326
iterator & operator++()
increment y first, then when reaching bottom, increment x
Definition: display.cpp:593
const rect_of_hexes & rect_
Definition: display.hpp:344
Rectangular area of hexes, allowing to decide how the top and bottom edges handles the vertical shift...
Definition: display.hpp:319
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