From 795718d72eff472c6f49c2911d60a02a398dbf70 Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 8 Nov 2023 22:24:21 +0000 Subject: [PATCH] UI element generator + very basic province panel --- extension/deps/openvic-simulation | 2 +- .../src/openvic-extension/AssetManager.cpp | 132 ++++++ .../src/openvic-extension/AssetManager.hpp | 40 ++ .../src/openvic-extension/GameSingleton.cpp | 174 +++----- .../src/openvic-extension/GameSingleton.hpp | 30 +- .../openvic-extension/LoadLocalisation.cpp | 10 +- extension/src/openvic-extension/UIAdapter.cpp | 416 ++++++++++++++++++ extension/src/openvic-extension/UIAdapter.hpp | 19 + extension/src/openvic-extension/Utilities.cpp | 50 ++- extension/src/openvic-extension/Utilities.hpp | 3 + .../{ => classes}/MapMesh.cpp | 0 .../{ => classes}/MapMesh.hpp | 0 .../src/openvic-extension/register_types.cpp | 2 +- game/src/Game/GameSession/GameSession.gd | 1 + game/src/Game/GameSession/GameSession.tscn | 17 +- .../ProvinceOverviewPanel.gd | 171 +++++-- .../ProvinceOverviewPanel.tscn | 124 ------ 17 files changed, 852 insertions(+), 339 deletions(-) create mode 100644 extension/src/openvic-extension/AssetManager.cpp create mode 100644 extension/src/openvic-extension/AssetManager.hpp create mode 100644 extension/src/openvic-extension/UIAdapter.cpp create mode 100644 extension/src/openvic-extension/UIAdapter.hpp rename extension/src/openvic-extension/{ => classes}/MapMesh.cpp (100%) rename extension/src/openvic-extension/{ => classes}/MapMesh.hpp (100%) delete mode 100644 game/src/Game/GameSession/ProvinceOverviewPanel/ProvinceOverviewPanel.tscn diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index a428ca4d..b7744724 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit a428ca4d6fdc950800aa6986694aed6eaaa54e14 +Subproject commit b7744724140d44715a23287f19edf7888191247c diff --git a/extension/src/openvic-extension/AssetManager.cpp b/extension/src/openvic-extension/AssetManager.cpp new file mode 100644 index 00000000..665f1c6b --- /dev/null +++ b/extension/src/openvic-extension/AssetManager.cpp @@ -0,0 +1,132 @@ +#include "AssetManager.hpp" + +#include + +#include "openvic-extension/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +using OpenVic::Utilities::std_to_godot_string; +using OpenVic::Utilities::godot_to_std_string; + +AssetManager::AssetManager(Dataloader const& new_dataloader) : dataloader { new_dataloader } {} + +AssetManager::image_asset_map_t::iterator AssetManager::_get_image_asset(StringName path) { + const image_asset_map_t::iterator it = image_assets.find(path); + if (it != image_assets.end()) { + return it; + } + const String lookedup_path = std_to_godot_string(dataloader.lookup_image_file(godot_to_std_string(path)).string()); + if (lookedup_path.is_empty()) { + UtilityFunctions::push_error("Failed to look up image: ", path); + return image_assets.end(); + } + const Ref image = Utilities::load_godot_image(lookedup_path); + if (image.is_null() || image->is_empty()) { + UtilityFunctions::push_error("Failed to load image: ", lookedup_path, " (looked up from ", path, ")"); + return image_assets.end(); + } + return image_assets.emplace(std::move(path), AssetManager::image_asset_t { image, nullptr }).first; +} + +Ref AssetManager::get_image(StringName path) { + const image_asset_map_t::const_iterator it = _get_image_asset(path); + if (it != image_assets.end()) { + return it->second.image; + } else { + return nullptr; + } +} + +Ref AssetManager::get_texture(StringName path) { + const image_asset_map_t::iterator it = _get_image_asset(path); + if (it != image_assets.end()) { + if (it->second.texture.is_null()) { + it->second.texture = ImageTexture::create_from_image(it->second.image); + if (it->second.texture.is_null()) { + UtilityFunctions::push_error("Failed to turn image into texture: ", path); + } + } + return it->second.texture; + } else { + return nullptr; + } +} + +Rect2i AssetManager::get_frame_region(GFX::frame_t frame, GFX::frame_t frame_count, Vector2i size) { + if (frame_count <= GFX::NO_FRAMES) { + UtilityFunctions::push_warning("No frames!"); + frame_count = 1; + } + if (frame <= GFX::NO_FRAMES || frame > frame_count) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); + frame = frame_count; + } + frame--; + return { frame * size.x / frame_count, 0, size.x / frame_count, size.y }; +} + +Ref AssetManager::make_atlas_texture(Ref texture, Rect2i region) { + ERR_FAIL_NULL_V(texture, nullptr); + Ref atlas; + atlas.instantiate(); + ERR_FAIL_NULL_V(atlas, nullptr); + atlas->set_atlas(texture); + atlas->set_region(region); + return atlas; +} + +Ref AssetManager::get_atlas_texture(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { + Ref texture = get_texture(path); + ERR_FAIL_NULL_V(texture, nullptr); + return make_atlas_texture(texture, get_frame_region(frame, frame_count, texture->get_size())); +} + +Ref AssetManager::get_texture(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { + if (frame_count < 2) { + if (frame_count == 1) { + if (frame != 1) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); + } + } else { + if (frame_count > GFX::NO_FRAMES) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of no frames "); + } + } + return get_texture(path); + } else { + if (frame <= GFX::NO_FRAMES || frame > frame_count) { + UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); + frame = frame_count; + } + return get_atlas_texture(path, frame, frame_count); + } +} + +Ref AssetManager::get_font(StringName name) { + const font_map_t::const_iterator it = fonts.find(name); + if (it != fonts.end()) { + return it->second; + } + + static const String font_dir = "gfx/fonts/"; + static const String font_ext = ".fnt"; + static const String image_ext = ".tga"; + + const String image_path = font_dir + name + image_ext; + const Ref image = get_image(image_path); + if (image.is_null()) { + UtilityFunctions::push_error("Failed to load font image: ", image_path, " for the font named ", name); + return nullptr; + } + const String lookedup_font_path = + std_to_godot_string(dataloader.lookup_file(godot_to_std_string(font_dir + name + font_ext)).string()); + const Ref font = Utilities::load_godot_font(lookedup_font_path, image); + if (font.is_null()) { + UtilityFunctions::push_error("Failed to load font file ", lookedup_font_path, " for the font named ", name); + return nullptr; + } + fonts.emplace(std::move(name), font); + return font; +} diff --git a/extension/src/openvic-extension/AssetManager.hpp b/extension/src/openvic-extension/AssetManager.hpp new file mode 100644 index 00000000..2b6c25e7 --- /dev/null +++ b/extension/src/openvic-extension/AssetManager.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace OpenVic { + + class AssetManager { + struct image_asset_t { + godot::Ref image; + godot::Ref texture; + }; + using image_asset_map_t = std::map; + using font_map_t = std::map>; + + Dataloader const& dataloader; + image_asset_map_t image_assets; + font_map_t fonts; + + image_asset_map_t::iterator _get_image_asset(godot::StringName path); + + public: + AssetManager(Dataloader const& new_dataloader); + + godot::Ref get_image(godot::StringName path); + godot::Ref get_texture(godot::StringName path); + + static godot::Rect2i get_frame_region(GFX::frame_t frame, GFX::frame_t frame_count, godot::Vector2i size); + static godot::Ref make_atlas_texture(godot::Ref texture, godot::Rect2i region); + godot::Ref get_atlas_texture(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); + + godot::Ref get_texture(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); + + godot::Ref get_font(godot::StringName name); + }; +} diff --git a/extension/src/openvic-extension/GameSingleton.cpp b/extension/src/openvic-extension/GameSingleton.cpp index 9dae7f28..64fc1000 100644 --- a/extension/src/openvic-extension/GameSingleton.cpp +++ b/extension/src/openvic-extension/GameSingleton.cpp @@ -55,30 +55,11 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::get_longform_date); OV_BIND_METHOD(GameSingleton::try_tick); + OV_BIND_METHOD(GameSingleton::generate_gui, { "gui_file", "gui_element" }); + ADD_SIGNAL(MethodInfo("state_updated")); ADD_SIGNAL(MethodInfo("province_selected", PropertyInfo(Variant::INT, "index"))); - OV_BIND_SMETHOD(get_province_info_province_key); - OV_BIND_SMETHOD(get_province_info_region_key); - OV_BIND_SMETHOD(get_province_info_life_rating_key); - OV_BIND_SMETHOD(get_province_info_terrain_type_key); - OV_BIND_SMETHOD(get_province_info_total_population_key); - OV_BIND_SMETHOD(get_province_info_pop_types_key); - OV_BIND_SMETHOD(get_province_info_pop_ideologies_key); - OV_BIND_SMETHOD(get_province_info_pop_cultures_key); - OV_BIND_SMETHOD(get_province_info_rgo_key); - OV_BIND_SMETHOD(get_province_info_buildings_key); - - OV_BIND_SMETHOD(get_building_info_building_key); - OV_BIND_SMETHOD(get_building_info_level_key); - OV_BIND_SMETHOD(get_building_info_expansion_state_key); - OV_BIND_SMETHOD(get_building_info_start_date_key); - OV_BIND_SMETHOD(get_building_info_end_date_key); - OV_BIND_SMETHOD(get_building_info_expansion_progress_key); - - OV_BIND_SMETHOD(get_piechart_info_size_key); - OV_BIND_SMETHOD(get_piechart_info_colour_key); - OV_BIND_SMETHOD( draw_pie_chart, { "image", "stopAngles", "colours", "radius", "shadow_displacement", "shadow_tightness", "shadow_radius", @@ -89,6 +70,24 @@ void GameSingleton::_bind_methods() { OV_BIND_SMETHOD(load_image, { "path" }); } +Control* GameSingleton::generate_gui(String const& gui_file, String const& gui_element) { + GUI::Scene const* scene = game_manager.get_ui_manager().get_scene_by_identifier(godot_to_std_string(gui_file)); + if (scene == nullptr) { + UtilityFunctions::push_error("Failed to find GUI file ", gui_file); + return nullptr; + } + GUI::Element const* element = scene->get_element_by_identifier(godot_to_std_string(gui_element)); + if (element == nullptr) { + UtilityFunctions::push_error("Failed to find GUI element ", gui_element, " in GUI file ", gui_file); + return nullptr; + } + Control* result = nullptr; + if (!GodotGUIBuilder::generate_element(element, asset_manager, result)) { + UtilityFunctions::push_error("Failed to generate GUI element ", gui_element, " in GUI file ", gui_file); + } + return result; +} + void GameSingleton::draw_pie_chart( Ref image, Array const& stopAngles, Array const& colours, float radius, Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, Color trim_colour, float trim_size, @@ -117,7 +116,7 @@ void GameSingleton::_on_state_updated() { * MAP-21, MAP-23, MAP-25, MAP-32, MAP-33, MAP-34 */ GameSingleton::GameSingleton() - : game_manager { std::bind(&GameSingleton::_on_state_updated, this) } { + : game_manager { std::bind(&GameSingleton::_on_state_updated, this) }, asset_manager { dataloader } { ERR_FAIL_COND(singleton != nullptr); singleton = this; } @@ -151,89 +150,16 @@ int32_t GameSingleton::get_province_index_from_uv_coords(Vector2 const& coords) return game_manager.get_map().get_province_index_at(x_mod_w, y_mod_h); } -StringName const& GameSingleton::get_province_info_province_key() { - static const StringName key = "province"; - return key; -} -StringName const& GameSingleton::get_province_info_region_key() { - static const StringName key = "region"; - return key; -} -StringName const& GameSingleton::get_province_info_life_rating_key() { - static const StringName key = "life_rating"; - return key; -} -StringName const& GameSingleton::get_province_info_terrain_type_key() { - static const StringName key = "terrain_type"; - return key; -} -StringName const& GameSingleton::get_province_info_total_population_key() { - static const StringName key = "total_population"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_types_key() { - static const StringName key = "pop_types"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_ideologies_key() { - static const StringName key = "pop_ideologies"; - return key; -} -StringName const& GameSingleton::get_province_info_pop_cultures_key() { - static const StringName key = "pop_cultures"; - return key; -} -StringName const& GameSingleton::get_province_info_rgo_key() { - static const StringName key = "rgo"; - return key; -} -StringName const& GameSingleton::get_province_info_buildings_key() { - static const StringName key = "buildings"; - return key; -} - -StringName const& GameSingleton::get_building_info_building_key() { - static const StringName key = "building"; - return key; -} -StringName const& GameSingleton::get_building_info_level_key() { - static const StringName key = "level"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_state_key() { - static const StringName key = "expansion_state"; - return key; -} -StringName const& GameSingleton::get_building_info_start_date_key() { - static const StringName key = "start_date"; - return key; -} -StringName const& GameSingleton::get_building_info_end_date_key() { - static const StringName key = "end_date"; - return key; -} -StringName const& GameSingleton::get_building_info_expansion_progress_key() { - static const StringName key = "expansion_progress"; - return key; -} - -StringName const& GameSingleton::get_piechart_info_size_key() { - static const StringName key = "size"; - return key; -} -StringName const& GameSingleton::get_piechart_info_colour_key() { - static const StringName key = "colour"; - return key; -} - template T> static Dictionary _distribution_to_dictionary(decimal_map_t const& dist) { + static const StringName piechart_info_size_key = "size"; + static const StringName piechart_info_colour_key = "colour"; Dictionary dict; for (auto const& [key, val] : dist) { if (key != nullptr) { Dictionary sub_dict; - sub_dict[GameSingleton::get_piechart_info_size_key()] = val.to_float(); - sub_dict[GameSingleton::get_piechart_info_colour_key()] = Utilities::to_godot_color(key->get_colour()); + sub_dict[piechart_info_size_key] = val.to_float(); + sub_dict[piechart_info_colour_key] = Utilities::to_godot_color(key->get_colour()); dict[std_view_to_godot_string(key->get_identifier())] = std::move(sub_dict); } else { UtilityFunctions::push_error("Null distribution key with value ", val.to_float()); @@ -243,45 +169,63 @@ static Dictionary _distribution_to_dictionary(decimal_map_t const& dis } Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { + static const StringName province_info_province_key = "province"; + static const StringName province_info_region_key = "region"; + static const StringName province_info_life_rating_key = "life_rating"; + static const StringName province_info_terrain_type_key = "terrain_type"; + static const StringName province_info_total_population_key = "total_population"; + static const StringName province_info_pop_types_key = "pop_types"; + static const StringName province_info_pop_ideologies_key = "pop_ideologies"; + static const StringName province_info_pop_cultures_key = "pop_cultures"; + static const StringName province_info_rgo_key = "rgo"; + static const StringName province_info_buildings_key = "buildings"; + Province const* province = game_manager.get_map().get_province_by_index(index); if (province == nullptr) { return {}; } Dictionary ret; - ret[get_province_info_province_key()] = std_view_to_godot_string(province->get_identifier()); + ret[province_info_province_key] = std_view_to_godot_string(province->get_identifier()); Region const* region = province->get_region(); if (region != nullptr) { - ret[get_province_info_region_key()] = std_view_to_godot_string(region->get_identifier()); + ret[province_info_region_key] = std_view_to_godot_string(region->get_identifier()); } Good const* rgo = province->get_rgo(); if (rgo != nullptr) { - ret[get_province_info_rgo_key()] = std_view_to_godot_string(rgo->get_identifier()); + ret[province_info_rgo_key] = std_view_to_godot_string(rgo->get_identifier()); } - ret[get_province_info_life_rating_key()] = province->get_life_rating(); + ret[province_info_life_rating_key] = province->get_life_rating(); TerrainType const* terrain_type = province->get_terrain_type(); if (terrain_type != nullptr) { - ret[get_province_info_terrain_type_key()] = std_view_to_godot_string(terrain_type->get_identifier()); + ret[province_info_terrain_type_key] = std_view_to_godot_string(terrain_type->get_identifier()); } - ret[get_province_info_total_population_key()] = province->get_total_population(); + ret[province_info_total_population_key] = province->get_total_population(); decimal_map_t const& pop_types = province->get_pop_type_distribution(); if (!pop_types.empty()) { - ret[get_province_info_pop_types_key()] = _distribution_to_dictionary(pop_types); + ret[province_info_pop_types_key] = _distribution_to_dictionary(pop_types); } decimal_map_t const& ideologies = province->get_ideology_distribution(); if (!ideologies.empty()) { - ret[get_province_info_pop_ideologies_key()] = _distribution_to_dictionary(ideologies); + ret[province_info_pop_ideologies_key] = _distribution_to_dictionary(ideologies); } decimal_map_t const& cultures = province->get_culture_distribution(); if (!cultures.empty()) { - ret[get_province_info_pop_cultures_key()] = _distribution_to_dictionary(cultures); + ret[province_info_pop_cultures_key] = _distribution_to_dictionary(cultures); } + static const StringName building_info_building_key = "building"; + static const StringName building_info_level_key = "level"; + static const StringName building_info_expansion_state_key = "expansion_state"; + static const StringName building_info_start_date_key = "start_date"; + static const StringName building_info_end_date_key = "end_date"; + static const StringName building_info_expansion_progress_key = "expansion_progress"; + std::vector const& buildings = province->get_buildings(); if (!buildings.empty()) { Array buildings_array; @@ -290,16 +234,16 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { BuildingInstance const& building = buildings[idx]; Dictionary building_dict; - building_dict[get_building_info_building_key()] = std_view_to_godot_string(building.get_identifier()); - building_dict[get_building_info_level_key()] = static_cast(building.get_current_level()); - building_dict[get_building_info_expansion_state_key()] = static_cast(building.get_expansion_state()); - building_dict[get_building_info_start_date_key()] = std_to_godot_string(building.get_start_date().to_string()); - building_dict[get_building_info_end_date_key()] = std_to_godot_string(building.get_end_date().to_string()); - building_dict[get_building_info_expansion_progress_key()] = building.get_expansion_progress(); + building_dict[building_info_building_key] = std_view_to_godot_string(building.get_identifier()); + building_dict[building_info_level_key] = static_cast(building.get_current_level()); + building_dict[building_info_expansion_state_key] = static_cast(building.get_expansion_state()); + building_dict[building_info_start_date_key] = std_to_godot_string(building.get_start_date().to_string()); + building_dict[building_info_end_date_key] = std_to_godot_string(building.get_end_date().to_string()); + building_dict[building_info_expansion_progress_key] = building.get_expansion_progress(); buildings_array[idx] = building_dict; } - ret[get_province_info_buildings_key()] = buildings_array; + ret[province_info_buildings_key] = buildings_array; } return ret; } diff --git a/extension/src/openvic-extension/GameSingleton.hpp b/extension/src/openvic-extension/GameSingleton.hpp index ec26c3c6..20af45ca 100644 --- a/extension/src/openvic-extension/GameSingleton.hpp +++ b/extension/src/openvic-extension/GameSingleton.hpp @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include +#include "openvic-extension/UIAdapter.hpp" + namespace OpenVic { class GameSingleton : public godot::Object { @@ -15,6 +18,7 @@ namespace OpenVic { GameManager game_manager; Dataloader dataloader; + AssetManager asset_manager; godot::Vector2i image_subdivisions; godot::Ref province_shape_texture; @@ -37,6 +41,9 @@ namespace OpenVic { static void _bind_methods(); public: + + godot::Control* generate_gui(godot::String const& gui_file, godot::String const& gui_element); + static void draw_pie_chart( godot::Ref image, godot::Array const& stopAngles, godot::Array const& colours, float radius, godot::Vector2 shadow_displacement, float shadow_tightness, float shadow_radius, float shadow_thickness, @@ -69,29 +76,8 @@ namespace OpenVic { int32_t get_province_index_from_uv_coords(godot::Vector2 const& coords) const; - static godot::StringName const& get_province_info_province_key(); - static godot::StringName const& get_province_info_region_key(); - static godot::StringName const& get_province_info_life_rating_key(); - static godot::StringName const& get_province_info_terrain_type_key(); - static godot::StringName const& get_province_info_total_population_key(); - static godot::StringName const& get_province_info_pop_types_key(); - static godot::StringName const& get_province_info_pop_ideologies_key(); - static godot::StringName const& get_province_info_pop_cultures_key(); - static godot::StringName const& get_province_info_rgo_key(); - static godot::StringName const& get_province_info_buildings_key(); - - static godot::StringName const& get_building_info_building_key(); - static godot::StringName const& get_building_info_level_key(); - static godot::StringName const& get_building_info_expansion_state_key(); - static godot::StringName const& get_building_info_start_date_key(); - static godot::StringName const& get_building_info_end_date_key(); - static godot::StringName const& get_building_info_expansion_progress_key(); - - static godot::StringName const& get_piechart_info_size_key(); - static godot::StringName const& get_piechart_info_colour_key(); - /* Get info to display in Province Overview Panel, packaged in - * a Dictionary using the StringNames above as keys. + * a Dictionary using StringName constants as keys. */ godot::Dictionary get_province_info_from_index(int32_t index) const; diff --git a/extension/src/openvic-extension/LoadLocalisation.cpp b/extension/src/openvic-extension/LoadLocalisation.cpp index ee906338..51074e0a 100644 --- a/extension/src/openvic-extension/LoadLocalisation.cpp +++ b/extension/src/openvic-extension/LoadLocalisation.cpp @@ -65,10 +65,7 @@ Error LoadLocalisation::_load_file(String const& file_path, Ref tra Ref LoadLocalisation::_get_translation(String const& locale) const { TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return nullptr; - } + ERR_FAIL_NULL_V(server, nullptr); Ref translation = server->get_translation_object(locale); if (translation.is_null() || translation->get_locale() != locale) { translation.instantiate(); @@ -127,10 +124,7 @@ Error LoadLocalisation::load_localisation_dir(String const& dir_path) const { return FAILED; } TranslationServer* server = TranslationServer::get_singleton(); - if (server == nullptr) { - UtilityFunctions::push_error("Failed to get TranslationServer singleton"); - return FAILED; - } + ERR_FAIL_NULL_V(server, FAILED); Error err = OK; for (String const& locale_name : dirs) { if (locale_name != server->standardize_locale(locale_name)) { diff --git a/extension/src/openvic-extension/UIAdapter.cpp b/extension/src/openvic-extension/UIAdapter.cpp new file mode 100644 index 00000000..5628d858 --- /dev/null +++ b/extension/src/openvic-extension/UIAdapter.cpp @@ -0,0 +1,416 @@ +#include "UIAdapter.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openvic-extension/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; +using namespace OpenVic::GUI; + +using OpenVic::Utilities::std_view_to_godot_string; + +bool GodotGUIBuilder::generate_element(GUI::Element const* element, AssetManager& asset_manager, godot::Control*& result) { + if (element == nullptr) { + UtilityFunctions::push_error("Invalid element passed to GodotGUIBuilder - null!"); + return false; + } + static const std::map type_map { + { GUI::Icon::get_type_static(), &generate_icon }, + { GUI::Button::get_type_static(), &generate_button }, + { GUI::Checkbox::get_type_static(), &generate_checkbox }, + { GUI::Text::get_type_static(), &generate_text }, + { GUI::OverlappingElementsBox::get_type_static(), &generate_overlapping_elements }, + { GUI::ListBox::get_type_static(), &generate_listbox }, + { GUI::Window::get_type_static(), &generate_window } + }; + const decltype(type_map)::const_iterator it = type_map.find(element->get_type()); + if (it != type_map.end()) { + return it->second(*element, asset_manager, result); + } else { + UtilityFunctions::push_error("Invalid GUI element type: ", std_view_to_godot_string(element->get_type())); + result = nullptr; + return false; + } +} + +template T> +static T* new_control(Element const& element) { + T* node = memnew(T); + ERR_FAIL_NULL_V(node, nullptr); + + using enum Element::orientation_t; + using enum Control::LayoutPreset; + static const std::map orientation_map { + { UPPER_LEFT, PRESET_TOP_LEFT }, { LOWER_LEFT, PRESET_BOTTOM_LEFT }, + { LOWER_RIGHT, PRESET_BOTTOM_RIGHT }, { UPPER_RIGHT, PRESET_TOP_RIGHT }, + { CENTER, PRESET_CENTER } + }; + + node->set_name(std_view_to_godot_string(element.get_name())); + const decltype(orientation_map)::const_iterator it = orientation_map.find(element.get_orientation()); + if (it != orientation_map.end()) { + node->set_anchors_and_offsets_preset(it->second); + } else { + UtilityFunctions::push_error("Invalid orientation for GUI element ", + std_view_to_godot_string(element.get_name())); + } + node->set_position(Utilities::to_godot_ivec2(element.get_position())); + node->set_focus_mode(Control::FOCUS_NONE); + + return node; +} + +bool GodotGUIBuilder::generate_icon(Element const& element, AssetManager& asset_manager, godot::Control*& result) { + Icon const& icon = static_cast(element); + + result = nullptr; + const String icon_name = std_view_to_godot_string(icon.get_name()); + + /* Change to use sprite type to choose Godot node type! */ + bool ret = true; + if (icon.get_sprite() != nullptr) { + if (icon.get_sprite()->is_type()) { + TextureRect* godot_texture_rect = new_control(icon); + if (godot_texture_rect == nullptr) { + UtilityFunctions::push_error("Failed to create godot::TextureRect for GUI icon ", icon_name); + return false; + } + + GFX::TextureSprite const* texture_sprite = icon.get_sprite()->cast_to(); + const Ref texture = asset_manager.get_texture(std_view_to_godot_string( + texture_sprite->get_texture_file()), icon.get_frame(), texture_sprite->get_no_of_frames()); + if (texture.is_valid()) { + godot_texture_rect->set_texture(texture); + } else { + UtilityFunctions::push_error("Failed to load texture sprite for GUI icon ", icon_name); + ret = false; + } + + result = godot_texture_rect; + } else if (icon.get_sprite()->is_type()) { + TextureRect* godot_texture_rect = new_control(icon); + if (godot_texture_rect == nullptr) { + UtilityFunctions::push_error("Failed to create godot::TextureRect for GUI icon ", icon_name); + return false; + } + + const StringName texture_file = std_view_to_godot_string(icon.get_sprite()->cast_to()->get_texture_file()); + const Ref texture = asset_manager.get_texture(texture_file); + if (texture.is_valid()) { + godot_texture_rect->set_texture(texture); + } else { + UtilityFunctions::push_error("Failed to load masked flag sprite ", texture_file, " for GUI icon ", icon_name); + ret = false; + } + + result = godot_texture_rect; + } else if (icon.get_sprite()->is_type()) { + godot::TextureProgressBar* godot_progress_bar = new_control(icon); + if (godot_progress_bar == nullptr) { + UtilityFunctions::push_error("Failed to create godot::TextureProgressBar for GUI icon ", icon_name); + return false; + } + + const StringName back_texture_file = std_view_to_godot_string(icon.get_sprite()->cast_to()->get_back_texture_file()); + const Ref back_texture = asset_manager.get_texture(back_texture_file); + if (back_texture.is_valid()) { + godot_progress_bar->set_under_texture(back_texture); + } else { + UtilityFunctions::push_error("Failed to load progress bar base sprite ", back_texture_file, " for GUI icon ", icon_name); + ret = false; + } + + const StringName progress_texture_file = std_view_to_godot_string(icon.get_sprite()->cast_to()->get_progress_texture_file()); + const Ref progress_texture = asset_manager.get_texture(progress_texture_file); + if (progress_texture.is_valid()) { + godot_progress_bar->set_progress_texture(progress_texture); + } else { + UtilityFunctions::push_error("Failed to load progress bar base sprite ", progress_texture_file, " for GUI icon ", icon_name); + ret = false; + } + + result = godot_progress_bar; + } else if (icon.get_sprite()->is_type()) { + UtilityFunctions::push_warning("PieChart sprite for Gui icon ", icon_name); + } else if (icon.get_sprite()->is_type()) { + UtilityFunctions::push_warning("LineChart sprite for Gui icon ", icon_name); + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(icon.get_sprite()->get_type()), + " for GUI icon ", icon_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Null sprite for GUI icon ", icon_name); + ret = false; + } + return ret; +} + +bool GodotGUIBuilder::generate_button(Element const& element, AssetManager& asset_manager, godot::Control*& result) { + GUI::Button const& button = static_cast(element); + + // TODO - shortcut, sprite, text + result = nullptr; + const String button_name = std_view_to_godot_string(button.get_name()); + + godot::Button* godot_button = new_control(button); + if (godot_button == nullptr) { + UtilityFunctions::push_error("Failed to create godot::Button for GUI button ", button_name); + return false; + } + + godot_button->set_text(std_view_to_godot_string(button.get_text())); + //godot_button->set_flat(true); + + bool ret = true; + if (button.get_sprite() != nullptr) { + Ref texture; + if (button.get_sprite()->is_type()) { + GFX::TextureSprite const* texture_sprite = button.get_sprite()->cast_to(); + texture = asset_manager.get_texture( + std_view_to_godot_string(texture_sprite->get_texture_file()), + texture_sprite->get_no_of_frames() > GFX::NO_FRAMES ? 1 : GFX::NO_FRAMES, + texture_sprite->get_no_of_frames()); + if (texture.is_null()) { + UtilityFunctions::push_error("Failed to load texture sprite for GUI button ", button_name); + ret = false; + } + } else if (button.get_sprite()->is_type()) { + texture = asset_manager.get_texture(std_view_to_godot_string( + button.get_sprite()->cast_to()->get_texture_file())); + if (texture.is_null()) { + UtilityFunctions::push_error("Failed to load masked flag sprite for GUI button ", button_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(button.get_sprite()->get_type()), + " for GUI button ", button_name); + ret = false; + } + + if (texture.is_valid()) { + godot_button->set_size(texture->get_size()); + Ref stylebox; + stylebox.instantiate(); + if (stylebox.is_valid()) { + stylebox->set_texture(texture); + godot_button->add_theme_stylebox_override("normal", stylebox); + } else { + UtilityFunctions::push_error("Failed to load instantiate texture stylebox for GUI button ", button_name); + ret = false; + } + } + } else { + UtilityFunctions::push_error("Null sprite for GUI button ", button_name); + ret = false; + } + + if (button.get_font() != nullptr) { + const StringName font_file = std_view_to_godot_string(button.get_font()->get_fontname()); + const Ref font = asset_manager.get_font(font_file); + if (font.is_valid()) { + godot_button->add_theme_font_override("font", font); + } else { + UtilityFunctions::push_error("Failed to load font for GUI button ", button_name); + ret = false; + } + const Color colour = Utilities::to_godot_color(button.get_font()->get_colour()); + godot_button->add_theme_color_override("font_color", colour); + } + + result = godot_button; + return ret; +} + +bool GodotGUIBuilder::generate_checkbox(Element const& element, AssetManager& asset_manager, godot::Control*& result) { + Checkbox const& checkbox = static_cast(element); + + // TODO - shortcut, sprite, text + result = nullptr; + const String checkbox_name = std_view_to_godot_string(checkbox.get_name()); + + godot::CheckBox* godot_checkbox = new_control(checkbox); + if (godot_checkbox == nullptr) { + UtilityFunctions::push_error("Failed to create godot::CheckBox for GUI checkbox ", checkbox_name); + return false; + } + + bool ret = true; + if (checkbox.get_sprite() != nullptr) { + GFX::TextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to(); + if (texture_sprite != nullptr) { + const String texture_name = std_view_to_godot_string(texture_sprite->get_name()); + const StringName texture_file = std_view_to_godot_string(texture_sprite->get_texture_file()); + const Ref texture = asset_manager.get_texture(texture_file); + if (texture.is_valid()) { + GFX::frame_t frames = texture_sprite->get_no_of_frames(); + if (frames < 2) { + frames = 1; + UtilityFunctions::push_error("Checkbox texture sprite ", texture_name, + " for GUI checkbox ", checkbox_name, " is missing a \"checked\" frame"); + ret = false; + } else if (frames > 2) { + UtilityFunctions::push_warning("Checkbox texture sprite ", texture_name, " for GUI checkbox ", + checkbox_name, " has too many frames: ", frames); + } + if (texture->get_width() % frames != 0) { + UtilityFunctions::push_warning("Checkbox texture sprite ", texture_name, " for GUI checkbox ", + checkbox_name, " has width ", texture->get_width(), " which isn't a multiple of frame count ", frames); + } + Rect2i region = AssetManager::get_frame_region(1, frames, texture->get_size()); + godot_checkbox->set_size(region.get_size()); + godot_checkbox->add_theme_icon_override("unchecked", AssetManager::make_atlas_texture(texture, region)); + if (frames > 1) { + region = AssetManager::get_frame_region(2, frames, texture->get_size()); + } + godot_checkbox->add_theme_icon_override("checked", AssetManager::make_atlas_texture(texture, region)); + } else { + UtilityFunctions::push_error("Failed to load texture sprite ", texture_file, " for GUI checkbox ", checkbox_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Invalid sprite type ", std_view_to_godot_string(checkbox.get_sprite()->get_type()), + " for GUI checkbox ", checkbox_name); + ret = false; + } + } else { + UtilityFunctions::push_error("Null sprite for GUI checkbox ", checkbox_name); + ret = false; + } + + result = godot_checkbox; + return ret; +} + +bool GodotGUIBuilder::generate_text(Element const& element, AssetManager& asset_manager, godot::Control*& result) { + Text const& text = static_cast(element); + + result = nullptr; + const String text_name = std_view_to_godot_string(text.get_name()); + + Label* godot_label = new_control