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