The Battle for Wesnoth  1.19.14+dev
label.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 #include "map/label.hpp"
17 #include "color.hpp"
18 #include "display.hpp"
19 #include "floating_label.hpp"
20 #include "formula/string_utils.hpp"
21 #include "game_board.hpp"
22 #include "game_data.hpp"
23 #include "resources.hpp"
24 #include "tooltips.hpp"
25 #include "utils/general.hpp"
26 
27 /**
28  * Our definition of map labels being obscured is if the tile is obscured,
29  * or the tile below is obscured. This is because in the case where the tile
30  * itself is visible, but the tile below is obscured, the bottom half of the
31  * tile will still be shrouded, and the label being drawn looks weird.
32  */
33 inline bool is_shrouded(const display* disp, const map_location& loc)
34 {
36 }
37 
38 /**
39  * Rather simple test for a hex being fogged.
40  * This only exists because is_shrouded() does. (The code looks nicer if
41  * the test for being fogged looks similar to the test for being shrouded.)
42  */
43 inline bool is_fogged(const display* disp, const map_location& loc)
44 {
45  return disp->fogged(loc);
46 }
47 
49  : team_(team)
50  , labels_()
51  , enabled_(true)
52  , categories_dirty(true)
53 {
54 }
55 
57  : team_(other.team_)
58  , labels_()
59  , enabled_(true)
60 {
61  config cfg;
62  other.write(cfg);
63  read(cfg);
64 }
65 
67 {
68  clear_all();
69 }
70 
72 {
73  if(this != &other) {
74  this->~map_labels();
75  new(this) map_labels(other);
76  }
77 
78  return *this;
79 }
80 
81 void map_labels::write(config& res) const
82 {
83  for(const auto& group : labels_) {
84  for(const auto& label : group.second) {
85  config item;
86  label.second.write(item);
87 
88  res.add_child("label", std::move(item));
89  }
90  }
91 }
92 
94 {
95  clear_all();
96 
97  for(const config& i : cfg.child_range("label")) {
98  add_label(*this, i);
99  }
100 
102 }
103 
104 terrain_label* map_labels::get_label_private(const map_location& loc, const std::string& team_name)
105 {
106  auto label_map = labels_.find(team_name);
107  if(label_map != labels_.end()) {
108  auto itor = label_map->second.find(loc);
109  if(itor != label_map->second.end()) {
110  return &itor->second;
111  }
112  }
113 
114  return nullptr;
115 }
116 
118 {
119  const terrain_label* res = get_label(loc, team_name());
120 
121  // No such team label. Try to find global label, except if that's what we just did.
122  // NOTE: This also avoid infinite recursion
123  if(res == nullptr && !team_name().empty()) {
124  return get_label(loc, "");
125  }
126 
127  return res;
128 }
129 
130 const std::string& map_labels::team_name() const
131 {
132  if(team_) {
133  return team_->team_name();
134  }
135 
136  static const std::string empty;
137  return empty;
138 }
139 
141 {
142  if(team_ != team) {
143  team_ = team;
144  categories_dirty = true;
145  }
146 }
147 
149  const t_string& text,
150  const int creator,
151  const std::string& team_name,
152  const color_t color,
153  const bool visible_in_fog,
154  const bool visible_in_shroud,
155  const bool immutable,
156  const std::string& category,
157  const t_string& tooltip)
158 {
159  terrain_label* res = nullptr;
160 
161  // See if there is already a label in this location for this team.
162  // (We do not use get_label_private() here because we might need
163  // the label_map as well as the terrain_label.)
164  team_label_map::iterator current_label_map = labels_.find(team_name);
165  label_map::iterator current_label;
166 
167  if(current_label_map != labels_.end() &&
168  (current_label = current_label_map->second.find(loc)) != current_label_map->second.end())
169  {
170  // Found old checking if need to erase it
171  if(text.str().empty()) {
172  // Erase the old label.
173  current_label_map->second.erase(current_label);
174 
175  // Restore the global label in the same spot, if any.
176  if(terrain_label* global_label = get_label_private(loc, "")) {
177  global_label->recalculate();
178  }
179  } else {
180  current_label->second.update_info(
181  text, creator, tooltip, team_name, color, visible_in_fog, visible_in_shroud, immutable, category);
182 
183  res = &current_label->second;
184  }
185  } else if(!text.str().empty()) {
186  // See if we will be replacing a global label.
187  terrain_label* global_label = get_label_private(loc, "");
188 
189  // Add the new label.
190  res = add_label(
191  *this, text, creator, team_name, loc, color, visible_in_fog, visible_in_shroud, immutable, category, tooltip);
192 
193  // Hide the old label.
194  if(global_label != nullptr) {
195  global_label->recalculate();
196  }
197  }
198 
199  categories_dirty = true;
200  return res;
201 }
202 
203 template<typename... T>
205 {
206  categories_dirty = true;
207 
208  terrain_label t(std::forward<T>(args)...);
209  return &(*labels_[t.team_name()].emplace(t.location(), std::move(t)).first).second;
210 }
211 
212 void map_labels::clear(const std::string& team_name, bool force)
213 {
215  if(i != labels_.end()) {
216  clear_map(i->second, force);
217  }
218 
219  i = labels_.find("");
220  if(i != labels_.end()) {
221  clear_map(i->second, force);
222  }
223 
224  categories_dirty = true;
225 }
226 
227 void map_labels::clear_map(label_map& m, bool force)
228 {
229  label_map::iterator i = m.begin();
230  while(i != m.end()) {
231  if(!i->second.immutable() || force) {
232  m.erase(i++);
233  } else {
234  ++i;
235  }
236  }
237 
238  categories_dirty = true;
239 }
240 
242 {
243  labels_.clear();
244 }
245 
247 {
248  for(auto& m : labels_) {
249  for(auto& l : m.second) {
250  l.second.recalculate();
251  }
252  }
253 }
254 
255 void map_labels::enable(bool is_enabled)
256 {
257  if(is_enabled != enabled_) {
258  enabled_ = is_enabled;
260  }
261 }
262 
263 /**
264  * Returns whether or not a global (non-team) label can be shown at a
265  * specified location.
266  * (Global labels are suppressed in favor of team labels.)
267  */
269 {
270  if(team_ == nullptr) {
271  // We're in the editor. All global labels can be shown.
272  return true;
273  }
274 
275  const team_label_map::const_iterator glabels = labels_.find(team_name());
276  return glabels == labels_.end() || glabels->second.find(loc) == glabels->second.end();
277 }
278 
280 {
281  for(auto& m : labels_) {
282  for(auto& l : m.second) {
283  l.second.calculate_shroud();
284  }
285  }
286 }
287 
288 const std::vector<std::string>& map_labels::all_categories() const
289 {
290  if(categories_dirty) {
291  categories_dirty = false;
292  categories.clear();
293  categories.push_back("team");
294 
295  for(const team& t : resources::gameboard->teams()) {
296  categories.push_back("side:" + std::to_string(t.side()));
297  }
298 
299  std::set<std::string> unique_cats;
300  for(const auto& m : labels_) {
301  for(const auto& l : m.second) {
302  if(l.second.category().empty()) {
303  continue;
304  }
305 
306  unique_cats.insert("cat:" + l.second.category());
307  }
308  }
309 
310  std::copy(unique_cats.begin(), unique_cats.end(), std::back_inserter(categories));
311  }
312 
313  return categories;
314 }
315 
316 /** Create a new label. */
318  const t_string& text,
319  const int creator,
320  const std::string& team_name,
321  const map_location& loc,
322  const color_t color,
323  const bool visible_in_fog,
324  const bool visible_in_shroud,
325  const bool immutable,
326  const std::string& category,
327  const t_string& tooltip)
328  : handle_(0)
329  , text_(text)
330  , tooltip_(tooltip)
331  , category_(category)
332  , team_name_(team_name)
333  , visible_in_fog_(visible_in_fog)
334  , visible_in_shroud_(visible_in_shroud)
335  , immutable_(immutable)
336  , creator_(creator)
337  , color_(color)
338  , parent_(&parent)
339  , loc_(loc)
340 {
341  recalculate();
342 }
343 
344 /** Load label from config. */
346  : handle_(0)
347  , tooltip_handle_(0)
348  , text_()
349  , tooltip_()
350  , team_name_()
351  , visible_in_fog_(true)
352  , visible_in_shroud_(false)
353  , immutable_(true)
354  , creator_(-1)
355  , color_()
356  , parent_(&parent)
357  , loc_()
358 {
359  read(cfg);
360 }
361 
363  : handle_(l.handle_)
364  , tooltip_handle_(l.tooltip_handle_)
365  , text_(std::move(l.text_))
366  , tooltip_(std::move(l.tooltip_))
367  , category_(std::move(l.category_))
368  , team_name_(std::move(l.team_name_))
369  , visible_in_fog_(l.visible_in_fog_)
370  , visible_in_shroud_(l.visible_in_shroud_)
371  , immutable_(l.immutable_)
372  , creator_(l.creator_)
373  , color_(l.color_)
374  , parent_(l.parent_)
375  , loc_(l.loc_)
376 {
377  l.handle_ = 0;
378  l.tooltip_handle_ = 0;
379 }
380 
382 {
383  clear();
384 }
385 
387 {
388  const variable_set& vs = *resources::gamedata;
389 
390  loc_ = map_location(cfg, &vs);
392 
393  std::string tmp_color = cfg["color"];
394 
395  text_ = cfg["text"];
396  tooltip_ = cfg["tooltip"];
397  team_name_ = cfg["team_name"].str();
398  visible_in_fog_ = cfg["visible_in_fog"].to_bool(true);
399  visible_in_shroud_ = cfg["visible_in_shroud"].to_bool();
400  immutable_ = cfg["immutable"].to_bool(true);
401  category_ = cfg["category"].str();
402 
403  int side = cfg["side"].to_int(-1);
404  if(side >= 0) {
405  creator_ = side - 1;
406  } else if(cfg["side"].str() == "current") {
407  config::attribute_value current_side = vs.get_variable_const("side_number");
408  if(!current_side.empty()) {
409  creator_ = current_side.to_int();
410  }
411  }
412 
413  // Not moved to rendering, as that would depend on variables at render-time
415 
417  tmp_color = utils::interpolate_variables_into_string(tmp_color, vs);
418 
419  if(!tmp_color.empty()) {
420  try {
421  color = color_t::from_rgb_string(tmp_color);
422  } catch(const std::invalid_argument&) {
423  // Prior to the color_t conversion, labels were written to savefiles with an alpha key, despite alpha not
424  // being accepted in color=. Because of this, this enables the loading of older saves without an exception
425  // throwing.
426  color = color_t::from_rgba_string(tmp_color);
427  }
428  }
429 
430  color_ = color;
431 }
432 
434 {
435  loc_.write(cfg);
436 
437  cfg["text"] = text();
438  cfg["tooltip"] = tooltip();
439  cfg["team_name"] = (this->team_name());
440  cfg["color"] = color_.to_rgb_string();
441  cfg["visible_in_fog"] = visible_in_fog_;
442  cfg["visible_in_shroud"] = visible_in_shroud_;
443  cfg["immutable"] = immutable_;
444  cfg["category"] = category_;
445  cfg["side"] = creator_ + 1;
446 }
447 
449  const int creator,
450  const t_string& tooltip,
451  const std::string& team_name,
452  const color_t color)
453 {
454  color_ = color;
455  text_ = text;
456  tooltip_ = tooltip;
458  creator_ = creator;
459 
460  recalculate();
461 }
462 
464  const int creator,
465  const t_string& tooltip,
466  const std::string& team_name,
467  const color_t color,
468  const bool visible_in_fog,
469  const bool visible_in_shroud,
470  const bool immutable,
471  const std::string& category)
472 {
477 
479 }
480 
482 {
483  if(handle_) {
485  }
486 
487  if(tooltip_.empty() || hidden()) {
489  tooltip_handle_ = 0;
490  return;
491  }
492 
493  // tooltips::update_tooltip(tooltip_handle, get_rect(), tooltip_.str(), "", true);
494 
495  if(tooltip_handle_) {
497  } else {
499  }
500 }
501 
503 {
505  if(!disp) {
506  return {};
507  }
508 
509  rect res = disp->get_location_rect(loc_);
510  res.x += disp->hex_size() / 4;
511  res.w -= disp->hex_size() / 2;
512 
513  return res;
514 }
515 
516 static int scale_to_map_zoom(int val)
517 {
518  return val * std::max(1.0, display::get_zoom_factor());
519 }
520 
522 {
524  if(!disp) {
525  return;
526  }
527 
528  if(text_.empty() && tooltip_.empty()) {
529  return;
530  }
531 
532  clear();
533 
534  if(!viewable(*disp)) {
535  return;
536  }
537 
538  // Note: the y part of loc_nextx is not used at all.
541  const int xloc = (disp->get_location(loc_).x + disp->get_location(loc_nextx).x * 2) / 3;
542  const int yloc = disp->get_location(loc_nexty).y - scale_to_map_zoom(font::SIZE_NORMAL);
543 
544  // If a color is specified don't allow to override it with markup. (prevents faking map labels for example)
545  // FIXME: @todo Better detect if it's team label and not provided by the scenario.
546  bool use_markup = color_ == font::LABEL_COLOR;
547 
548  font::floating_label flabel(text_.str());
550  flabel.set_color(color_);
551  flabel.set_position(xloc, yloc);
552  flabel.set_clip_rect(disp->map_outside_area());
556  flabel.use_markup(use_markup);
557 
559 
561 }
562 
563 /**
564  * This is a lightweight test used to see if labels are revealed as a result
565  * of unit actions (i.e. fog/shroud clearing). It should not contain any tests
566  * that are invariant during unit movement (disregarding potential WML events);
567  * those belong in visible().
568  */
570 {
572  if(!disp) {
573  return false;
574  }
575 
576  // Respect user's label preferences
577  std::string category = "cat:" + category_;
578  std::string creator = "side:" + std::to_string(creator_ + 1);
579  const std::vector<std::string>& hidden_categories = disp->context().hidden_label_categories();
580 
581  if(utils::contains(hidden_categories, category)) {
582  return true;
583  }
584 
585  if(creator_ >= 0 && utils::contains(hidden_categories, creator)) {
586  return true;
587  }
588 
589  if(!team_name().empty() && utils::contains(hidden_categories, "team")) {
590  return true;
591  }
592 
593  // Fog can hide some labels.
594  if(!visible_in_fog_ && is_fogged(disp, loc_)) {
595  return true;
596  }
597 
598  // Shroud can hide some labels.
599  if(!visible_in_shroud_ && is_shrouded(disp, loc_)) {
600  return true;
601  }
602 
603  return false;
604 }
605 
606 /**
607  * This is a test used to see if we should bother with the overhead of actually
608  * creating a label. Conditions that can change during unit movement (disregarding
609  * potential WML events) should not be listed here; they belong in hidden().
610  */
611 bool terrain_label::viewable(const display& disp) const
612 {
613  if(!parent_->enabled()) {
614  return false;
615  }
616 
617  // In the editor, all labels are viewable.
618  if(disp.in_editor()) {
619  return true;
620  }
621 
622  // Observers are not privvy to team labels.
623  const bool can_see_team_labels = !disp.context().is_observer();
624 
625  // Global labels are shown unless covered by a team label.
626  if(team_name_.empty()) {
627  return !can_see_team_labels || parent_->visible_global_label(loc_);
628  }
629 
630  // Team labels are only shown to members of the team.
631  return can_see_team_labels && parent_->team_name() == team_name_;
632 }
633 
635 {
636  if(handle_) {
638  handle_ = 0;
639  }
640 
641  if(tooltip_handle_) {
643  tooltip_handle_ = 0;
644  }
645 }
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
Variant for storing WML attributes.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & add_child(config_key_type key)
Definition: config.cpp:436
bool is_observer() const
Check if we are an observer in this game.
virtual const std::vector< std::string > & hidden_label_categories() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
point get_location(const map_location &loc) const
Functions to get the on-screen positions of hexes.
Definition: display.cpp:668
static int hex_size()
Function which returns the size of a hex in pixels (from top tip to bottom tip or left edge to right ...
Definition: display.hpp:258
static double get_zoom_factor()
Returns the current zoom factor.
Definition: display.hpp:261
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:663
rect map_outside_area() const
Returns the available area for a map, this may differ from the above.
Definition: display.cpp:510
virtual bool in_editor() const
Definition: display.hpp:210
const display_context & context() const
Definition: display.hpp:184
rect get_location_rect(const map_location &loc) const
Returns the on-screen rect corresponding to a loc.
Definition: display.cpp:676
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
bool shrouded(const map_location &loc) const
Returns true if location (x,y) is covered in shroud.
Definition: display.cpp:658
void set_position(double xpos, double ypos)
void set_color(const color_t &color)
void set_clip_rect(const rect &r)
void set_scroll_mode(LABEL_SCROLL_MODE scroll)
void set_font_size(int font_size)
void write(config &res) const
Definition: label.cpp:81
void clear_map(label_map &, bool)
Definition: label.cpp:227
const std::vector< std::string > & all_categories() const
Definition: label.cpp:288
const team * team_
Definition: label.hpp:97
bool enabled() const
Definition: label.hpp:67
const std::string & team_name() const
Definition: label.cpp:130
bool visible_global_label(const map_location &) const
Returns whether or not a global (non-team) label can be shown at a specified location.
Definition: label.cpp:268
terrain_label * add_label(T &&... args)
Definition: label.cpp:204
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:148
const terrain_label * get_label(const map_location &loc, const std::string &team_name) const
Definition: label.hpp:48
void recalculate_shroud()
Definition: label.cpp:279
terrain_label * get_label_private(const map_location &loc, const std::string &team_name)
Definition: label.cpp:104
void clear_all()
Definition: label.cpp:241
map_labels & operator=(const map_labels &)
Definition: label.cpp:71
void enable(bool is_enabled)
Definition: label.cpp:255
void set_team(const team *)
Definition: label.cpp:140
void clear(const std::string &, bool force)
Definition: label.cpp:212
bool enabled_
Definition: label.hpp:100
team_label_map labels_
Definition: label.hpp:99
std::vector< std::string > categories
Definition: label.hpp:102
bool categories_dirty
Definition: label.hpp:103
map_labels(const map_labels &)
Definition: label.cpp:56
void recalculate_labels()
Definition: label.cpp:246
~map_labels()
Definition: label.cpp:66
std::map< map_location, terrain_label > label_map
Definition: label.hpp:35
void read(const config &cfg)
Definition: label.cpp:93
bool empty() const
Definition: tstring.hpp:197
const std::string & str() const
Definition: tstring.hpp:201
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
const std::string & team_name() const
Definition: team.hpp:320
To store label data Class implements logic for rendering.
Definition: label.hpp:111
~terrain_label()
Definition: label.cpp:381
terrain_label(const terrain_label &)=delete
Delete copy ctor and assignment ops.
rect get_rect() const
Definition: label.cpp:502
bool visible_in_shroud() const
Definition: label.hpp:169
bool visible_in_fog_
Definition: label.hpp:223
t_string tooltip_
Definition: label.hpp:218
bool immutable() const
Definition: label.hpp:174
int creator() const
Definition: label.hpp:149
std::string category_
Definition: label.hpp:220
color_t color_
Definition: label.hpp:228
void update_info(const t_string &, const int creator, const t_string &, const std::string &, const color_t)
Definition: label.cpp:448
bool visible_in_fog() const
Definition: label.hpp:164
const t_string & tooltip() const
Definition: label.hpp:144
std::string team_name_
Definition: label.hpp:221
int tooltip_handle_
Definition: label.hpp:215
void write(config &res) const
Definition: label.cpp:433
map_location loc_
Definition: label.hpp:231
int creator_
Definition: label.hpp:226
const map_labels * parent_
Definition: label.hpp:230
bool immutable_
Definition: label.hpp:225
bool hidden() const
This is a lightweight test used to see if labels are revealed as a result of unit actions (i....
Definition: label.cpp:569
void clear()
Definition: label.cpp:634
const color_t & color() const
Definition: label.hpp:184
void recalculate()
Definition: label.cpp:521
bool viewable(const display &disp) const
This is a test used to see if we should bother with the overhead of actually creating a label.
Definition: label.cpp:611
const std::string & category() const
Definition: label.hpp:159
bool visible_in_shroud_
Definition: label.hpp:224
const t_string & text() const
Definition: label.hpp:139
void calculate_shroud()
Definition: label.cpp:481
void read(const config &cfg)
Definition: label.cpp:386
const std::string & team_name() const
Definition: label.hpp:154
t_string text_
Definition: label.hpp:217
virtual config::attribute_value get_variable_const(const std::string &id) const =0
map_display and display: classes which take care of displaying the map and game-data on the screen.
const config * cfg
std::size_t i
Definition: function.cpp:1032
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:203
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
static int scale_to_map_zoom(int val)
Definition: label.cpp:516
bool is_fogged(const display *disp, const map_location &loc)
Rather simple test for a hex being fogged.
Definition: label.cpp:43
const color_t LABEL_COLOR
int add_floating_label(const floating_label &flabel)
add a label floating on the screen above everything else.
void show_floating_label(int handle, bool value)
hides or shows a floating label
void remove_floating_label(int handle, const std::chrono::milliseconds &fadeout)
removes the floating label given by 'handle' from the screen
const int SIZE_NORMAL
Definition: constants.cpp:20
@ ANCHOR_LABEL_MAP
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
int add_tooltip(const rect &origin, const std::string &message, const std::string &action)
Definition: tooltips.cpp:293
void remove_tooltip(int id)
Definition: tooltips.cpp:283
bool update_tooltip(int id, const rect &origin, const std::string &message)
Definition: tooltips.cpp:261
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
t_string interpolate_variables_into_tstring(const t_string &tstr, const variable_set &variables)
Function that does the same as the above, for t_stringS.
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
static color_t from_rgb_string(std::string_view c)
Creates a new opaque color_t object from a string variable in "R,G,B" format.
Definition: color.cpp:44
std::string to_rgb_string() const
Returns the stored color as an "R,G,B" string.
Definition: color.cpp:117
static color_t from_rgba_string(std::string_view c)
Creates a new color_t object from a string variable in "R,G,B,A" format.
Definition: color.cpp:23
Encapsulates the map of the game.
Definition: location.hpp:46
map_location get_direction(direction dir, unsigned int n=1u) const
Definition: location.cpp:362
void write(config &cfg) const
Definition: location.cpp:223
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49