The Battle for Wesnoth  1.19.5+dev
theme.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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(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 theme& theme::operator=(theme&& other) = default;
603 
604 bool theme::set_resolution(const SDL_Rect& screen)
605 {
606  screen_dimensions_ = screen;
607 
608  bool result = false;
609 
610  int current_rating = 1000000;
611  const config* current = nullptr;
612  for(const config& i : cfg_.child_range("resolution")) {
613  int width = i["width"].to_int();
614  int height = i["height"].to_int();
615  LOG_DP << "comparing resolution " << screen.w << "," << screen.h << " to " << width << "," << height;
616  if(screen.w >= width && screen.h >= height) {
617  LOG_DP << "loading theme: " << width << "," << height;
618  current = &i;
619  result = true;
620  break;
621  }
622 
623  const int rating = width * height;
624  if(rating < current_rating) {
625  current = &i;
626  current_rating = rating;
627  }
628  }
629 
630  if(!current) {
631  if(cfg_.child_count("resolution")) {
632  ERR_DP << "No valid resolution found";
633  }
634  return false;
635  }
636  cur_spec_width_ = (*current)["width"].to_size_t();
637  cur_spec_height_ = (*current)["height"].to_size_t();
638 
639  std::map<std::string, std::string> title_stash_menus;
641  for(m = menus_.begin(); m != menus_.end(); ++m) {
642  if(!m->title().empty() && !m->get_id().empty())
643  title_stash_menus[m->get_id()] = m->title();
644  }
645 
646  std::map<std::string, std::string> title_stash_actions;
648  for(a = actions_.begin(); a != actions_.end(); ++a) {
649  if(!a->title().empty() && !a->get_id().empty())
650  title_stash_actions[a->get_id()] = a->title();
651  }
652 
653  panels_.clear();
654  labels_.clear();
655  status_.clear();
656  menus_.clear();
657  actions_.clear();
658  sliders_.clear();
659 
661 
662  for(m = menus_.begin(); m != menus_.end(); ++m) {
663  if(title_stash_menus.find(m->get_id()) != title_stash_menus.end())
664  m->set_title(title_stash_menus[m->get_id()]);
665  }
666 
667  for(a = actions_.begin(); a != actions_.end(); ++a) {
668  if(title_stash_actions.find(a->get_id()) != title_stash_actions.end())
669  a->set_title(title_stash_actions[a->get_id()]);
670  }
671 
673 
674  return result;
675 }
676 
677 void theme::add_object(std::size_t sw, std::size_t sh, const config& cfg)
678 {
679  if(const auto c = cfg.optional_child("main_map")) {
680  main_map_ = object(sw, sh, c.value());
681  }
682 
683  if(const auto c = cfg.optional_child("mini_map")) {
684  mini_map_ = object(sw, sh, c.value());
685  }
686 
687  if(const auto c = cfg.optional_child("palette")) {
688  palette_ = object(sw, sh, c.value());
689  }
690 
691  if(const auto status_cfg = cfg.optional_child("status")) {
692  for(const auto [child_key, child_cfg] : status_cfg->all_children_view()) {
693  status_[child_key].reset(new status_item(sw, sh, child_cfg));
694  }
695  if(const auto unit_image_cfg = status_cfg->optional_child("unit_image")) {
696  unit_image_ = object(sw, sh, unit_image_cfg.value());
697  } else {
698  unit_image_ = object();
699  }
700  }
701 
702  for(const config& p : cfg.child_range("panel")) {
703  panel new_panel(sw, sh, p);
704  set_object_location(new_panel, p["rect"], p["ref"]);
705  panels_.push_back(new_panel);
706  }
707 
708  for(const config& lb : cfg.child_range("label")) {
709  label new_label(sw, sh, lb);
710  set_object_location(new_label, lb["rect"], lb["ref"]);
711  labels_.push_back(new_label);
712  }
713 
714  for(const config& m : cfg.child_range("menu")) {
715  menu new_menu(sw, sh, m);
716  DBG_DP << "adding menu: " << (new_menu.is_context() ? "is context" : "not context");
717  if(new_menu.is_context())
718  context_ = new_menu;
719  else {
720  set_object_location(new_menu, m["rect"], m["ref"]);
721  menus_.push_back(new_menu);
722  }
723 
724  DBG_DP << "done adding menu...";
725  }
726 
727  for(const config& a : cfg.child_range("action")) {
728  action new_action(sw, sh, a);
729  DBG_DP << "adding action: " << (new_action.is_context() ? "is context" : "not context");
730  if(new_action.is_context())
731  action_context_ = new_action;
732  else {
733  set_object_location(new_action, a["rect"], a["ref"]);
734  actions_.push_back(new_action);
735  }
736 
737  DBG_DP << "done adding action...";
738  }
739 
740  for(const config& s : cfg.child_range("slider")) {
741  slider new_slider(sw, sh, s);
742  DBG_DP << "adding slider";
743  set_object_location(new_slider, s["rect"], s["ref"]);
744  sliders_.push_back(new_slider);
745 
746  DBG_DP << "done adding slider...";
747  }
748 
749  if(auto c = cfg.optional_child("main_map_border")) {
750  border_ = border_t(*c);
751  }
752 
753  // Battery charge indicator is always hidden if there isn't enough horizontal space
754  // (GitHub issue #3714)
755  static const int BATTERY_ICON_MIN_WIDTH = 1152;
756  if(!desktop::battery_info::does_device_have_battery() || screen_dimensions_.w < BATTERY_ICON_MIN_WIDTH) {
757  if(auto c = cfg.optional_child("no_battery")) {
758  modify(*c);
759  }
760  }
761 }
762 
763 void theme::remove_object(const std::string& id)
764 {
765  if(status_.erase(id) > 0u) {
766  return;
767  }
768 
769  for(auto p = panels_.begin(); p != panels_.end(); ++p) {
770  if(p->get_id() == id) {
771  panels_.erase(p);
772  return;
773  }
774  }
775  for(auto l = labels_.begin(); l != labels_.end(); ++l) {
776  if(l->get_id() == id) {
777  labels_.erase(l);
778  return;
779  }
780  }
781  for(auto m = menus_.begin(); m != menus_.end(); ++m) {
782  if(m->get_id() == id) {
783  menus_.erase(m);
784  return;
785  }
786  }
787  for(auto a = actions_.begin(); a != actions_.end(); ++a) {
788  if(a->get_id() == id) {
789  actions_.erase(a);
790  return;
791  }
792  }
793  for(auto s = sliders_.begin(); s != sliders_.end(); ++s) {
794  if(s->get_id() == id) {
795  sliders_.erase(s);
796  return;
797  }
798  }
799 
800  std::stringstream stream;
801  stream << "theme object " << id << " not found";
802  throw config::error(stream.str());
803 }
804 
805 void theme::set_object_location(theme::object& element, std::string rect_str, std::string ref_id)
806 {
807  theme::object ref_element = element;
808  if(ref_id.empty()) {
809  ref_id = element.get_id();
810  } else {
811  ref_element = find_element(ref_id);
812  }
813  if(ref_element.get_id() == ref_id) {
814  SDL_Rect location_ref_rect = ref_element.get_location();
815  element.modify_location(rect_str, location_ref_rect);
816  }
817 }
818 
819 void theme::modify(const config& cfg)
820 {
821  std::map<std::string, std::string> title_stash;
823  for(m = menus_.begin(); m != menus_.end(); ++m) {
824  if(!m->title().empty() && !m->get_id().empty())
825  title_stash[m->get_id()] = m->title();
826  }
827 
829  for(a = actions_.begin(); a != actions_.end(); ++a) {
830  if(!a->title().empty() && !a->get_id().empty())
831  title_stash[a->get_id()] = a->title();
832  }
833 
834  // Change existing theme objects.
835  for(const config& c : cfg.child_range("change")) {
836  std::string id = c["id"];
837  std::string ref_id = c["ref"];
838  theme::object& element = find_element(id);
839  if(element.get_id() == id)
840  set_object_location(element, c["rect"], ref_id);
841  }
842 
843  // Add new theme objects.
844  for(const config& c : cfg.child_range("add")) {
846  }
847 
848  // Remove existent theme objects.
849  for(const config& c : cfg.child_range("remove")) {
850  remove_object(c["id"]);
851  }
852 
853  for(m = menus_.begin(); m != menus_.end(); ++m) {
854  if(title_stash.find(m->get_id()) != title_stash.end())
855  m->set_title(title_stash[m->get_id()]);
856  }
857  for(a = actions_.begin(); a != actions_.end(); ++a) {
858  if(title_stash.find(a->get_id()) != title_stash.end())
859  a->set_title(title_stash[a->get_id()]);
860  }
861 }
862 
863 theme::object& theme::find_element(const std::string& id)
864 {
865  static theme::object empty_object;
866  theme::object* res = &empty_object;
867 
868  auto status_item_it = status_.find(id);
869  if(status_item_it != status_.end()) {
870  res = status_item_it->second.get();
871  }
872 
873  for(std::vector<theme::panel>::iterator p = panels_.begin(); p != panels_.end(); ++p) {
874  if(p->get_id() == id) {
875  res = &(*p);
876  }
877  }
878  for(std::vector<theme::label>::iterator l = labels_.begin(); l != labels_.end(); ++l) {
879  if(l->get_id() == id) {
880  res = &(*l);
881  }
882  }
883  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
884  if(m->get_id() == id) {
885  res = &(*m);
886  }
887  }
888  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
889  if(a->get_id() == id) {
890  res = &(*a);
891  }
892  }
893  if(id == "main-map") {
894  res = &main_map_;
895  }
896  if(id == "mini-map") {
897  res = &mini_map_;
898  }
899  if(id == "palette") {
900  res = &palette_;
901  }
902  if(id == "unit-image") {
903  res = &unit_image_;
904  }
905  return *res;
906 }
907 
908 const theme::status_item* theme::get_status_item(const std::string& key) const
909 {
910  const auto& i = status_.find(key);
911  if(i != status_.end())
912  return i->second.get();
913  else
914  return nullptr;
915 }
916 
917 const theme::menu* theme::get_menu_item(const std::string& key) const
918 {
919  for(const theme::menu& m : menus_) {
920  if(m.get_id() == key)
921  return &m;
922  }
923  return nullptr;
924 }
925 
926 const theme::action* theme::get_action_item(const std::string& key) const
927 {
928  for(const theme::action& a : actions_) {
929  if(a.get_id() == key)
930  return &a;
931  }
932  return nullptr;
933 }
934 
935 theme::object* theme::refresh_title(const std::string& id, const std::string& new_title)
936 {
937  theme::object* res = nullptr;
938 
939  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
940  if(a->get_id() == id) {
941  res = &(*a);
942  a->set_title(new_title);
943  }
944  }
945 
946  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
947  if(m->get_id() == id) {
948  res = &(*m);
949  m->set_title(new_title);
950  }
951  }
952 
953  return res;
954 }
955 
956 theme::object* theme::refresh_title2(const std::string& id, const std::string& title_tag)
957 {
958  std::string new_title;
959 
960  const config& cfg = find_ref(id, cfg_, false);
961  if(!cfg[title_tag].empty())
962  new_title = cfg[title_tag].str();
963 
964  return refresh_title(id, new_title + cfg["title_literal"].str());
965 }
966 
967 void theme::modify_label(const std::string& id, const std::string& text)
968 {
969  theme::label* label = dynamic_cast<theme::label*>(&find_element(id));
970  if(!label) {
971  LOG_DP << "Theme contains no label called '" << id << "'.";
972  return;
973  }
974  label->set_text(text);
975 }
976 
978 {
979  if(cfg) {
980  known_themes.clear();
981  for(const config& thm : cfg->child_range("theme")) {
982  known_themes[thm["id"]] = thm;
983  }
984  }
985 }
986 
987 std::vector<theme_info> theme::get_basic_theme_info(bool include_hidden)
988 {
989  std::vector<theme_info> res;
990 
991  for(const auto& [id, cfg] : known_themes) {
992  if(!cfg["hidden"].to_bool(false) || include_hidden) {
993  auto& info = res.emplace_back();
994  info.id = id;
995  info.name = cfg["name"].t_str();
996  info.description = cfg["description"].t_str();
997  }
998  }
999 
1000  return res;
1001 }
1002 
1003 const config& theme::get_theme_config(const std::string& id)
1004 {
1005  auto iter = known_themes.find(id);
1006  if(iter != known_themes.end()) {
1007  return iter->second;
1008  }
1009 
1010  if (!id.empty()) { // (treat empty id as request for default theme)
1011  ERR_DP << "Theme '" << id << "' not found."
1012  << " Falling back to default theme.";
1013  }
1014 
1015  iter = known_themes.find("Default");
1016  if(iter != known_themes.end()) {
1017  return iter->second;
1018  }
1019 
1020  ERR_DP << "Default theme not found.";
1021 
1022  static config empty;
1023  return empty;
1024 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:638
std::size_t child_count(config_key_type key) const
Definition: config.cpp:296
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:810
void merge_attributes(const config &)
Definition: config.cpp:742
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:884
boost::iterator_range< all_children_iterator > all_children_itors
Definition: config.hpp:776
child_itors child_range(config_key_type key)
Definition: config.cpp:272
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:384
config & add_child(config_key_type key)
Definition: config.cpp:440
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:863
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:677
std::vector< action > actions_
Definition: theme.hpp:302
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:1003
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:926
object * refresh_title(const std::string &id, const std::string &new_title)
Definition: theme.cpp:935
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:917
menu context_
Definition: theme.hpp:305
void remove_object(const std::string &id)
Definition: theme.cpp:763
object * refresh_title2(const std::string &id, const std::string &title_tag)
Definition: theme.cpp:956
void modify(const config &cfg)
Definition: theme.cpp:819
border_t border_
Definition: theme.hpp:312
void set_object_location(theme::object &element, std::string rect_str, std::string ref_id)
Definition: theme.cpp:805
theme & operator=(const theme &)=delete
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:987
action action_context_
Definition: theme.hpp:306
void modify_label(const std::string &id, const std::string &text)
Definition: theme.cpp:967
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:604
static void set_known_themes(const game_config_view *cfg)
Copies the theme configs from the main game config.
Definition: theme.cpp:977
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:908
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:1028
map_location loc_
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:200
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
Standard logging facilities (interface).
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:150
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:319
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
boost::variant< constant, n_var, boost::recursive_wrapper< not_op >, boost::recursive_wrapper< ternary_op > > expr
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
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:59
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:47
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.