support loading gui2 themes from add-ons
Requires a `gui-theme.cfg` file in Add-on root with a `[gui]` tag. (Add-ons without all buttons in title_screen may get flagged as defunct.)
This commit is contained in:
parent
37a0828428
commit
59911072aa
10 changed files with 166 additions and 70 deletions
5
changelog_entries/gui2-theme-support-in-addons.md
Normal file
5
changelog_entries/gui2-theme-support-in-addons.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
### User interface
|
||||
GUI2 themes can be loaded from add-ons. Requires a `gui-theme.cfg` file in add-on root with a `[gui]` tag that acts as the entry point for the theme.
|
||||
|
||||
### Lua API
|
||||
Added new function gui.switch_theme() to allow switching to another gui2 theme from inside a scenario.
|
|
@ -867,6 +867,8 @@
|
|||
min="0"
|
||||
max="infinite"
|
||||
super="$generic/widget_definition"
|
||||
{REQUIRED_KEY "id" string}
|
||||
{REQUIRED_KEY "description" string}
|
||||
[tag]
|
||||
name="resolution"
|
||||
min="0"
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "game_initialization/multiplayer.hpp"
|
||||
#include "generators/map_generator.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "gui/gui.hpp"
|
||||
#include "gui/dialogs/message.hpp"
|
||||
#include "gui/dialogs/outro.hpp"
|
||||
#include "gui/widgets/retval.hpp"
|
||||
|
@ -204,7 +205,6 @@ level_result::type campaign_controller::play_game()
|
|||
gui2::dialogs::outro::display(state_.classification());
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
} else if(res == level_result::type::observer_end && mp_info_ && !mp_info_->is_host) {
|
||||
const int dlg_res = gui2::show_message(_("Game Over"),
|
||||
|
@ -291,3 +291,10 @@ level_result::type campaign_controller::play_game()
|
|||
|
||||
return level_result::type::victory;
|
||||
}
|
||||
|
||||
campaign_controller::~campaign_controller()
|
||||
{
|
||||
// If the scenario changed the current gui2 theme,
|
||||
// change it back to the value stored in preferences
|
||||
gui2::switch_theme(prefs::get().gui2_theme());
|
||||
}
|
||||
|
|
|
@ -56,6 +56,8 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
~campaign_controller();
|
||||
|
||||
level_result::type play_game();
|
||||
level_result::type play_replay()
|
||||
{
|
||||
|
|
|
@ -86,18 +86,19 @@ title_screen::~title_screen()
|
|||
{
|
||||
}
|
||||
|
||||
using btn_callback = std::function<void()>;
|
||||
|
||||
static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
|
||||
void title_screen::register_button(const std::string& id, hotkey::HOTKEY_COMMAND hk, std::function<void()> callback)
|
||||
{
|
||||
if(hk != hotkey::HOTKEY_NULL) {
|
||||
win.register_hotkey(hk, std::bind(callback));
|
||||
register_hotkey(hk, std::bind(callback));
|
||||
}
|
||||
|
||||
auto b = find_widget<button>(&win, id, false, false);
|
||||
if(b != nullptr)
|
||||
{
|
||||
connect_signal_mouse_left_click(*b, std::bind(callback));
|
||||
try {
|
||||
button& btn = find_widget<button>(this, id, false);
|
||||
connect_signal_mouse_left_click(btn, std::bind(callback));
|
||||
} catch(const wml_exception& e) {
|
||||
ERR_GUI_P << e.user_message;
|
||||
prefs::get().set_gui2_theme("default");
|
||||
set_retval(RELOAD_UI);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,16 +236,16 @@ void title_screen::init_callbacks()
|
|||
update_tip(true);
|
||||
}
|
||||
|
||||
register_button(*this, "next_tip", hotkey::TITLE_SCREEN__NEXT_TIP,
|
||||
register_button("next_tip", hotkey::TITLE_SCREEN__NEXT_TIP,
|
||||
std::bind(&title_screen::update_tip, this, true));
|
||||
|
||||
register_button(*this, "previous_tip", hotkey::TITLE_SCREEN__PREVIOUS_TIP,
|
||||
register_button("previous_tip", hotkey::TITLE_SCREEN__PREVIOUS_TIP,
|
||||
std::bind(&title_screen::update_tip, this, false));
|
||||
|
||||
//
|
||||
// Help
|
||||
//
|
||||
register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
|
||||
register_button("help", hotkey::HOTKEY_HELP, []() {
|
||||
if(gui2::new_widgets) {
|
||||
gui2::dialogs::help_browser::display();
|
||||
}
|
||||
|
@ -255,12 +256,12 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// About
|
||||
//
|
||||
register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
|
||||
register_button("about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
|
||||
|
||||
//
|
||||
// Campaign
|
||||
//
|
||||
register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
|
||||
register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
|
||||
try{
|
||||
if(game_.new_campaign()) {
|
||||
// Suspend drawing of the title screen,
|
||||
|
@ -276,13 +277,13 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// Multiplayer
|
||||
//
|
||||
register_button(*this, "multiplayer", hotkey::TITLE_SCREEN__MULTIPLAYER,
|
||||
register_button("multiplayer", hotkey::TITLE_SCREEN__MULTIPLAYER,
|
||||
std::bind(&title_screen::button_callback_multiplayer, this));
|
||||
|
||||
//
|
||||
// Load game
|
||||
//
|
||||
register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
|
||||
register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
|
||||
if(game_.load_game()) {
|
||||
// Suspend drawing of the title screen,
|
||||
// so it doesn't flicker in between loading screens.
|
||||
|
@ -294,7 +295,7 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// Addons
|
||||
//
|
||||
register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
|
||||
register_button("addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
|
||||
if(manage_addons()) {
|
||||
set_retval(RELOAD_GAME_DATA);
|
||||
}
|
||||
|
@ -303,7 +304,7 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// Editor
|
||||
//
|
||||
register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
|
||||
register_button("editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
|
||||
|
||||
//
|
||||
// Cores
|
||||
|
@ -314,7 +315,7 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// Language
|
||||
//
|
||||
register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
|
||||
register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
|
||||
try {
|
||||
if(game_.change_language()) {
|
||||
on_resize();
|
||||
|
@ -328,53 +329,32 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// Preferences
|
||||
//
|
||||
register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, [this]() {
|
||||
gui2::dialogs::preferences_dialog pref_dlg;
|
||||
pref_dlg.show();
|
||||
if (pref_dlg.get_retval() == RELOAD_UI) {
|
||||
set_retval(RELOAD_UI);
|
||||
}
|
||||
|
||||
// Currently blurred windows don't capture well if there is something
|
||||
// on top of them at the time of blur. Resizing the game window in
|
||||
// preferences will cause the title screen tip and menu panels to
|
||||
// capture the prefs dialog in their blur. This workaround simply
|
||||
// forces them to re-capture the blur after the dialog closes.
|
||||
panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
|
||||
if(tip_panel != nullptr) {
|
||||
tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
|
||||
tip_panel->queue_redraw();
|
||||
}
|
||||
panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
|
||||
if(menu_panel != nullptr) {
|
||||
menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
|
||||
menu_panel->queue_redraw();
|
||||
}
|
||||
});
|
||||
register_button("preferences", hotkey::HOTKEY_PREFERENCES,
|
||||
std::bind(&title_screen::show_preferences, this));
|
||||
|
||||
//
|
||||
// Achievements
|
||||
//
|
||||
register_button(*this, "achievements", hotkey::HOTKEY_ACHIEVEMENTS,
|
||||
register_button("achievements", hotkey::HOTKEY_ACHIEVEMENTS,
|
||||
std::bind(&title_screen::show_achievements, this));
|
||||
|
||||
//
|
||||
// Community
|
||||
//
|
||||
register_button(*this, "community", hotkey::HOTKEY_NULL,
|
||||
register_button("community", hotkey::HOTKEY_NULL,
|
||||
std::bind(&title_screen::show_community, this));
|
||||
|
||||
//
|
||||
// Quit
|
||||
//
|
||||
register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
|
||||
register_button("quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
|
||||
// A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
|
||||
find_widget<button>(this, "quit", false, true);
|
||||
|
||||
//
|
||||
// Debug clock
|
||||
//
|
||||
register_button(*this, "clock", hotkey::HOTKEY_NULL,
|
||||
register_button("clock", hotkey::HOTKEY_NULL,
|
||||
std::bind(&title_screen::show_debug_clock_window, this));
|
||||
|
||||
auto clock = find_widget<button>(this, "clock", false, false);
|
||||
|
@ -385,7 +365,7 @@ void title_screen::init_callbacks()
|
|||
//
|
||||
// GUI Test and Debug Window
|
||||
//
|
||||
register_button(*this, "test_dialog", hotkey::HOTKEY_NULL,
|
||||
register_button("test_dialog", hotkey::HOTKEY_NULL,
|
||||
std::bind(&title_screen::show_gui_test_dialog, this));
|
||||
|
||||
auto test_dialog = find_widget<button>(this, "test_dialog", false, false);
|
||||
|
@ -484,11 +464,6 @@ void title_screen::show_debug_clock_window()
|
|||
}
|
||||
}
|
||||
|
||||
void title_screen::show_gui_test_dialog()
|
||||
{
|
||||
gui2::dialogs::gui_test_dialog::execute();
|
||||
}
|
||||
|
||||
void title_screen::hotkey_callback_select_tests()
|
||||
{
|
||||
game_config_manager::get()->load_game_config_for_create(false, true);
|
||||
|
@ -525,6 +500,36 @@ void title_screen::show_community()
|
|||
dlg.display(4);
|
||||
}
|
||||
|
||||
void title_screen::show_gui_test_dialog()
|
||||
{
|
||||
gui2::dialogs::gui_test_dialog::execute();
|
||||
}
|
||||
|
||||
void title_screen::show_preferences()
|
||||
{
|
||||
gui2::dialogs::preferences_dialog pref_dlg;
|
||||
pref_dlg.show();
|
||||
if (pref_dlg.get_retval() == RELOAD_UI) {
|
||||
set_retval(RELOAD_UI);
|
||||
}
|
||||
|
||||
// Currently blurred windows don't capture well if there is something
|
||||
// on top of them at the time of blur. Resizing the game window in
|
||||
// preferences will cause the title screen tip and menu panels to
|
||||
// capture the prefs dialog in their blur. This workaround simply
|
||||
// forces them to re-capture the blur after the dialog closes.
|
||||
panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
|
||||
if(tip_panel != nullptr) {
|
||||
tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
|
||||
tip_panel->queue_redraw();
|
||||
}
|
||||
panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
|
||||
if(menu_panel != nullptr) {
|
||||
menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
|
||||
menu_panel->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void title_screen::button_callback_multiplayer()
|
||||
{
|
||||
while(true) {
|
||||
|
|
|
@ -71,6 +71,8 @@ private:
|
|||
|
||||
void init_callbacks();
|
||||
|
||||
void register_button(const std::string& id, hotkey::HOTKEY_COMMAND hk, std::function<void()> callback);
|
||||
|
||||
/***** ***** ***** ***** Callbacks ***** ***** ****** *****/
|
||||
|
||||
void on_resize();
|
||||
|
@ -91,6 +93,9 @@ private:
|
|||
/** Shows the gui test window. */
|
||||
void show_gui_test_dialog();
|
||||
|
||||
/** Shows the preferences dialog. */
|
||||
void show_preferences();
|
||||
|
||||
void hotkey_callback_select_tests();
|
||||
|
||||
void show_achievements();
|
||||
|
|
|
@ -38,34 +38,94 @@ void init()
|
|||
// Save current screen size.
|
||||
settings::update_screen_size_variables();
|
||||
|
||||
//
|
||||
// Read and validate the WML files.
|
||||
//
|
||||
config guis_cfg;
|
||||
try {
|
||||
schema_validation::schema_validator validator(filesystem::get_wml_location("schema/gui.cfg").value());
|
||||
config guis_cfg, addons_cfg;
|
||||
preproc_map preproc(game_config::config_cache::instance().get_preproc_map());
|
||||
|
||||
preproc_map preproc(game_config::config_cache::instance().get_preproc_map());
|
||||
filesystem::scoped_istream stream = preprocess_file(filesystem::get_wml_location("gui/_main.cfg").value(), &preproc);
|
||||
//
|
||||
// Read and validate theme WML files from mainline
|
||||
//
|
||||
std::string current_file;
|
||||
const std::string schema_file = "schema/gui.cfg";
|
||||
try {
|
||||
schema_validation::schema_validator validator(filesystem::get_wml_location(schema_file).value());
|
||||
|
||||
// Core theme files
|
||||
current_file = "gui/_main.cfg";
|
||||
filesystem::scoped_istream stream = preprocess_file(filesystem::get_wml_location(current_file).value(), &preproc);
|
||||
read(guis_cfg, *stream, &validator);
|
||||
|
||||
} catch(const config::error& e) {
|
||||
ERR_GUI_P << e.what();
|
||||
ERR_GUI_P << "Setting: could not read file 'data/gui/_main.cfg'.";
|
||||
ERR_GUI_P << "Setting: could not read gui file: " << current_file;
|
||||
} catch(const abstract_validator::error& e) {
|
||||
ERR_GUI_P << "Setting: could not read file 'data/schema/gui.cfg'.";
|
||||
ERR_GUI_P << "Setting: could not read schema file: " << schema_file;
|
||||
ERR_GUI_P << e.message;
|
||||
}
|
||||
|
||||
//
|
||||
// Parse GUI definitions.
|
||||
// Read and validate theme WML files from addons
|
||||
//
|
||||
|
||||
// Add the $user_campaign_dir/*/gui.cfg files to the addon gui config.
|
||||
std::vector<std::string> user_dirs;
|
||||
{
|
||||
const std::string user_campaign_dir = filesystem::get_addons_dir();
|
||||
std::vector<std::string> user_files;
|
||||
filesystem::get_files_in_dir(
|
||||
user_campaign_dir, &user_files, &user_dirs, filesystem::name_mode::ENTIRE_FILE_PATH);
|
||||
}
|
||||
|
||||
for(const std::string& umc : user_dirs) {
|
||||
try {
|
||||
const std::string gui_file = umc + "/gui-theme.cfg";
|
||||
current_file = filesystem::get_short_wml_path(gui_file);
|
||||
if(filesystem::file_exists(gui_file)) {
|
||||
config addon_cfg;
|
||||
schema_validation::schema_validator validator(filesystem::get_wml_location(schema_file).value());
|
||||
read(addon_cfg, *preprocess_file(gui_file, &preproc), &validator);
|
||||
addons_cfg.append(addon_cfg);
|
||||
}
|
||||
} catch(const config::error& e) {
|
||||
ERR_GUI_P << e.what();
|
||||
ERR_GUI_P << "Setting: could not read gui file: " << current_file;
|
||||
} catch(const abstract_validator::error& e) {
|
||||
ERR_GUI_P << "Setting: could not read schema file: " << schema_file;
|
||||
ERR_GUI_P << e.message;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parse GUI definitions from mainline
|
||||
//
|
||||
for(const config& g : guis_cfg.child_range("gui")) {
|
||||
const std::string id = g["id"];
|
||||
|
||||
auto iter = guis.emplace(id, gui_definition(g)).first;
|
||||
auto [iter, is_unique] = guis.try_emplace(id, g);
|
||||
|
||||
if(id == "default") {
|
||||
default_gui = iter;
|
||||
if (!is_unique) {
|
||||
ERR_GUI_P << "GUI Theme ID '" << id << "' already exists.";
|
||||
} else {
|
||||
if(id == "default") {
|
||||
default_gui = iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parse GUI definitions from addons
|
||||
//
|
||||
for(const config& g : addons_cfg.child_range("gui")) {
|
||||
const std::string id = g["id"];
|
||||
|
||||
try {
|
||||
auto [iter, is_unique] = guis.try_emplace(id, g);
|
||||
|
||||
if (!is_unique) {
|
||||
ERR_GUI_P << "GUI Theme ID '" << id << "' already exists.";
|
||||
}
|
||||
} catch (const wml_exception& e) {
|
||||
ERR_GUI_P << "Non-functional theme: " << id;
|
||||
ERR_GUI_P << e.user_message;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "hotkey/command_executor.hpp"
|
||||
#include "hotkey/hotkey_item.hpp"
|
||||
|
||||
#include "gui/gui.hpp"
|
||||
#include "gui/dialogs/achievements_dialog.hpp"
|
||||
#include "gui/dialogs/lua_interpreter.hpp"
|
||||
#include "gui/dialogs/message.hpp"
|
||||
|
@ -361,6 +362,7 @@ bool command_executor::do_execute_command(const hotkey::ui_command& cmd, bool pr
|
|||
quit_confirmation::quit_to_desktop();
|
||||
break;
|
||||
case HOTKEY_QUIT_GAME:
|
||||
gui2::switch_theme(prefs::get().gui2_theme());
|
||||
quit_confirmation::quit_to_title();
|
||||
break;
|
||||
case HOTKEY_SURRENDER:
|
||||
|
|
|
@ -418,6 +418,10 @@ void schema_validator::close_tag()
|
|||
|
||||
void schema_validator::print_cache()
|
||||
{
|
||||
if (cache_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto& m : cache_.top()) {
|
||||
for(auto& list : m.second) {
|
||||
print(list);
|
||||
|
@ -432,10 +436,12 @@ void schema_validator::validate(const config& cfg, const std::string& name, int
|
|||
// close previous errors and print them to output.
|
||||
print_cache();
|
||||
|
||||
// clear cache
|
||||
auto cache_it = cache_.top().find(&cfg);
|
||||
if(cache_it != cache_.top().end()) {
|
||||
cache_it->second.clear();
|
||||
if (!cache_.empty()) {
|
||||
// clear cache
|
||||
auto cache_it = cache_.top().find(&cfg);
|
||||
if(cache_it != cache_.top().end()) {
|
||||
cache_it->second.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Please note that validating unknown tag keys the result will be false
|
||||
|
|
|
@ -882,6 +882,8 @@ static int do_gameloop(commandline_options& cmdline_opts)
|
|||
case gui2::dialogs::title_screen::RELOAD_GAME_DATA:
|
||||
gui2::dialogs::loading_screen::display([&config_manager]() {
|
||||
config_manager.reload_changed_game_config();
|
||||
gui2::init();
|
||||
gui2::switch_theme(prefs::get().gui2_theme());
|
||||
});
|
||||
break;
|
||||
case gui2::dialogs::title_screen::MAP_EDITOR:
|
||||
|
|
Loading…
Add table
Reference in a new issue