The Battle for Wesnoth  1.19.12+dev
theme.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 "theme.hpp"
17 
18 #include "desktop/battery_info.hpp"
19 #include "gettext.hpp"
21 #include "hotkey/hotkey_item.hpp"
22 #include "log.hpp"
23 #include "sdl/rect.hpp"
25 #include "wml_exception.hpp"
26 #include "game_config_view.hpp"
27 #include <sstream>
28 #include <utility>
29 
30 static lg::log_domain log_display("display");
31 #define DBG_DP LOG_STREAM(debug, log_display)
32 #define LOG_DP LOG_STREAM(info, log_display)
33 #define ERR_DP LOG_STREAM(err, log_display)
34 
35 namespace
36 {
37 
38 const std::size_t DefaultFontSize = font::SIZE_NORMAL;
39 const color_t DefaultFontRGB {200, 200, 200};
40 
41 _rect ref_rect {0, 0, 0, 0};
42 }
43 
44 static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2 = 0)
45 {
46  std::size_t ref = 0;
47  if(expr[0] == '=') {
48  ref = ref1;
49  expr = expr.substr(1);
50  } else if((expr[0] == '+') || (expr[0] == '-')) {
51  ref = ref2;
52  }
53 
54  return ref + atoi(expr.c_str());
55 }
56 
57 // If x2 or y2 are not specified, use x1 and y1 values
58 static _rect read_rect(const config& cfg)
59 {
60  _rect rect {0, 0, 0, 0};
61  std::vector<std::string> items = utils::split(cfg["rect"].str());
62  if(items.size() >= 1)
63  rect.x1 = atoi(items[0].c_str());
64 
65  if(items.size() >= 2)
66  rect.y1 = atoi(items[1].c_str());
67 
68  if(items.size() >= 3)
69  rect.x2 = atoi(items[2].c_str());
70  else
71  rect.x2 = rect.x1;
72 
73  if(items.size() >= 4)
74  rect.y2 = atoi(items[3].c_str());
75  else
76  rect.y2 = rect.y1;
77 
78  return rect;
79 }
80 
81 static SDL_Rect read_sdl_rect(const config& cfg)
82 {
83  SDL_Rect sdlrect;
84  const _rect rect = read_rect(cfg);
85  sdlrect.x = rect.x1;
86  sdlrect.y = rect.y1;
87  sdlrect.w = (rect.x2 > rect.x1) ? (rect.x2 - rect.x1) : 0;
88  sdlrect.h = (rect.y2 > rect.y1) ? (rect.y2 - rect.y1) : 0;
89 
90  return sdlrect;
91 }
92 
93 static std::string resolve_rect(const std::string& rect_str)
94 {
95  _rect rect {0, 0, 0, 0};
96  std::stringstream resolved;
97  const std::vector<std::string> items = utils::split(rect_str.c_str());
98  if(items.size() >= 1) {
99  rect.x1 = compute(items[0], ref_rect.x1, ref_rect.x2);
100  resolved << rect.x1;
101  }
102  if(items.size() >= 2) {
103  rect.y1 = compute(items[1], ref_rect.y1, ref_rect.y2);
104  resolved << "," << rect.y1;
105  }
106  if(items.size() >= 3) {
107  rect.x2 = compute(items[2], ref_rect.x2, rect.x1);
108  resolved << "," << rect.x2;
109  }
110  if(items.size() >= 4) {
111  rect.y2 = compute(items[3], ref_rect.y2, rect.y1);
112  resolved << "," << rect.y2;
113  }
114 
115  // DBG_DP << "Rect " << rect_str << "\t: " << resolved.str();
116 
117  ref_rect = rect;
118  return resolved.str();
119 }
120 
121 static config& find_ref(const std::string& id, config& cfg, bool remove = false)
122 {
123  static config empty_config;
124 
126  for(config::all_children_iterator i = itors.begin(); i != itors.end(); ++i) {
127  config& icfg = i->cfg;
128  if(i->cfg["id"] == id) {
129  if(remove) {
130  cfg.erase(i);
131  return empty_config;
132  } else {
133  return icfg;
134  }
135  }
136 
137  // Recursively look in children.
138  config& c = find_ref(id, icfg, remove);
139  if(&c != &empty_config) {
140  return c;
141  }
142  }
143 
144  // Not found.
145  return empty_config;
146 }
147 
148 #ifdef DEBUG
149 
150 // to be called from gdb
151 static config& find_ref(const char* id, config& cfg)
152 {
153  return find_ref(std::string(id), cfg);
154 }
155 
156 namespace
157 {
158 // avoid some compiler warnings in stricter mode.
159 static config cfg;
160 static config& result = find_ref("", cfg);
161 } // namespace
162 
163 #endif
164 
165 /**
166  * Returns a copy of the wanted resolution.
167  *
168  * The function returns a copy since our caller uses a copy of this resolution
169  * as base to expand a partial resolution.
170  *
171  * @param resolutions A config object containing the expanded
172  * resolutions.
173  * @param id The id of the resolution to return.
174  *
175  * @throw config::error If the @p id is not found.
176  *
177  * @returns A copy of the resolution config.
178  */
179 static config get_resolution(const config& resolutions, const std::string& id)
180 {
181  for(const auto& resolution : resolutions.child_range("resolution")) {
182  if(resolution["id"] == id) {
183  return resolution;
184  }
185  }
186 
187  throw config::error("[partialresolution] refers to non-existent [resolution] " + id);
188 }
189 
190 /**
191  * Returns a config with all partial resolutions of a theme expanded.
192  *
193  * @param theme The original object, whose objects need to be
194  * expanded.
195  *
196  * @returns A new object with the expanded resolutions in
197  * a theme. This object no longer contains
198  * partial resolutions.
199  */
201 {
202  config result;
203 
204  // Add all the resolutions
205  for(const auto& resolution : theme.child_range("resolution")) {
206  result.add_child("resolution", resolution);
207  }
208 
209  // Resolve all the partialresolutions
210  for(const auto& part : theme.child_range("partialresolution")) {
211  config resolution = get_resolution(result, part["inherits"]);
212  resolution.merge_attributes(part);
213 
214  for(const auto& remove : part.child_range("remove")) {
215  VALIDATE(!remove["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][remove]", "id"));
216 
217  find_ref(remove["id"], resolution, true);
218  }
219 
220  for(const auto& change : part.child_range("change")) {
221  VALIDATE(!change["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][change]", "id"));
222 
223  config& target = find_ref(change["id"], resolution, false);
224  target.merge_attributes(change);
225  }
226 
227  // cannot add [status] sub-elements, but who cares
228  for(const auto& add : part.child_range("add")) {
229  for(const auto [key, cfg] : add.all_children_view()) {
230  resolution.add_child(key, cfg);
231  }
232  }
233 
234  result.add_child("resolution", resolution);
235  }
236 
237  return result;
238 }
239 
240 static void do_resolve_rects(const config& cfg, config& resolved_config, config* resol_cfg = nullptr)
241 {
242  // recursively resolve children
243  for(const auto [child_key, child_cfg] : cfg.all_children_view()) {
244  config& dest = resolved_config.add_child(child_key);
245  do_resolve_rects(child_cfg, dest, child_key == "resolution" ? &dest : resol_cfg);
246  }
247 
248  // copy all key/values
249  resolved_config.merge_attributes(cfg);
250 
251  // override default reference rect with "ref" parameter if any
252  if(!cfg["ref"].empty()) {
253  if(resol_cfg == nullptr) {
254  ERR_DP << "Use of ref= outside a [resolution] block";
255  } else {
256  // DBG_DP << ">> Looking for " << cfg["ref"];
257  const config& ref = find_ref(cfg["ref"], *resol_cfg);
258 
259  if(ref["id"].empty()) {
260  ERR_DP << "Reference to non-existent rect id \"" << cfg["ref"] << "\"";
261  } else if(ref["rect"].empty()) {
262  ERR_DP << "Reference to id \"" << cfg["ref"] << "\" which does not have a \"rect\"";
263  } else {
264  ref_rect = read_rect(ref);
265  }
266  }
267  }
268  // resolve the rect value to absolute coordinates
269  if(!cfg["rect"].empty()) {
270  resolved_config["rect"] = resolve_rect(cfg["rect"]);
271  }
272 }
273 
275  : location_modified_(false)
276  , id_()
277  , loc_(sdl::empty_rect)
278  , relative_loc_(sdl::empty_rect)
279  , last_screen_(sdl::empty_rect)
280  , xanchor_(object::FIXED)
281  , yanchor_(object::FIXED)
282  , spec_width_(0)
283  , spec_height_(0)
284 {
285 }
286 
287 theme::object::object(std::size_t sw, std::size_t sh, const config& cfg)
288  : location_modified_(false)
289  , id_(cfg["id"])
290  , loc_(read_sdl_rect(cfg))
291  , relative_loc_(sdl::empty_rect)
292  , last_screen_(sdl::empty_rect)
293  , xanchor_(read_anchor(cfg["xanchor"]))
294  , yanchor_(read_anchor(cfg["yanchor"]))
295  , spec_width_(sw)
296  , spec_height_(sh)
297 {
298 }
299 
301  : size(0.0)
302  , background_image()
303  , tile_image()
304  , show_border(true)
305 {
306 }
307 
309  : size(cfg["border_size"].to_double())
310  , background_image(cfg["background_image"])
311  , tile_image(cfg["tile_image"])
312  , show_border(cfg["show_border"].to_bool(true))
313 {
314  VALIDATE(size >= 0.0 && size <= 0.5, _("border_size should be between 0.0 and 0.5."));
315 }
316 
317 rect& theme::object::location(const SDL_Rect& screen) const
318 {
319  if(last_screen_ == screen && !location_modified_)
320  return relative_loc_;
321 
322  last_screen_ = screen;
323 
324  switch(xanchor_) {
325  case FIXED:
326  relative_loc_.x = loc_.x;
327  relative_loc_.w = loc_.w;
328  break;
329  case TOP_ANCHORED:
330  relative_loc_.x = loc_.x;
331  relative_loc_.w = loc_.w + screen.w - std::min<std::size_t>(spec_width_, loc_.w + screen.w);
332  break;
333  case BOTTOM_ANCHORED:
334  relative_loc_.x = loc_.x + screen.w - std::min<std::size_t>(spec_width_, loc_.x + screen.w);
335  relative_loc_.w = loc_.w;
336  break;
337  case PROPORTIONAL:
338  relative_loc_.x = (loc_.x * screen.w) / spec_width_;
339  relative_loc_.w = (loc_.w * screen.w) / spec_width_;
340  break;
341  default:
342  assert(false);
343  }
344 
345  switch(yanchor_) {
346  case FIXED:
347  relative_loc_.y = loc_.y;
348  relative_loc_.h = loc_.h;
349  break;
350  case TOP_ANCHORED:
351  relative_loc_.y = loc_.y;
352  relative_loc_.h = loc_.h + screen.h - std::min<std::size_t>(spec_height_, loc_.h + screen.h);
353  break;
354  case BOTTOM_ANCHORED:
355  relative_loc_.y = loc_.y + screen.h - std::min<std::size_t>(spec_height_, loc_.y + screen.h);
356  relative_loc_.h = loc_.h;
357  break;
358  case PROPORTIONAL:
359  relative_loc_.y = (loc_.y * screen.h) / spec_height_;
360  relative_loc_.h = (loc_.h * screen.h) / spec_height_;
361  break;
362  default:
363  assert(false);
364  }
365 
366  relative_loc_.w = std::min<int>(relative_loc_.w, screen.w);
367  if(relative_loc_.x > screen.w) {
368  relative_loc_.x = std::min<int>(relative_loc_.x, screen.w - relative_loc_.w);
369  }
370  relative_loc_.w = std::min<int>(relative_loc_.w, screen.w - relative_loc_.x);
371 
372  relative_loc_.h = std::min<int>(relative_loc_.h, screen.h);
373  if(relative_loc_.y > screen.h) {
374  relative_loc_.y = std::min<int>(relative_loc_.y, screen.h - relative_loc_.h);
375  }
376  relative_loc_.h = std::min<int>(relative_loc_.h, screen.h - relative_loc_.y);
377 
378  return relative_loc_;
379 }
380 
382 {
383  static const std::string top_anchor = "top", left_anchor = "left", bot_anchor = "bottom", right_anchor = "right",
384  proportional_anchor = "proportional";
385  if(str == top_anchor || str == left_anchor)
386  return TOP_ANCHORED;
387  else if(str == bot_anchor || str == right_anchor)
388  return BOTTOM_ANCHORED;
389  else if(str == proportional_anchor)
390  return PROPORTIONAL;
391  else
392  return FIXED;
393 }
394 
396 {
397  loc_.x = rect.x1;
398  loc_.y = rect.y1;
399  loc_.w = rect.x2 - rect.x1;
400  loc_.h = rect.y2 - rect.y1;
401  location_modified_ = true;
402 }
403 
404 void theme::object::modify_location(const std::string& rect_str, SDL_Rect location_ref_rect)
405 {
406  _rect rect {0, 0, 0, 0};
407  const std::vector<std::string> items = utils::split(rect_str.c_str());
408  if(items.size() >= 1) {
409  rect.x1 = compute(items[0], location_ref_rect.x, location_ref_rect.x + location_ref_rect.w);
410  }
411  if(items.size() >= 2) {
412  rect.y1 = compute(items[1], location_ref_rect.y, location_ref_rect.y + location_ref_rect.h);
413  }
414  if(items.size() >= 3) {
415  rect.x2 = compute(items[2], location_ref_rect.x + location_ref_rect.w, rect.x1);
416  }
417  if(items.size() >= 4) {
418  rect.y2 = compute(items[3], location_ref_rect.y + location_ref_rect.h, rect.y1);
419  }
420  modify_location(rect);
421 }
422 
424  : text_()
425  , icon_()
426  , font_()
427  , font_rgb_set_(false)
428  , font_rgb_(DefaultFontRGB)
429 {
430 }
431 
432 theme::label::label(std::size_t sw, std::size_t sh, const config& cfg)
433  : object(sw, sh, cfg)
434  , text_(cfg["prefix"].str() + cfg["prefix_literal"].str() + cfg["text"].str() + cfg["postfix_literal"].str() + cfg["postfix"].str())
435  , icon_(cfg["icon"])
436  , font_(cfg["font_size"].to_size_t())
437  , font_rgb_set_(false)
438  , font_rgb_(DefaultFontRGB)
439 {
440  if(font_ == 0)
441  font_ = DefaultFontSize;
442 
443  if(cfg.has_attribute("font_rgb")) {
444  font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"].str());
445  font_rgb_set_ = true;
446  }
447 }
448 
449 theme::status_item::status_item(std::size_t sw, std::size_t sh, const config& cfg)
450  : object(sw, sh, cfg)
451  , prefix_(cfg["prefix"].str() + cfg["prefix_literal"].str())
452  , postfix_(cfg["postfix_literal"].str() + cfg["postfix"].str())
453  , label_()
454  , font_(cfg["font_size"].to_size_t())
455  , font_rgb_set_(false)
456  , font_rgb_(DefaultFontRGB)
457 {
458  if(font_ == 0)
459  font_ = DefaultFontSize;
460 
461  if(auto label_child = cfg.optional_child("label")) {
462  label_ = label(sw, sh, *label_child);
463  }
464 
465  if(cfg.has_attribute("font_rgb")) {
466  font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"].str());
467  font_rgb_set_ = true;
468  }
469 }
470 
471 theme::panel::panel(std::size_t sw, std::size_t sh, const config& cfg)
472  : object(sw, sh, cfg)
473  , image_(cfg["image"])
474 {
475 }
476 
478  : object()
479  , title_()
480  , tooltip_()
481  , image_()
482  , overlay_()
483  , black_line_(false)
484 {
485 }
486 theme::slider::slider(std::size_t sw, std::size_t sh, const config& cfg)
487  : object(sw, sh, cfg)
488  , title_(cfg["title"].str() + cfg["title_literal"].str())
489  , tooltip_(cfg["tooltip"])
490  , image_(cfg["image"])
491  , overlay_(cfg["overlay"])
492  , black_line_(cfg["black_line"].to_bool(false))
493 {
494 }
495 
497  : object()
498  , button_(true)
499  , context_(false)
500  , title_()
501  , tooltip_()
502  , image_()
503  , overlay_()
504  , items_()
505 {
506 }
507 
508 theme::menu::menu(std::size_t sw, std::size_t sh, const config& cfg)
509  : object(sw, sh, cfg)
510  , button_(cfg["button"].to_bool(true))
511  , context_(cfg["is_context_menu"].to_bool(false))
512  , title_(cfg["title"].str() + cfg["title_literal"].str())
513  , tooltip_(cfg["tooltip"])
514  , image_(cfg["image"])
515  , overlay_(cfg["overlay"])
516  , items_()
517 {
518  for(const auto& item : utils::split(cfg["items"])) {
519  items_.emplace_back("id", item);
520  }
521 
522  const auto& cmd = hotkey::get_hotkey_command(items_[0]["id"]);
523  if(cfg["auto_tooltip"].to_bool() && tooltip_.empty() && items_.size() == 1) {
524  tooltip_ = cmd.description + hotkey::get_names(items_[0]["id"]) + "\n" + cmd.tooltip;
525  } else if(cfg["tooltip_name_prepend"].to_bool() && items_.size() == 1) {
526  tooltip_ = cmd.description + hotkey::get_names(items_[0]["id"]) + "\n" + tooltip_;
527  }
528 }
529 
531  : object()
532  , context_(false)
533  , auto_tooltip_(false)
534  , tooltip_name_prepend_(false)
535  , title_()
536  , tooltip_()
537  , image_()
538  , overlay_()
539  , type_()
540  , items_()
541 {
542 }
543 
544 theme::action::action(std::size_t sw, std::size_t sh, const config& cfg)
545  : object(sw, sh, cfg)
546  , context_(cfg["is_context_menu"].to_bool())
547  , auto_tooltip_(cfg["auto_tooltip"].to_bool(false))
548  , tooltip_name_prepend_(cfg["tooltip_name_prepend"].to_bool(false))
549  , title_(cfg["title"].str() + cfg["title_literal"].str())
550  , tooltip_(cfg["tooltip"])
551  , image_(cfg["image"])
552  , overlay_(cfg["overlay"])
553  , type_(cfg["type"])
554  , items_(utils::split(cfg["items"]))
555 {
556 }
557 
558 const std::string theme::action::tooltip(std::size_t index) const
559 {
560  const auto& cmd = hotkey::get_hotkey_command(items_[index]);
561  std::stringstream result;
562  if(auto_tooltip_ && tooltip_.empty() && items_.size() > index) {
563  result << cmd.description;
564  if(!hotkey::get_names(items_[index]).empty())
565  result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
566  result << "\n" << cmd.tooltip;
567  } else if(tooltip_name_prepend_ && items_.size() == 1) {
568  result << cmd.description;
569  if(!hotkey::get_names(items_[index]).empty())
570  result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
571  result << "\n" << tooltip_;
572  } else {
573  result << tooltip_;
574  }
575 
576  return result.str();
577 }
578 
579 theme::theme(const config& cfg, const SDL_Rect& screen)
580  : theme_reset_event_("theme_reset")
581  , cur_theme()
582  , cfg_()
583  , panels_()
584  , labels_()
585  , menus_()
586  , actions_()
587  , context_()
588  , status_()
589  , main_map_()
590  , mini_map_()
591  , unit_image_()
592  , palette_()
593  , border_()
594  , screen_dimensions_(screen)
595  , cur_spec_width_(0)
596  , cur_spec_height_(0)
597 {
599  set_resolution(screen);
600 }
601 
602 bool theme::set_resolution(const SDL_Rect& screen)
603 {
604  screen_dimensions_ = screen;
605 
606  bool result = false;
607 
608  int current_rating = 1000000;
609  const config* current = nullptr;
610  for(const config& i : cfg_.child_range("resolution")) {
611  int width = i["width"].to_int();
612  int height = i["height"].to_int();
613  LOG_DP << "comparing resolution " << screen.w << "," << screen.h << " to " << width << "," << height;
614  if(screen.w >= width && screen.h >= height) {
615  LOG_DP << "loading theme: " << width << "," << height;
616  current = &i;
617  result = true;
618  break;
619  }
620 
621  const int rating = width * height;
622  if(rating < current_rating) {
623  current = &i;
624  current_rating = rating;
625  }
626  }
627 
628  if(!current) {
629  if(cfg_.child_count("resolution")) {
630  ERR_DP << "No valid resolution found";
631  }
632  return false;
633  }
634  cur_spec_width_ = (*current)["width"].to_size_t();
635  cur_spec_height_ = (*current)["height"].to_size_t();
636 
637  std::map<std::string, std::string> title_stash_menus;
639  for(m = menus_.begin(); m != menus_.end(); ++m) {
640  if(!m->title().empty() && !m->get_id().empty())
641  title_stash_menus[m->get_id()] = m->title();
642  }
643 
644  std::map<std::string, std::string> title_stash_actions;
646  for(a = actions_.begin(); a != actions_.end(); ++a) {
647  if(!a->title().empty() && !a->get_id().empty())
648  title_stash_actions[a->get_id()] = a->title();
649  }
650 
651  panels_.clear();
652  labels_.clear();
653  status_.clear();
654  menus_.clear();
655  actions_.clear();
656  sliders_.clear();
657 
659 
660  for(m = menus_.begin(); m != menus_.end(); ++m) {
661  if(title_stash_menus.find(m->get_id()) != title_stash_menus.end())
662  m->set_title(title_stash_menus[m->get_id()]);
663  }
664 
665  for(a = actions_.begin(); a != actions_.end(); ++a) {
666  if(title_stash_actions.find(a->get_id()) != title_stash_actions.end())
667  a->set_title(title_stash_actions[a->get_id()]);
668  }
669 
671 
672  return result;
673 }
674 
675 void theme::add_object(std::size_t sw, std::size_t sh, const config& cfg)
676 {
677  if(const auto c = cfg.optional_child("main_map")) {
678  main_map_ = object(sw, sh, c.value());
679  }
680 
681  if(const auto c = cfg.optional_child("mini_map")) {
682  mini_map_ = object(sw, sh, c.value());
683  }
684 
685  if(const auto c = cfg.optional_child("palette")) {
686  palette_ = object(sw, sh, c.value());
687  }
688 
689  if(const auto status_cfg = cfg.optional_child("status")) {
690  for(const auto [child_key, child_cfg] : status_cfg->all_children_view()) {
691  status_[child_key].reset(new status_item(sw, sh, child_cfg));
692  }
693  if(const auto unit_image_cfg = status_cfg->optional_child("unit_image")) {
694  unit_image_ = object(sw, sh, unit_image_cfg.value());
695  } else {
696  unit_image_ = object();
697  }
698  }
699 
700  for(const config& p : cfg.child_range("panel")) {
701  panel new_panel(sw, sh, p);
702  set_object_location(new_panel, p["rect"], p["ref"]);
703  panels_.push_back(new_panel);
704  }
705 
706  for(const config& lb : cfg.child_range("label")) {
707  label new_label(sw, sh, lb);
708  set_object_location(new_label, lb["rect"], lb["ref"]);
709  labels_.push_back(new_label);
710  }
711 
712  for(const config& m : cfg.child_range("menu")) {
713  menu new_menu(sw, sh, m);
714  DBG_DP << "adding menu: " << (new_menu.is_context() ? "is context" : "not context");
715  if(new_menu.is_context())
716  context_ = new_menu;
717  else {
718  set_object_location(new_menu, m["rect"], m["ref"]);
719  menus_.push_back(new_menu);
720  }
721 
722  DBG_DP << "done adding menu...";
723  }
724 
725  for(const config& a : cfg.child_range("action")) {
726  action new_action(sw, sh, a);
727  DBG_DP << "adding action: " << (new_action.is_context() ? "is context" : "not context");
728  if(new_action.is_context())
729  action_context_ = new_action;
730  else {
731  set_object_location(new_action, a["rect"], a["ref"]);
732  actions_.push_back(new_action);
733  }
734 
735  DBG_DP << "done adding action...";
736  }
737 
738  for(const config& s : cfg.child_range("slider")) {
739  slider new_slider(sw, sh, s);
740  DBG_DP << "adding slider";
741  set_object_location(new_slider, s["rect"], s["ref"]);
742  sliders_.push_back(new_slider);
743 
744  DBG_DP << "done adding slider...";
745  }
746 
747  if(auto c = cfg.optional_child("main_map_border")) {
748  border_ = border_t(*c);
749  }
750 
751  // Battery charge indicator is always hidden if there isn't enough horizontal space
752  // (GitHub issue #3714)
753  static const int BATTERY_ICON_MIN_WIDTH = 1152;
754  if(!desktop::battery_info::does_device_have_battery() || screen_dimensions_.w < BATTERY_ICON_MIN_WIDTH) {
755  if(auto c = cfg.optional_child("no_battery")) {
756  modify(*c);
757  }
758  }
759 }
760 
761 void theme::remove_object(const std::string& id)
762 {
763  if(status_.erase(id) > 0u) {
764  return;
765  }
766 
767  for(auto p = panels_.begin(); p != panels_.end(); ++p) {
768  if(p->get_id() == id) {
769  panels_.erase(p);
770  return;
771  }
772  }
773  for(auto l = labels_.begin(); l != labels_.end(); ++l) {
774  if(l->get_id() == id) {
775  labels_.erase(l);
776  return;
777  }
778  }
779  for(auto m = menus_.begin(); m != menus_.end(); ++m) {
780  if(m->get_id() == id) {
781  menus_.erase(m);
782  return;
783  }
784  }
785  for(auto a = actions_.begin(); a != actions_.end(); ++a) {
786  if(a->get_id() == id) {
787  actions_.erase(a);
788  return;
789  }
790  }
791  for(auto s = sliders_.begin(); s != sliders_.end(); ++s) {
792  if(s->get_id() == id) {
793  sliders_.erase(s);
794  return;
795  }
796  }
797 
798  std::stringstream stream;
799  stream << "theme object " << id << " not found";
800  throw config::error(stream.str());
801 }
802 
803 void theme::set_object_location(theme::object& element, const std::string& rect_str, std::string ref_id)
804 {
805  theme::object ref_element = element;
806  if(ref_id.empty()) {
807  ref_id = element.get_id();
808  } else {
809  ref_element = find_element(ref_id);
810  }
811  if(ref_element.get_id() == ref_id) {
812  SDL_Rect location_ref_rect = ref_element.get_location();
813  element.modify_location(rect_str, location_ref_rect);
814  }
815 }
816 
817 void theme::modify(const config& cfg)
818 {
819  std::map<std::string, std::string> title_stash;
821  for(m = menus_.begin(); m != menus_.end(); ++m) {
822  if(!m->title().empty() && !m->get_id().empty())
823  title_stash[m->get_id()] = m->title();
824  }
825 
827  for(a = actions_.begin(); a != actions_.end(); ++a) {
828  if(!a->title().empty() && !a->get_id().empty())
829  title_stash[a->get_id()] = a->title();
830  }
831 
832  // Change existing theme objects.
833  for(const config& c : cfg.child_range("change")) {
834  std::string id = c["id"];
835  std::string ref_id = c["ref"];
836  theme::object& element = find_element(id);
837  if(element.get_id() == id)
838  set_object_location(element, c["rect"], ref_id);
839  }
840 
841  // Add new theme objects.
842  for(const config& c : cfg.child_range("add")) {
844  }
845 
846  // Remove existent theme objects.
847  for(const config& c : cfg.child_range("remove")) {
848  remove_object(c["id"]);
849  }
850 
851  for(m = menus_.begin(); m != menus_.end(); ++m) {
852  if(title_stash.find(m->get_id()) != title_stash.end())
853  m->set_title(title_stash[m->get_id()]);
854  }
855  for(a = actions_.begin(); a != actions_.end(); ++a) {
856  if(title_stash.find(a->get_id()) != title_stash.end())
857  a->set_title(title_stash[a->get_id()]);
858  }
859 }
860 
861 theme::object& theme::find_element(const std::string& id)
862 {
863  static theme::object empty_object;
864  theme::object* res = &empty_object;
865 
866  auto status_item_it = status_.find(id);
867  if(status_item_it != status_.end()) {
868  res = status_item_it->second.get();
869  }
870 
871  for(std::vector<theme::panel>::iterator p = panels_.begin(); p != panels_.end(); ++p) {
872  if(p->get_id() == id) {
873  res = &(*p);
874  }
875  }
876  for(std::vector<theme::label>::iterator l = labels_.begin(); l != labels_.end(); ++l) {
877  if(l->get_id() == id) {
878  res = &(*l);
879  }
880  }
881  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
882  if(m->get_id() == id) {
883  res = &(*m);
884  }
885  }
886  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
887  if(a->get_id() == id) {
888  res = &(*a);
889  }
890  }
891  if(id == "main-map") {
892  res = &main_map_;
893  }
894  if(id == "mini-map") {
895  res = &mini_map_;
896  }
897  if(id == "palette") {
898  res = &palette_;
899  }
900  if(id == "unit-image") {
901  res = &unit_image_;
902  }
903  return *res;
904 }
905 
906 const theme::status_item* theme::get_status_item(const std::string& key) const
907 {
908  const auto& i = status_.find(key);
909  if(i != status_.end())
910  return i->second.get();
911  else
912  return nullptr;
913 }
914 
915 const theme::menu* theme::get_menu_item(const std::string& key) const
916 {
917  for(const theme::menu& m : menus_) {
918  if(m.get_id() == key)
919  return &m;
920  }
921  return nullptr;
922 }
923 
924 const theme::action* theme::get_action_item(const std::string& key) const
925 {
926  for(const theme::action& a : actions_) {
927  if(a.get_id() == key)
928  return &a;
929  }
930  return nullptr;
931 }
932 
933 theme::object* theme::refresh_title(const std::string& id, const std::string& new_title)
934 {
935  theme::object* res = nullptr;
936 
937  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
938  if(a->get_id() == id) {
939  res = &(*a);
940  a->set_title(new_title);
941  }
942  }
943 
944  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
945  if(m->get_id() == id) {
946  res = &(*m);
947  m->set_title(new_title);
948  }
949  }
950 
951  return res;
952 }
953 
954 theme::object* theme::refresh_title2(const std::string& id, const std::string& title_tag)
955 {
956  std::string new_title;
957 
958  const config& cfg = find_ref(id, cfg_, false);
959  if(!cfg[title_tag].empty())
960  new_title = cfg[title_tag].str();
961 
962  return refresh_title(id, new_title + cfg["title_literal"].str());
963 }
964 
965 void theme::modify_label(const std::string& id, const std::string& text)
966 {
967  theme::label* label = dynamic_cast<theme::label*>(&find_element(id));
968  if(!label) {
969  LOG_DP << "Theme contains no label called '" << id << "'.";
970  return;
971  }
972  label->set_text(text);
973 }
974 
976 {
977  if(cfg) {
978  known_themes.clear();
979  for(const config& thm : cfg->child_range("theme")) {
980  known_themes[thm["id"]] = thm;
981  }
982  }
983 }
984 
985 std::vector<theme_info> theme::get_basic_theme_info(bool include_hidden)
986 {
987  std::vector<theme_info> res;
988 
989  for(const auto& [id, cfg] : known_themes) {
990  if(!cfg["hidden"].to_bool(false) || include_hidden) {
991  auto& info = res.emplace_back();
992  info.id = id;
993  info.name = cfg["name"].t_str();
994  info.description = cfg["description"].t_str();
995  }
996  }
997 
998  return res;
999 }
1000 
1001 const config& theme::get_theme_config(const std::string& id)
1002 {
1003  auto iter = known_themes.find(id);
1004  if(iter != known_themes.end()) {
1005  return iter->second;
1006  }
1007 
1008  if (!id.empty()) { // (treat empty id as request for default theme)
1009  ERR_DP << "Theme '" << id << "' not found."
1010  << " Falling back to default theme.";
1011  }
1012 
1013  iter = known_themes.find("Default");
1014  if(iter != known_themes.end()) {
1015  return iter->second;
1016  }
1017 
1018  ERR_DP << "Default theme not found.";
1019 
1020  static config empty;
1021  return empty;
1022 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:634
std::size_t child_count(config_key_type key) const
Definition: config.cpp:292
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
void merge_attributes(const config &)
Definition: config.cpp:738
bool has_attribute(config_key_type key) const
Definition: config.cpp:157
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:880
boost::iterator_range< all_children_iterator > all_children_itors
Definition: config.hpp:762
child_itors child_range(config_key_type key)
Definition: config.cpp:268
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
virtual void notify_observers()
A class grating read only view to a vector of config objects, viewed as one config with all children ...
config_array_view child_range(config_key_type key) const
bool is_context() const
Definition: theme.hpp:172
const std::string tooltip(std::size_t index) const
Definition: theme.cpp:558
bool font_rgb_set_
Definition: theme.hpp:121
std::size_t font_
Definition: theme.hpp:120
void set_text(const std::string &text)
Definition: theme.hpp:110
color_t font_rgb_
Definition: theme.hpp:122
std::vector< config > items_
Definition: theme.hpp:244
bool is_context() const
Definition: theme.hpp:227
std::string tooltip_
Definition: theme.hpp:243
virtual rect & location(const SDL_Rect &screen) const
Definition: theme.cpp:317
void modify_location(const _rect &rect)
Definition: theme.cpp:395
const rect & get_location() const
Definition: theme.hpp:54
static ANCHORING read_anchor(const std::string &str)
Definition: theme.cpp:381
const std::string & get_id() const
Definition: theme.hpp:55
panel(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:471
color_t font_rgb_
Definition: theme.hpp:148
std::size_t font_
Definition: theme.hpp:146
status_item(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:449
Definition: theme.hpp:44
object main_map_
Definition: theme.hpp:310
theme::object & find_element(const std::string &id)
Definition: theme.cpp:861
std::vector< panel > panels_
Definition: theme.hpp:299
void add_object(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:675
std::vector< action > actions_
Definition: theme.hpp:302
void set_object_location(theme::object &element, const std::string &rect_str, std::string ref_id)
Definition: theme.cpp:803
std::map< std::string, std::unique_ptr< status_item > > status_
Definition: theme.hpp:308
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:1001
object mini_map_
Definition: theme.hpp:310
object unit_image_
Definition: theme.hpp:310
const action * get_action_item(const std::string &key) const
Definition: theme.cpp:924
object * refresh_title(const std::string &id, const std::string &new_title)
Definition: theme.cpp:933
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:915
menu context_
Definition: theme.hpp:305
void remove_object(const std::string &id)
Definition: theme.cpp:761
object * refresh_title2(const std::string &id, const std::string &title_tag)
Definition: theme.cpp:954
void modify(const config &cfg)
Definition: theme.cpp:817
border_t border_
Definition: theme.hpp:312
static std::map< std::string, config > known_themes
Definition: theme.hpp:317
std::vector< menu > menus_
Definition: theme.hpp:301
std::size_t cur_spec_height_
Definition: theme.hpp:315
std::vector< slider > sliders_
Definition: theme.hpp:303
std::size_t cur_spec_width_
Definition: theme.hpp:315
static std::vector< theme_info > get_basic_theme_info(bool include_hidden=false)
Returns minimal info about saved themes, optionally including hidden ones.
Definition: theme.cpp:985
action action_context_
Definition: theme.hpp:306
void modify_label(const std::string &id, const std::string &text)
Definition: theme.cpp:965
std::vector< label > labels_
Definition: theme.hpp:300
config cfg_
Definition: theme.hpp:298
SDL_Rect screen_dimensions_
Definition: theme.hpp:314
bool set_resolution(const SDL_Rect &screen)
Definition: theme.cpp:602
static void set_known_themes(const game_config_view *cfg)
Copies the theme configs from the main game config.
Definition: theme.cpp:975
object palette_
Definition: theme.hpp:310
events::generic_event theme_reset_event_
Definition: theme.hpp:295
const status_item * get_status_item(const std::string &item) const
Definition: theme.cpp:906
theme(const config &cfg, const SDL_Rect &screen)
Definition: theme.cpp:579
std::string cur_theme
Definition: theme.hpp:297
std::size_t i
Definition: function.cpp:1030
map_location loc_
static std::string _(const char *str)
Definition: gettext.hpp:97
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
Standard logging facilities (interface).
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:159
const int SIZE_NORMAL
Definition: constants.cpp:20
void remove()
Removes a tip.
Definition: tooltip.cpp:94
const hotkey_command & get_hotkey_command(const std::string &command)
returns the hotkey_command with the given name
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
logger & info()
Definition: log.cpp:318
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:32
boost::variant< constant, n_var, boost::recursive_wrapper< not_op >, boost::recursive_wrapper< ternary_op > > expr
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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 > split(const config_attribute_value &val)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Contains the SDL_Rect helper code.
Definition: theme.hpp:34
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
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
double size
Definition: theme.hpp:91
mock_char c
mock_party p
static map_location::direction sw
static map_location::direction s
static _rect read_rect(const config &cfg)
Definition: theme.cpp:58
static config get_resolution(const config &resolutions, const std::string &id)
Returns a copy of the wanted resolution.
Definition: theme.cpp:179
#define LOG_DP
Definition: theme.cpp:32
static config & find_ref(const std::string &id, config &cfg, bool remove=false)
Definition: theme.cpp:121
static std::string resolve_rect(const std::string &rect_str)
Definition: theme.cpp:93
static lg::log_domain log_display("display")
static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2=0)
Definition: theme.cpp:44
static config expand_partialresolution(const config &theme)
Returns a config with all partial resolutions of a theme expanded.
Definition: theme.cpp:200
#define ERR_DP
Definition: theme.cpp:33
static SDL_Rect read_sdl_rect(const config &cfg)
Definition: theme.cpp:81
#define DBG_DP
Definition: theme.cpp:31
static void do_resolve_rects(const config &cfg, config &resolved_config, config *resol_cfg=nullptr)
Definition: theme.cpp:240
Definitions related to theme-support.
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key (attribute).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.