mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-02 21:43:34 +00:00
1017 lines
26 KiB
C++
1017 lines
26 KiB
C++
/*
|
|
Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
|
|
Part of the Battle for Wesnoth Project https://www.wesnoth.org/
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/** @file */
|
|
|
|
#include "theme.hpp"
|
|
|
|
#include "desktop/battery_info.hpp"
|
|
#include "display.hpp"
|
|
#include "gettext.hpp"
|
|
#include "hotkey/hotkey_command.hpp"
|
|
#include "hotkey/hotkey_item.hpp"
|
|
#include "log.hpp"
|
|
#include "sdl/rect.hpp"
|
|
#include "serialization/string_utils.hpp"
|
|
#include "wml_exception.hpp"
|
|
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
static lg::log_domain log_display("display");
|
|
#define DBG_DP LOG_STREAM(debug, log_display)
|
|
#define LOG_DP LOG_STREAM(info, log_display)
|
|
#define ERR_DP LOG_STREAM(err, log_display)
|
|
|
|
namespace
|
|
{
|
|
const int XDim = 1024;
|
|
const int YDim = 768;
|
|
|
|
const std::size_t DefaultFontSize = font::SIZE_NORMAL;
|
|
const color_t DefaultFontRGB {200, 200, 200};
|
|
|
|
_rect ref_rect {0, 0, 0, 0};
|
|
}
|
|
|
|
static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2 = 0)
|
|
{
|
|
std::size_t ref = 0;
|
|
if(expr[0] == '=') {
|
|
ref = ref1;
|
|
expr = expr.substr(1);
|
|
} else if((expr[0] == '+') || (expr[0] == '-')) {
|
|
ref = ref2;
|
|
}
|
|
|
|
return ref + atoi(expr.c_str());
|
|
}
|
|
|
|
// If x2 or y2 are not specified, use x1 and y1 values
|
|
static _rect read_rect(const config& cfg)
|
|
{
|
|
_rect rect {0, 0, 0, 0};
|
|
std::vector<std::string> items = utils::split(cfg["rect"].str());
|
|
if(items.size() >= 1)
|
|
rect.x1 = atoi(items[0].c_str());
|
|
|
|
if(items.size() >= 2)
|
|
rect.y1 = atoi(items[1].c_str());
|
|
|
|
if(items.size() >= 3)
|
|
rect.x2 = atoi(items[2].c_str());
|
|
else
|
|
rect.x2 = rect.x1;
|
|
|
|
if(items.size() >= 4)
|
|
rect.y2 = atoi(items[3].c_str());
|
|
else
|
|
rect.y2 = rect.y1;
|
|
|
|
return rect;
|
|
}
|
|
|
|
static SDL_Rect read_sdl_rect(const config& cfg)
|
|
{
|
|
SDL_Rect sdlrect;
|
|
const _rect rect = read_rect(cfg);
|
|
sdlrect.x = rect.x1;
|
|
sdlrect.y = rect.y1;
|
|
sdlrect.w = (rect.x2 > rect.x1) ? (rect.x2 - rect.x1) : 0;
|
|
sdlrect.h = (rect.y2 > rect.y1) ? (rect.y2 - rect.y1) : 0;
|
|
|
|
return sdlrect;
|
|
}
|
|
|
|
static std::string resolve_rect(const std::string& rect_str)
|
|
{
|
|
_rect rect {0, 0, 0, 0};
|
|
std::stringstream resolved;
|
|
const std::vector<std::string> items = utils::split(rect_str.c_str());
|
|
if(items.size() >= 1) {
|
|
rect.x1 = compute(items[0], ref_rect.x1, ref_rect.x2);
|
|
resolved << rect.x1;
|
|
}
|
|
if(items.size() >= 2) {
|
|
rect.y1 = compute(items[1], ref_rect.y1, ref_rect.y2);
|
|
resolved << "," << rect.y1;
|
|
}
|
|
if(items.size() >= 3) {
|
|
rect.x2 = compute(items[2], ref_rect.x2, rect.x1);
|
|
resolved << "," << rect.x2;
|
|
}
|
|
if(items.size() >= 4) {
|
|
rect.y2 = compute(items[3], ref_rect.y2, rect.y1);
|
|
resolved << "," << rect.y2;
|
|
}
|
|
|
|
// DBG_DP << "Rect " << rect_str << "\t: " << resolved.str() << "\n";
|
|
|
|
ref_rect = rect;
|
|
return resolved.str();
|
|
}
|
|
|
|
static config& find_ref(const std::string& id, config& cfg, bool remove = false)
|
|
{
|
|
static config empty_config;
|
|
|
|
config::all_children_itors itors = cfg.all_children_range();
|
|
for(config::all_children_iterator i = itors.begin(); i != itors.end(); ++i) {
|
|
config& icfg = i->cfg;
|
|
if(i->cfg["id"] == id) {
|
|
if(remove) {
|
|
cfg.erase(i);
|
|
return empty_config;
|
|
} else {
|
|
return icfg;
|
|
}
|
|
}
|
|
|
|
// Recursively look in children.
|
|
config& c = find_ref(id, icfg, remove);
|
|
if(&c != &empty_config) {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
// Not found.
|
|
return empty_config;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
// to be called from gdb
|
|
static config& find_ref(const char* id, config& cfg)
|
|
{
|
|
return find_ref(std::string(id), cfg);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// avoid some compiler warnings in stricter mode.
|
|
static config cfg;
|
|
static config& result = find_ref("", cfg);
|
|
} // namespace
|
|
|
|
#endif
|
|
|
|
/**
|
|
* Returns a copy of the wanted resolution.
|
|
*
|
|
* The function returns a copy since our caller uses a copy of this resolution
|
|
* as base to expand a partial resolution.
|
|
*
|
|
* @param resolutions A config object containing the expanded
|
|
* resolutions.
|
|
* @param id The id of the resolution to return.
|
|
*
|
|
* @throw config::error If the @p id is not found.
|
|
*
|
|
* @returns A copy of the resolution config.
|
|
*/
|
|
static config get_resolution(const config& resolutions, const std::string& id)
|
|
{
|
|
for(const auto& resolution : resolutions.child_range("resolution")) {
|
|
if(resolution["id"] == id) {
|
|
return resolution;
|
|
}
|
|
}
|
|
|
|
throw config::error("[partialresolution] refers to non-existent [resolution] " + id);
|
|
}
|
|
|
|
/**
|
|
* Returns a config with all partial resolutions of a theme expanded.
|
|
*
|
|
* @param theme The original object, whose objects need to be
|
|
* expanded.
|
|
*
|
|
* @returns A new object with the expanded resolutions in
|
|
* a theme. This object no longer contains
|
|
* partial resolutions.
|
|
*/
|
|
static config expand_partialresolution(const config& theme)
|
|
{
|
|
config result;
|
|
|
|
// Add all the resolutions
|
|
for(const auto& resolution : theme.child_range("resolution")) {
|
|
result.add_child("resolution", resolution);
|
|
}
|
|
|
|
// Resolve all the partialresolutions
|
|
for(const auto& part : theme.child_range("partialresolution")) {
|
|
config resolution = get_resolution(result, part["inherits"]);
|
|
resolution.merge_attributes(part);
|
|
|
|
for(const auto& remove : part.child_range("remove")) {
|
|
VALIDATE(!remove["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][remove]", "id"));
|
|
|
|
find_ref(remove["id"], resolution, true);
|
|
}
|
|
|
|
for(const auto& change : part.child_range("change")) {
|
|
VALIDATE(!change["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][change]", "id"));
|
|
|
|
config& target = find_ref(change["id"], resolution, false);
|
|
target.merge_attributes(change);
|
|
}
|
|
|
|
// cannot add [status] sub-elements, but who cares
|
|
for(const auto& add : part.child_range("add")) {
|
|
for(const auto& child : add.all_children_range()) {
|
|
resolution.add_child(child.key, child.cfg);
|
|
}
|
|
}
|
|
|
|
result.add_child("resolution", resolution);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void do_resolve_rects(const config& cfg, config& resolved_config, config* resol_cfg = nullptr)
|
|
{
|
|
// recursively resolve children
|
|
for(const config::any_child& value : cfg.all_children_range()) {
|
|
config& childcfg = resolved_config.add_child(value.key);
|
|
do_resolve_rects(value.cfg, childcfg, value.key == "resolution" ? &childcfg : resol_cfg);
|
|
}
|
|
|
|
// copy all key/values
|
|
resolved_config.merge_attributes(cfg);
|
|
|
|
// override default reference rect with "ref" parameter if any
|
|
if(!cfg["ref"].empty()) {
|
|
if(resol_cfg == nullptr) {
|
|
ERR_DP << "Use of ref= outside a [resolution] block" << std::endl;
|
|
} else {
|
|
// DBG_DP << ">> Looking for " << cfg["ref"] << "\n";
|
|
const config& ref = find_ref(cfg["ref"], *resol_cfg);
|
|
|
|
if(ref["id"].empty()) {
|
|
ERR_DP << "Reference to non-existent rect id \"" << cfg["ref"] << "\"" << std::endl;
|
|
} else if(ref["rect"].empty()) {
|
|
ERR_DP << "Reference to id \"" << cfg["ref"] << "\" which does not have a \"rect\"\n";
|
|
} else {
|
|
ref_rect = read_rect(ref);
|
|
}
|
|
}
|
|
}
|
|
// resolve the rect value to absolute coordinates
|
|
if(!cfg["rect"].empty()) {
|
|
resolved_config["rect"] = resolve_rect(cfg["rect"]);
|
|
}
|
|
}
|
|
|
|
theme::object::object()
|
|
: location_modified_(false)
|
|
, id_()
|
|
, loc_(sdl::empty_rect)
|
|
, relative_loc_(sdl::empty_rect)
|
|
, last_screen_(sdl::empty_rect)
|
|
, xanchor_(object::FIXED)
|
|
, yanchor_(object::FIXED)
|
|
{
|
|
}
|
|
|
|
theme::object::object(const config& cfg)
|
|
: location_modified_(false)
|
|
, id_(cfg["id"])
|
|
, loc_(read_sdl_rect(cfg))
|
|
, relative_loc_(sdl::empty_rect)
|
|
, last_screen_(sdl::empty_rect)
|
|
, xanchor_(read_anchor(cfg["xanchor"]))
|
|
, yanchor_(read_anchor(cfg["yanchor"]))
|
|
{
|
|
}
|
|
|
|
theme::border_t::border_t()
|
|
: size(0.0)
|
|
, background_image()
|
|
, tile_image()
|
|
, show_border(true)
|
|
{
|
|
}
|
|
|
|
theme::border_t::border_t(const config& cfg)
|
|
: size(cfg["border_size"].to_double())
|
|
, background_image(cfg["background_image"])
|
|
, tile_image(cfg["tile_image"])
|
|
, show_border(cfg["show_border"].to_bool(true))
|
|
{
|
|
VALIDATE(size >= 0.0 && size <= 0.5, _("border_size should be between 0.0 and 0.5."));
|
|
}
|
|
|
|
SDL_Rect& theme::object::location(const SDL_Rect& screen) const
|
|
{
|
|
if(last_screen_ == screen && !location_modified_)
|
|
return relative_loc_;
|
|
|
|
last_screen_ = screen;
|
|
|
|
switch(xanchor_) {
|
|
case FIXED:
|
|
relative_loc_.x = loc_.x;
|
|
relative_loc_.w = loc_.w;
|
|
break;
|
|
case TOP_ANCHORED:
|
|
relative_loc_.x = loc_.x;
|
|
relative_loc_.w = screen.w - std::min<std::size_t>(XDim - loc_.w, screen.w);
|
|
break;
|
|
case BOTTOM_ANCHORED:
|
|
relative_loc_.x = screen.w - std::min<std::size_t>(XDim - loc_.x, screen.w);
|
|
relative_loc_.w = loc_.w;
|
|
break;
|
|
case PROPORTIONAL:
|
|
relative_loc_.x = (loc_.x * screen.w) / XDim;
|
|
relative_loc_.w = (loc_.w * screen.w) / XDim;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
switch(yanchor_) {
|
|
case FIXED:
|
|
relative_loc_.y = loc_.y;
|
|
relative_loc_.h = loc_.h;
|
|
break;
|
|
case TOP_ANCHORED:
|
|
relative_loc_.y = loc_.y;
|
|
relative_loc_.h = screen.h - std::min<std::size_t>(YDim - loc_.h, screen.h);
|
|
break;
|
|
case BOTTOM_ANCHORED:
|
|
relative_loc_.y = screen.h - std::min<std::size_t>(YDim - loc_.y, screen.h);
|
|
relative_loc_.h = loc_.h;
|
|
break;
|
|
case PROPORTIONAL:
|
|
relative_loc_.y = (loc_.y * screen.h) / YDim;
|
|
relative_loc_.h = (loc_.h * screen.h) / YDim;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
relative_loc_.x = std::min<int>(relative_loc_.x, screen.w);
|
|
relative_loc_.w = std::min<int>(relative_loc_.w, screen.w - relative_loc_.x);
|
|
relative_loc_.y = std::min<int>(relative_loc_.y, screen.h);
|
|
relative_loc_.h = std::min<int>(relative_loc_.h, screen.h - relative_loc_.y);
|
|
|
|
return relative_loc_;
|
|
}
|
|
|
|
theme::object::ANCHORING theme::object::read_anchor(const std::string& str)
|
|
{
|
|
static const std::string top_anchor = "top", left_anchor = "left", bot_anchor = "bottom", right_anchor = "right",
|
|
proportional_anchor = "proportional";
|
|
if(str == top_anchor || str == left_anchor)
|
|
return TOP_ANCHORED;
|
|
else if(str == bot_anchor || str == right_anchor)
|
|
return BOTTOM_ANCHORED;
|
|
else if(str == proportional_anchor)
|
|
return PROPORTIONAL;
|
|
else
|
|
return FIXED;
|
|
}
|
|
|
|
void theme::object::modify_location(const _rect& rect)
|
|
{
|
|
loc_.x = rect.x1;
|
|
loc_.y = rect.y1;
|
|
loc_.w = rect.x2 - rect.x1;
|
|
loc_.h = rect.y2 - rect.y1;
|
|
location_modified_ = true;
|
|
}
|
|
|
|
void theme::object::modify_location(std::string rect_str, SDL_Rect location_ref_rect)
|
|
{
|
|
_rect rect {0, 0, 0, 0};
|
|
const std::vector<std::string> items = utils::split(rect_str.c_str());
|
|
if(items.size() >= 1) {
|
|
rect.x1 = compute(items[0], location_ref_rect.x, location_ref_rect.x + location_ref_rect.w);
|
|
}
|
|
if(items.size() >= 2) {
|
|
rect.y1 = compute(items[1], location_ref_rect.y, location_ref_rect.y + location_ref_rect.h);
|
|
}
|
|
if(items.size() >= 3) {
|
|
rect.x2 = compute(items[2], location_ref_rect.x + location_ref_rect.w, rect.x1);
|
|
}
|
|
if(items.size() >= 4) {
|
|
rect.y2 = compute(items[3], location_ref_rect.y + location_ref_rect.h, rect.y1);
|
|
}
|
|
modify_location(rect);
|
|
}
|
|
|
|
theme::label::label()
|
|
: text_()
|
|
, icon_()
|
|
, font_()
|
|
, font_rgb_set_(false)
|
|
, font_rgb_(DefaultFontRGB)
|
|
{
|
|
}
|
|
|
|
theme::label::label(const config& cfg)
|
|
: object(cfg)
|
|
, text_(cfg["prefix"].str() + cfg["text"].str() + cfg["postfix"].str())
|
|
, icon_(cfg["icon"])
|
|
, font_(cfg["font_size"])
|
|
, font_rgb_set_(false)
|
|
, font_rgb_(DefaultFontRGB)
|
|
{
|
|
if(font_ == 0)
|
|
font_ = DefaultFontSize;
|
|
|
|
if(cfg.has_attribute("font_rgb")) {
|
|
font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"]);
|
|
font_rgb_set_ = true;
|
|
}
|
|
}
|
|
|
|
theme::status_item::status_item(const config& cfg)
|
|
: object(cfg)
|
|
, prefix_(cfg["prefix"].str() + cfg["prefix_literal"].str())
|
|
, postfix_(cfg["postfix_literal"].str() + cfg["postfix"].str())
|
|
, label_()
|
|
, font_(cfg["font_size"])
|
|
, font_rgb_set_(false)
|
|
, font_rgb_(DefaultFontRGB)
|
|
{
|
|
if(font_ == 0)
|
|
font_ = DefaultFontSize;
|
|
|
|
if(const config& label_child = cfg.child("label")) {
|
|
label_ = label(label_child);
|
|
}
|
|
|
|
if(cfg.has_attribute("font_rgb")) {
|
|
font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"]);
|
|
font_rgb_set_ = true;
|
|
}
|
|
}
|
|
|
|
theme::panel::panel(const config& cfg)
|
|
: object(cfg)
|
|
, image_(cfg["image"])
|
|
{
|
|
}
|
|
|
|
theme::slider::slider()
|
|
: object()
|
|
, title_()
|
|
, tooltip_()
|
|
, image_()
|
|
, overlay_()
|
|
, black_line_(false)
|
|
{
|
|
}
|
|
theme::slider::slider(const config& cfg)
|
|
: object(cfg)
|
|
, title_(cfg["title"].str() + cfg["title_literal"].str())
|
|
, tooltip_(cfg["tooltip"])
|
|
, image_(cfg["image"])
|
|
, overlay_(cfg["overlay"])
|
|
, black_line_(cfg["black_line"].to_bool(false))
|
|
{
|
|
}
|
|
|
|
theme::menu::menu()
|
|
: object()
|
|
, button_(true)
|
|
, context_(false)
|
|
, title_()
|
|
, tooltip_()
|
|
, image_()
|
|
, overlay_()
|
|
, items_()
|
|
{
|
|
}
|
|
|
|
theme::menu::menu(const config& cfg)
|
|
: object(cfg)
|
|
, button_(cfg["button"].to_bool(true))
|
|
, context_(cfg["is_context_menu"].to_bool(false))
|
|
, title_(cfg["title"].str() + cfg["title_literal"].str())
|
|
, tooltip_(cfg["tooltip"])
|
|
, image_(cfg["image"])
|
|
, overlay_(cfg["overlay"])
|
|
, items_()
|
|
{
|
|
for(const auto& item : utils::split(cfg["items"])) {
|
|
items_.emplace_back("id", item);
|
|
}
|
|
|
|
if(cfg["auto_tooltip"].to_bool() && tooltip_.empty() && items_.size() == 1) {
|
|
tooltip_ = hotkey::get_description(items_[0]["id"]) + hotkey::get_names(items_[0]["id"]) + "\n"
|
|
+ hotkey::get_tooltip(items_[0]["id"]);
|
|
} else if(cfg["tooltip_name_prepend"].to_bool() && items_.size() == 1) {
|
|
tooltip_ = hotkey::get_description(items_[0]["id"]) + hotkey::get_names(items_[0]["id"]) + "\n" + tooltip_;
|
|
}
|
|
}
|
|
|
|
theme::action::action()
|
|
: object()
|
|
, context_(false)
|
|
, auto_tooltip_(false)
|
|
, tooltip_name_prepend_(false)
|
|
, title_()
|
|
, tooltip_()
|
|
, image_()
|
|
, overlay_()
|
|
, type_()
|
|
, items_()
|
|
{
|
|
}
|
|
|
|
theme::action::action(const config& cfg)
|
|
: object(cfg)
|
|
, context_(cfg["is_context_menu"].to_bool())
|
|
, auto_tooltip_(cfg["auto_tooltip"].to_bool(false))
|
|
, tooltip_name_prepend_(cfg["tooltip_name_prepend"].to_bool(false))
|
|
, title_(cfg["title"].str() + cfg["title_literal"].str())
|
|
, tooltip_(cfg["tooltip"])
|
|
, image_(cfg["image"])
|
|
, overlay_(cfg["overlay"])
|
|
, type_(cfg["type"])
|
|
, items_(utils::split(cfg["items"]))
|
|
{
|
|
}
|
|
|
|
const std::string theme::action::tooltip(std::size_t index) const
|
|
{
|
|
std::stringstream result;
|
|
if(auto_tooltip_ && tooltip_.empty() && items_.size() > index) {
|
|
result << hotkey::get_description(items_[index]);
|
|
if(!hotkey::get_names(items_[index]).empty())
|
|
result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
|
|
result << "\n" << hotkey::get_tooltip(items_[index]);
|
|
} else if(tooltip_name_prepend_ && items_.size() == 1) {
|
|
result << hotkey::get_description(items_[index]);
|
|
if(!hotkey::get_names(items_[index]).empty())
|
|
result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
|
|
result << "\n" << tooltip_;
|
|
} else {
|
|
result << tooltip_;
|
|
}
|
|
|
|
return result.str();
|
|
}
|
|
|
|
theme::theme(const config& cfg, const SDL_Rect& screen)
|
|
: theme_reset_event_("theme_reset")
|
|
, cur_theme()
|
|
, cfg_()
|
|
, panels_()
|
|
, labels_()
|
|
, menus_()
|
|
, actions_()
|
|
, context_()
|
|
, status_()
|
|
, main_map_()
|
|
, mini_map_()
|
|
, unit_image_()
|
|
, palette_()
|
|
, border_()
|
|
, screen_dimensions_(screen)
|
|
{
|
|
do_resolve_rects(expand_partialresolution(cfg), cfg_);
|
|
set_resolution(screen);
|
|
}
|
|
|
|
theme& theme::operator=(theme&& other)
|
|
{
|
|
theme_reset_event_ = other.theme_reset_event_;
|
|
known_themes = std::move(other.known_themes);
|
|
cur_theme = std::move(other.cur_theme);
|
|
cfg_ = std::move(other.cfg_);
|
|
panels_ = std::move(other.panels_);
|
|
labels_ = std::move(other.labels_);
|
|
menus_ = std::move(other.menus_);
|
|
actions_ = std::move(other.actions_);
|
|
sliders_ = std::move(other.sliders_);
|
|
context_ = other.context_;
|
|
action_context_ = other.action_context_;
|
|
status_ = std::move(other.status_);
|
|
main_map_ = other.main_map_;
|
|
mini_map_ = other.mini_map_;
|
|
unit_image_ = other.unit_image_;
|
|
palette_ = other.palette_;
|
|
border_ = other.border_;
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool theme::set_resolution(const SDL_Rect& screen)
|
|
{
|
|
screen_dimensions_ = screen;
|
|
|
|
bool result = false;
|
|
|
|
int current_rating = 1000000;
|
|
const config* current = nullptr;
|
|
for(const config& i : cfg_.child_range("resolution")) {
|
|
int width = i["width"];
|
|
int height = i["height"];
|
|
LOG_DP << "comparing resolution " << screen.w << "," << screen.h << " to " << width << "," << height << "\n";
|
|
if(screen.w >= width && screen.h >= height) {
|
|
LOG_DP << "loading theme: " << width << "," << height << "\n";
|
|
current = &i;
|
|
result = true;
|
|
break;
|
|
}
|
|
|
|
const int rating = width * height;
|
|
if(rating < current_rating) {
|
|
current = &i;
|
|
current_rating = rating;
|
|
}
|
|
}
|
|
|
|
if(!current) {
|
|
if(cfg_.child_count("resolution")) {
|
|
ERR_DP << "No valid resolution found" << std::endl;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::map<std::string, std::string> title_stash_menus;
|
|
std::vector<theme::menu>::iterator m;
|
|
for(m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(!m->title().empty() && !m->get_id().empty())
|
|
title_stash_menus[m->get_id()] = m->title();
|
|
}
|
|
|
|
std::map<std::string, std::string> title_stash_actions;
|
|
std::vector<theme::action>::iterator a;
|
|
for(a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(!a->title().empty() && !a->get_id().empty())
|
|
title_stash_actions[a->get_id()] = a->title();
|
|
}
|
|
|
|
panels_.clear();
|
|
labels_.clear();
|
|
status_.clear();
|
|
menus_.clear();
|
|
actions_.clear();
|
|
sliders_.clear();
|
|
|
|
add_object(*current);
|
|
|
|
for(m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(title_stash_menus.find(m->get_id()) != title_stash_menus.end())
|
|
m->set_title(title_stash_menus[m->get_id()]);
|
|
}
|
|
|
|
for(a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(title_stash_actions.find(a->get_id()) != title_stash_actions.end())
|
|
a->set_title(title_stash_actions[a->get_id()]);
|
|
}
|
|
|
|
theme_reset_event_.notify_observers();
|
|
|
|
return result;
|
|
}
|
|
|
|
void theme::add_object(const config& cfg)
|
|
{
|
|
if(const config& c = cfg.child("main_map")) {
|
|
main_map_ = object(c);
|
|
}
|
|
|
|
if(const config& c = cfg.child("mini_map")) {
|
|
mini_map_ = object(c);
|
|
}
|
|
|
|
if(const config& c = cfg.child("palette")) {
|
|
palette_ = object(c);
|
|
}
|
|
|
|
if(const config& status_cfg = cfg.child("status")) {
|
|
for(const config::any_child& i : status_cfg.all_children_range()) {
|
|
status_[i.key].reset(new status_item(i.cfg));
|
|
}
|
|
if(const config& unit_image_cfg = status_cfg.child("unit_image")) {
|
|
unit_image_ = object(unit_image_cfg);
|
|
} else {
|
|
unit_image_ = object();
|
|
}
|
|
}
|
|
|
|
for(const config& p : cfg.child_range("panel")) {
|
|
panel new_panel(p);
|
|
set_object_location(new_panel, p["rect"], p["ref"]);
|
|
panels_.push_back(new_panel);
|
|
}
|
|
|
|
for(const config& lb : cfg.child_range("label")) {
|
|
label new_label(lb);
|
|
set_object_location(new_label, lb["rect"], lb["ref"]);
|
|
labels_.push_back(new_label);
|
|
}
|
|
|
|
for(const config& m : cfg.child_range("menu")) {
|
|
menu new_menu(m);
|
|
DBG_DP << "adding menu: " << (new_menu.is_context() ? "is context" : "not context") << "\n";
|
|
if(new_menu.is_context())
|
|
context_ = new_menu;
|
|
else {
|
|
set_object_location(new_menu, m["rect"], m["ref"]);
|
|
menus_.push_back(new_menu);
|
|
}
|
|
|
|
DBG_DP << "done adding menu...\n";
|
|
}
|
|
|
|
for(const config& a : cfg.child_range("action")) {
|
|
action new_action(a);
|
|
DBG_DP << "adding action: " << (new_action.is_context() ? "is context" : "not context") << "\n";
|
|
if(new_action.is_context())
|
|
action_context_ = new_action;
|
|
else {
|
|
set_object_location(new_action, a["rect"], a["ref"]);
|
|
actions_.push_back(new_action);
|
|
}
|
|
|
|
DBG_DP << "done adding action...\n";
|
|
}
|
|
|
|
for(const config& s : cfg.child_range("slider")) {
|
|
slider new_slider(s);
|
|
DBG_DP << "adding slider\n";
|
|
set_object_location(new_slider, s["rect"], s["ref"]);
|
|
sliders_.push_back(new_slider);
|
|
|
|
DBG_DP << "done adding slider...\n";
|
|
}
|
|
|
|
if(const config& c = cfg.child("main_map_border")) {
|
|
border_ = border_t(c);
|
|
}
|
|
|
|
// Battery charge indicator is always hidden if there isn't enough horizontal space
|
|
// (GitHub issue #3714)
|
|
static const int BATTERY_ICON_MIN_WIDTH = 1152;
|
|
if(!desktop::battery_info::does_device_have_battery() || screen_dimensions_.w < BATTERY_ICON_MIN_WIDTH) {
|
|
if(const config& c = cfg.child("no_battery")) {
|
|
modify(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void theme::remove_object(const std::string& id)
|
|
{
|
|
if(status_.erase(id) > 0u) {
|
|
return;
|
|
}
|
|
|
|
for(auto p = panels_.begin(); p != panels_.end(); ++p) {
|
|
if(p->get_id() == id) {
|
|
panels_.erase(p);
|
|
return;
|
|
}
|
|
}
|
|
for(auto l = labels_.begin(); l != labels_.end(); ++l) {
|
|
if(l->get_id() == id) {
|
|
labels_.erase(l);
|
|
return;
|
|
}
|
|
}
|
|
for(auto m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(m->get_id() == id) {
|
|
menus_.erase(m);
|
|
return;
|
|
}
|
|
}
|
|
for(auto a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(a->get_id() == id) {
|
|
actions_.erase(a);
|
|
return;
|
|
}
|
|
}
|
|
for(auto s = sliders_.begin(); s != sliders_.end(); ++s) {
|
|
if(s->get_id() == id) {
|
|
sliders_.erase(s);
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::stringstream stream;
|
|
stream << "theme object " << id << " not found";
|
|
throw config::error(stream.str());
|
|
}
|
|
|
|
void theme::set_object_location(theme::object& element, std::string rect_str, std::string ref_id)
|
|
{
|
|
theme::object ref_element = element;
|
|
if(ref_id.empty()) {
|
|
ref_id = element.get_id();
|
|
} else {
|
|
ref_element = find_element(ref_id);
|
|
}
|
|
if(ref_element.get_id() == ref_id) {
|
|
SDL_Rect location_ref_rect = ref_element.get_location();
|
|
element.modify_location(rect_str, location_ref_rect);
|
|
}
|
|
}
|
|
|
|
void theme::modify(const config& cfg)
|
|
{
|
|
std::map<std::string, std::string> title_stash;
|
|
std::vector<theme::menu>::iterator m;
|
|
for(m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(!m->title().empty() && !m->get_id().empty())
|
|
title_stash[m->get_id()] = m->title();
|
|
}
|
|
|
|
std::vector<theme::action>::iterator a;
|
|
for(a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(!a->title().empty() && !a->get_id().empty())
|
|
title_stash[a->get_id()] = a->title();
|
|
}
|
|
|
|
// Change existing theme objects.
|
|
for(const config& c : cfg.child_range("change")) {
|
|
std::string id = c["id"];
|
|
std::string ref_id = c["ref"];
|
|
theme::object& element = find_element(id);
|
|
if(element.get_id() == id)
|
|
set_object_location(element, c["rect"], ref_id);
|
|
}
|
|
|
|
// Add new theme objects.
|
|
for(const config& c : cfg.child_range("add")) {
|
|
add_object(c);
|
|
}
|
|
|
|
// Remove existent theme objects.
|
|
for(const config& c : cfg.child_range("remove")) {
|
|
remove_object(c["id"]);
|
|
}
|
|
|
|
for(m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(title_stash.find(m->get_id()) != title_stash.end())
|
|
m->set_title(title_stash[m->get_id()]);
|
|
}
|
|
for(a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(title_stash.find(a->get_id()) != title_stash.end())
|
|
a->set_title(title_stash[a->get_id()]);
|
|
}
|
|
}
|
|
|
|
theme::object& theme::find_element(const std::string& id)
|
|
{
|
|
static theme::object empty_object;
|
|
theme::object* res = &empty_object;
|
|
|
|
auto status_item_it = status_.find(id);
|
|
if(status_item_it != status_.end()) {
|
|
res = status_item_it->second.get();
|
|
}
|
|
|
|
for(std::vector<theme::panel>::iterator p = panels_.begin(); p != panels_.end(); ++p) {
|
|
if(p->get_id() == id) {
|
|
res = &(*p);
|
|
}
|
|
}
|
|
for(std::vector<theme::label>::iterator l = labels_.begin(); l != labels_.end(); ++l) {
|
|
if(l->get_id() == id) {
|
|
res = &(*l);
|
|
}
|
|
}
|
|
for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(m->get_id() == id) {
|
|
res = &(*m);
|
|
}
|
|
}
|
|
for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(a->get_id() == id) {
|
|
res = &(*a);
|
|
}
|
|
}
|
|
if(id == "main-map") {
|
|
res = &main_map_;
|
|
}
|
|
if(id == "mini-map") {
|
|
res = &mini_map_;
|
|
}
|
|
if(id == "palette") {
|
|
res = &palette_;
|
|
}
|
|
if(id == "unit-image") {
|
|
res = &unit_image_;
|
|
}
|
|
return *res;
|
|
}
|
|
|
|
const theme::status_item* theme::get_status_item(const std::string& key) const
|
|
{
|
|
const auto& i = status_.find(key);
|
|
if(i != status_.end())
|
|
return i->second.get();
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
typedef std::map<std::string, config> known_themes_map;
|
|
known_themes_map theme::known_themes;
|
|
|
|
void theme::set_known_themes(const config* cfg)
|
|
{
|
|
known_themes.clear();
|
|
if(!cfg)
|
|
return;
|
|
|
|
for(const config& thm : cfg->child_range("theme")) {
|
|
std::string thm_id = thm["id"];
|
|
|
|
if(!thm["hidden"].to_bool(false)) {
|
|
known_themes[thm_id] = thm;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<theme_info> theme::get_known_themes()
|
|
{
|
|
std::vector<theme_info> res;
|
|
|
|
for(known_themes_map::const_iterator i = known_themes.begin(); i != known_themes.end(); ++i) {
|
|
res.push_back(theme_info());
|
|
res.back().id = i->first;
|
|
res.back().name = i->second["name"].t_str();
|
|
res.back().description = i->second["description"].t_str();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
const theme::menu* theme::get_menu_item(const std::string& key) const
|
|
{
|
|
for(const theme::menu& m : menus_) {
|
|
if(m.get_id() == key)
|
|
return &m;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const theme::action* theme::get_action_item(const std::string& key) const
|
|
{
|
|
for(const theme::action& a : actions_) {
|
|
if(a.get_id() == key)
|
|
return &a;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
theme::object* theme::refresh_title(const std::string& id, const std::string& new_title)
|
|
{
|
|
theme::object* res = nullptr;
|
|
|
|
for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
|
|
if(a->get_id() == id) {
|
|
res = &(*a);
|
|
a->set_title(new_title);
|
|
}
|
|
}
|
|
|
|
for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
|
|
if(m->get_id() == id) {
|
|
res = &(*m);
|
|
m->set_title(new_title);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
theme::object* theme::refresh_title2(const std::string& id, const std::string& title_tag)
|
|
{
|
|
std::string new_title;
|
|
|
|
const config& cfg = find_ref(id, cfg_, false);
|
|
if(!cfg[title_tag].empty())
|
|
new_title = cfg[title_tag].str();
|
|
|
|
return refresh_title(id, new_title);
|
|
}
|
|
|
|
void theme::modify_label(const std::string& id, const std::string& text)
|
|
{
|
|
theme::label* label = dynamic_cast<theme::label*>(&find_element(id));
|
|
if(!label) {
|
|
LOG_DP << "Theme contains no label called '" << id << "'.\n";
|
|
return;
|
|
}
|
|
label->set_text(text);
|
|
}
|