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