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