diff options
Diffstat (limited to 'extension')
21 files changed, 687 insertions, 403 deletions
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation -Subproject 14e47d58b85f657ec1fed8abf88219f09bd3efb +Subproject bff91f78f9c5339079c10adfbf8232e5159c1a2 diff --git a/extension/src/openvic-extension/UIAdapter.hpp b/extension/src/openvic-extension/UIAdapter.hpp deleted file mode 100644 index 258b5e9..0000000 --- a/extension/src/openvic-extension/UIAdapter.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include <godot_cpp/classes/control.hpp> - -#include <openvic-simulation/interface/GUI.hpp> - -#include "openvic-extension/singletons/AssetManager.hpp" - -namespace OpenVic::GodotGUIBuilder { - - bool generate_element( - GUI::Element const* element, godot::String const& name, AssetManager& asset_manager, godot::Control*& result - ); - -#define GEN_GUI_ARGS \ - GUI::Element const& element, godot::String const& name, AssetManager& asset_manager, godot::Control*& result - - bool generate_icon(GEN_GUI_ARGS); - bool generate_button(GEN_GUI_ARGS); - bool generate_checkbox(GEN_GUI_ARGS); - bool generate_text(GEN_GUI_ARGS); - bool generate_overlapping_elements(GEN_GUI_ARGS); - bool generate_listbox(GEN_GUI_ARGS); - bool generate_window(GEN_GUI_ARGS); - -#undef GEN_GUI_ARGS - -} diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.cpp b/extension/src/openvic-extension/classes/GFXIconTexture.cpp index 57c5d50..895bf6b 100644 --- a/extension/src/openvic-extension/classes/GFXIconTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXIconTexture.cpp @@ -5,6 +5,7 @@ #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; @@ -71,12 +72,8 @@ Error GFXIconTexture::set_gfx_texture_sprite_name(String const& gfx_texture_spri if (gfx_texture_sprite_name.is_empty()) { return set_gfx_texture_sprite(nullptr); } - GameSingleton* game_singleton = GameSingleton::get_singleton(); - ERR_FAIL_NULL_V(game_singleton, FAILED); - GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( - godot_to_std_string(gfx_texture_sprite_name) - ); - ERR_FAIL_NULL_V_MSG(sprite, FAILED, vformat("GFX sprite not found: %s", gfx_texture_sprite_name)); + GFX::Sprite const* sprite = UITools::get_gfx_sprite(gfx_texture_sprite_name); + ERR_FAIL_NULL_V(sprite, FAILED); GFX::TextureSprite const* new_texture_sprite = sprite->cast_to<GFX::TextureSprite>(); ERR_FAIL_NULL_V_MSG( new_texture_sprite, FAILED, vformat( diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.hpp b/extension/src/openvic-extension/classes/GFXIconTexture.hpp index 3ed5b3e..176d855 100644 --- a/extension/src/openvic-extension/classes/GFXIconTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXIconTexture.hpp @@ -35,12 +35,12 @@ namespace OpenVic { ); /* Search for a GFX::TextureSprite with the specfied name and, - * if successful, call set_gfx_texture_sprite to set it and its icon */ + * if successful, call set_gfx_texture_sprite to set it and its icon. */ godot::Error set_gfx_texture_sprite_name( godot::String const& gfx_texture_sprite_name, GFX::frame_t icon = GFX::NO_FRAMES ); - /* Return the name of the GFX::TextureSprite, or an empty String if it's null */ + /* Return the name of the GFX::TextureSprite, or an empty String if it's null. */ godot::String get_gfx_texture_sprite_name() const; /* Set icon_index to a value between one and icon_count (inclusive), and update the AtlasTexture's region diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp index 3636855..0ed7755 100644 --- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp @@ -1,10 +1,9 @@ #include "GFXMaskedFlagTexture.hpp" -#include <godot_cpp/variant/utility_functions.hpp> - #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; @@ -66,6 +65,7 @@ void GFXMaskedFlagTexture::_bind_methods() { OV_BIND_METHOD(GFXMaskedFlagTexture::get_gfx_masked_flag_name); OV_BIND_METHOD(GFXMaskedFlagTexture::set_flag_country_name_and_type, { "new_flag_country_name", "new_flag_type" }); + OV_BIND_METHOD(GFXMaskedFlagTexture::set_flag_country_name, { "new_flag_country_name" }); OV_BIND_METHOD(GFXMaskedFlagTexture::get_flag_country_name); OV_BIND_METHOD(GFXMaskedFlagTexture::get_flag_type); } @@ -123,12 +123,8 @@ Error GFXMaskedFlagTexture::set_gfx_masked_flag_name(String const& gfx_masked_fl if (gfx_masked_flag_name.is_empty()) { return set_gfx_masked_flag(nullptr); } - GameSingleton* game_singleton = GameSingleton::get_singleton(); - ERR_FAIL_NULL_V(game_singleton, FAILED); - GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( - godot_to_std_string(gfx_masked_flag_name) - ); - ERR_FAIL_NULL_V_MSG(sprite, FAILED, vformat("GFX sprite not found: %s", gfx_masked_flag_name)); + GFX::Sprite const* sprite = UITools::get_gfx_sprite(gfx_masked_flag_name); + ERR_FAIL_NULL_V(sprite, FAILED); GFX::MaskedFlag const* new_masked_flag = sprite->cast_to<GFX::MaskedFlag>(); ERR_FAIL_NULL_V_MSG( new_masked_flag, FAILED, vformat( @@ -158,6 +154,7 @@ Error GFXMaskedFlagTexture::set_flag_country_and_type(Country const* new_flag_co flag_type = new_flag_type; flag_image = new_flag_image; } else { + // TODO - use REB flag as default/error flag flag_country = nullptr; flag_type = String {}; flag_image.unref(); @@ -178,6 +175,24 @@ Error GFXMaskedFlagTexture::set_flag_country_name_and_type(String const& new_fla return set_flag_country_and_type(new_flag_country, new_flag_type); } +Error GFXMaskedFlagTexture::set_flag_country(Country const* new_flag_country) { + // TODO - get country's current flag type from the game state + return set_flag_country_and_type( new_flag_country, {}); +} + +Error GFXMaskedFlagTexture::set_flag_country_name(String const& new_flag_country_name) { + if (new_flag_country_name.is_empty()) { + return set_flag_country(nullptr); + } + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, FAILED); + Country const* new_flag_country = game_singleton->get_game_manager().get_country_manager().get_country_by_identifier( + godot_to_std_string(new_flag_country_name) + ); + ERR_FAIL_NULL_V_MSG(new_flag_country, FAILED, vformat("Country not found: %s", new_flag_country_name)); + return set_flag_country(new_flag_country); +} + String GFXMaskedFlagTexture::get_flag_country_name() const { return flag_country != nullptr ? std_view_to_godot_string(flag_country->get_identifier()) : String {}; } diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp index be3b15a..f71a1d7 100644 --- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp @@ -37,7 +37,7 @@ namespace OpenVic { /* Search for a GFX::MaskedFlag with the specfied name and, if successful, set it using set_gfx_masked_flag. */ godot::Error set_gfx_masked_flag_name(godot::String const& gfx_masked_flag_name); - /* Return the name of the GFX::MaskedFlag, or an empty String if it's null */ + /* Return the name of the GFX::MaskedFlag, or an empty String if it's null. */ godot::String get_gfx_masked_flag_name() const; /* Set flag_country and flag_type and update the combined image to use that flag, or no flag if it doesn't exist. */ @@ -49,7 +49,14 @@ namespace OpenVic { godot::String const& new_flag_country_name, godot::StringName const& new_flag_type ); - /* Return the name of the selected flag's country, or an empty String if it's null */ + /* Look up the specified country's current flag type, then call set_flag_country_and_type + * with the country and its flag type as arguments. */ + godot::Error set_flag_country(Country const* new_flag_country); + + /* Look up the country with the specified identifier, then call set_flag_country with the country its argument. */ + godot::Error set_flag_country_name(godot::String const& new_flag_country_name); + + /* Return the name of the selected flag's country, or an empty String if it's null. */ godot::String get_flag_country_name() const; }; } diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp index 6fe2fe0..e1bffc5 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp @@ -1,11 +1,9 @@ #include "GFXPieChartTexture.hpp" -#include <godot_cpp/variant/utility_functions.hpp> - #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" -#include "openvic-extension/utility/Utilities.hpp" +#include "openvic-extension/utility/UITools.hpp" using namespace godot; using namespace OpenVic; @@ -14,14 +12,14 @@ using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string_name; -#define PI std::numbers::pi_v<float> +static constexpr float PI = std::numbers::pi_v<float>; Error GFXPieChartTexture::_generate_pie_chart_image() { ERR_FAIL_NULL_V(gfx_pie_chart, FAILED); - if (gfx_pie_chart->get_size() <= 0) { - UtilityFunctions::push_error("Invalid GFX::PieChart size for GFXPieChartTexture - ", gfx_pie_chart->get_size()); - return FAILED; - } + ERR_FAIL_COND_V_MSG( + gfx_pie_chart->get_size() <= 0, FAILED, + vformat("Invalid GFX::PieChart size for GFXPieChartTexture - %d", gfx_pie_chart->get_size()) + ); const int32_t pie_chart_size = 2 * gfx_pie_chart->get_size(); bool can_update = true; if ( @@ -75,7 +73,7 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { return OK; } -Error GFXPieChartTexture::set_slices(Array const& new_slices) { +Error GFXPieChartTexture::set_slices_array(TypedArray<Dictionary> const& new_slices) { static const StringName colour_key = "colour"; static const StringName weight_key = "weight"; @@ -83,15 +81,11 @@ Error GFXPieChartTexture::set_slices(Array const& new_slices) { total_weight = 0.0f; for (int32_t i = 0; i < new_slices.size(); ++i) { Dictionary const& slice_dict = new_slices[i]; - if (!slice_dict.has(colour_key) || !slice_dict.has(weight_key)) { - UtilityFunctions::push_error("Invalid slice keys at index ", i, " - ", slice_dict); - continue; - } + ERR_CONTINUE_MSG( + !slice_dict.has(colour_key) || !slice_dict.has(weight_key), vformat("Invalid slice keys at index %d", i) + ); const slice_t slice = std::make_pair(slice_dict[colour_key], slice_dict[weight_key]); - if (slice.second <= 0.0f) { - UtilityFunctions::push_error("Invalid slice weight at index ", i, " - ", slice.second); - continue; - } + ERR_CONTINUE_MSG(slice.second <= 0.0f, vformat("Invalid slice values at index %d", i)); total_weight += slice.second; slices.emplace_back(std::move(slice)); } @@ -104,10 +98,10 @@ void GFXPieChartTexture::_bind_methods() { OV_BIND_METHOD(GFXPieChartTexture::set_gfx_pie_chart_name, { "gfx_pie_chart_name" }); OV_BIND_METHOD(GFXPieChartTexture::get_gfx_pie_chart_name); - OV_BIND_METHOD(GFXPieChartTexture::set_slices, { "new_slices" }); + OV_BIND_METHOD(GFXPieChartTexture::set_slices_array, { "new_slices" }); } -GFXPieChartTexture::GFXPieChartTexture() : total_weight { 0.0f } {} +GFXPieChartTexture::GFXPieChartTexture() : gfx_pie_chart { nullptr }, total_weight { 0.0f } {} Ref<GFXPieChartTexture> GFXPieChartTexture::make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart) { Ref<GFXPieChartTexture> pie_chart_texture; @@ -146,12 +140,8 @@ Error GFXPieChartTexture::set_gfx_pie_chart_name(String const& gfx_pie_chart_nam if (gfx_pie_chart_name.is_empty()) { return set_gfx_pie_chart(nullptr); } - GameSingleton* game_singleton = GameSingleton::get_singleton(); - ERR_FAIL_NULL_V(game_singleton, FAILED); - GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( - godot_to_std_string(gfx_pie_chart_name) - ); - ERR_FAIL_NULL_V_MSG(sprite, FAILED, vformat("GFX sprite not found: %s", gfx_pie_chart_name)); + GFX::Sprite const* sprite = UITools::get_gfx_sprite(gfx_pie_chart_name); + ERR_FAIL_NULL_V(sprite, FAILED); GFX::PieChart const* new_pie_chart = sprite->cast_to<GFX::PieChart>(); ERR_FAIL_NULL_V_MSG( new_pie_chart, FAILED, vformat( diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp index 315b00e..ad8e751 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -4,6 +4,8 @@ #include <openvic-simulation/interface/GFX.hpp> +#include "openvic-extension/utility/Utilities.hpp" + namespace OpenVic { class GFXPieChartTexture : public godot::ImageTexture { GDCLASS(GFXPieChartTexture, godot::ImageTexture) @@ -23,11 +25,40 @@ namespace OpenVic { public: GFXPieChartTexture(); - /* Set slices given new_slices, an Array of Dictionaries, each with the following keys: + /* Set slices given an Array of Dictionaries, each with the following key-value entries: * - colour: Color - * - weight: float - */ - godot::Error set_slices(godot::Array const& new_slices); + * - weight: float */ + godot::Error set_slices_array(godot::TypedArray<godot::Dictionary> const& new_slices); + + /* Generate slice data from a distribution of HasIdentifierAndColour derived objects, sorted by their weight. + * The resulting Array of Dictionaries can be used as an argument for set_slices_array. */ + template<std::derived_from<HasIdentifierAndColour> T> + static godot::TypedArray<godot::Dictionary> distribution_to_slices_array(fixed_point_map_t<T const*> const& dist) { + using entry_t = std::pair<T const*, fixed_point_t>; + std::vector<entry_t> sorted_dist; + sorted_dist.reserve(dist.size()); + for (entry_t const& entry : dist) { + ERR_CONTINUE_MSG( + entry.first == nullptr, godot::vformat("Null distribution key with value %f", entry.second.to_float()) + ); + sorted_dist.push_back(entry); + } + std::sort(sorted_dist.begin(), sorted_dist.end(), [](entry_t const& lhs, entry_t const& rhs) -> bool { + return lhs.second < rhs.second; + }); + static const godot::StringName identifier_key = "identifier"; + static const godot::StringName colour_key = "colour"; + static const godot::StringName weight_key = "weight"; + godot::TypedArray<godot::Dictionary> array; + for (auto const& [key, val] : sorted_dist) { + godot::Dictionary sub_dict; + sub_dict[identifier_key] = Utilities::std_view_to_godot_string(key->get_identifier()); + sub_dict[colour_key] = Utilities::to_godot_color(key->get_colour()); + sub_dict[weight_key] = val.to_float(); + array.push_back(sub_dict); + } + return array; + } /* Create a GFXPieChartTexture using the specific GFX::PieChart. * Returns nullptr if setting gfx_pie_chart fails. */ @@ -43,7 +74,7 @@ namespace OpenVic { /* Search for a GFX::PieChart with the specfied name and, if successful, set it using set_gfx_pie_chart. */ godot::Error set_gfx_pie_chart_name(godot::String const& gfx_pie_chart_name); - /* Return the name of the GFX::PieChart, or an empty String if it's null */ + /* Return the name of the GFX::PieChart, or an empty String if it's null. */ godot::String get_gfx_pie_chart_name() const; }; } diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index 1d55c54..043f65d 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -3,10 +3,8 @@ #include <godot_cpp/classes/style_box_texture.hpp> #include <godot_cpp/variant/utility_functions.hpp> -#include "openvic-extension/UIAdapter.hpp" -#include "openvic-extension/singletons/AssetManager.hpp" -#include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; @@ -15,33 +13,58 @@ using namespace OpenVic; using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; +#define APPLY_TO_CHILD_TYPES(F) \ + F(Button, button) \ + F(CheckBox, check_box) \ + F(Label, label) \ + F(Panel, panel) \ + F(TextureProgressBar, progress_bar) \ + F(TextureRect, texture_rect) \ + F(GUIOverlappingElementsBox, gui_overlapping_elements_box) + +#define APPLY_TO_TEXTURE_TYPES(F) \ + F(GFXIconTexture, gfx_icon_texture) \ + F(GFXMaskedFlagTexture, gfx_masked_flag_texture) \ + F(GFXPieChartTexture, gfx_pie_chart_texture) + void GUINode::_bind_methods() { + OV_BIND_SMETHOD(generate_gui_element, { "gui_file", "gui_element", "name" }, DEFVAL(String {})); OV_BIND_METHOD(GUINode::add_gui_element, { "gui_file", "gui_element", "name" }, DEFVAL(String {})); - OV_BIND_METHOD(GUINode::get_button_node, { "path" }); - OV_BIND_METHOD(GUINode::get_check_box_node, { "path" }); - OV_BIND_METHOD(GUINode::get_label_node, { "path" }); - OV_BIND_METHOD(GUINode::get_panel_node, { "path" }); - OV_BIND_METHOD(GUINode::get_progress_bar_node, { "path" }); - OV_BIND_METHOD(GUINode::get_texture_rect_node, { "path" }); +#define GET_BINDINGS(type, name) \ + OV_BIND_SMETHOD(get_##name##_from_node, { "node" }); \ + OV_BIND_METHOD(GUINode::get_##name##_from_nodepath, { "path" }); + + APPLY_TO_CHILD_TYPES(GET_BINDINGS) + + OV_BIND_SMETHOD(get_texture_from_node, { "node" }); + OV_BIND_METHOD(GUINode::get_texture_from_nodepath, { "path" }); + + APPLY_TO_TEXTURE_TYPES(GET_BINDINGS) - OV_BIND_METHOD(GUINode::get_texture_from_node, { "path" }); - OV_BIND_METHOD(GUINode::get_gfx_icon_texture_from_node, { "path" }); - OV_BIND_METHOD(GUINode::get_gfx_masked_flag_texture_from_node, { "path" }); - OV_BIND_METHOD(GUINode::get_gfx_pie_chart_texture_from_node, { "path" }); +#undef GET_BINDINGS OV_BIND_METHOD(GUINode::hide_node, { "path" }); OV_BIND_METHOD(GUINode::hide_nodes, { "paths" }); } -Error GUINode::_add_gui_element(GUI::Element const* element, String const& name) { - ERR_FAIL_NULL_V(element, FAILED); - AssetManager* asset_manager = AssetManager::get_singleton(); - ERR_FAIL_NULL_V(asset_manager, FAILED); +GUINode::GUINode() { + set_mouse_filter(MOUSE_FILTER_IGNORE); +} + +Control* GUINode::generate_gui_element(String const& gui_file, String const& gui_element, String const& name) { + Control* result = nullptr; + if (!UITools::generate_gui_element(gui_file, gui_element, name, result)) { + UtilityFunctions::push_error("Error generating GUI element ", gui_element, " from GUI file ", gui_file); + } + return result; +} + +Error GUINode::add_gui_element(String const& gui_file, String const& gui_element, String const& name) { Error err = OK; Control* result = nullptr; - if (!GodotGUIBuilder::generate_element(element, name, *asset_manager, result)) { - UtilityFunctions::push_error("Failed to generate GUI element ", std_view_to_godot_string(element->get_name())); + if (!UITools::generate_gui_element(gui_file, gui_element, name, result)) { + UtilityFunctions::push_error("Error generating GUI element ", gui_element, " from GUI file ", gui_file); err = FAILED; } if (result != nullptr) { @@ -50,107 +73,95 @@ Error GUINode::_add_gui_element(GUI::Element const* element, String const& name) return err; } -Error GUINode::add_gui_element(String const& gui_file, String const& gui_element, String const& name) { - GameSingleton const* game_singleton = GameSingleton::get_singleton(); - ERR_FAIL_NULL_V(game_singleton, FAILED); - GUI::Scene const* scene = - game_singleton->get_game_manager().get_ui_manager().get_scene_by_identifier(godot_to_std_string(gui_file)); - ERR_FAIL_NULL_V_MSG(scene, FAILED, vformat("Failed to find GUI file %s", gui_file)); - GUI::Element const* element = scene->get_scene_element_by_identifier(godot_to_std_string(gui_element)); - ERR_FAIL_NULL_V_MSG(element, FAILED, vformat("Failed to find GUI element %s in GUI file %s", gui_element, gui_file)); - return _add_gui_element(element, name); -} - template<std::derived_from<godot::Node> T> -T* GUINode::_get_cast_node(NodePath const& path) const { - Node* node = get_node_or_null(path); - ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to find node %s", path)); +static T* _cast_node(Node* node) { + ERR_FAIL_NULL_V(node, nullptr); T* result = Object::cast_to<T>(node); - ERR_FAIL_NULL_V_MSG(result, nullptr, vformat("Failed to cast node %s to type %s", path, T::get_class_static())); + ERR_FAIL_NULL_V_MSG( + result, nullptr, + vformat("Failed to cast node %s from type %s to %s", node->get_name(), node->get_class(), T::get_class_static()) + ); return result; } -Button* GUINode::get_button_node(NodePath const& path) const { - return _get_cast_node<Button>(path); -} - -CheckBox* GUINode::get_check_box_node(NodePath const& path) const { - return _get_cast_node<CheckBox>(path); -} - -Label* GUINode::get_label_node(NodePath const& path) const { - return _get_cast_node<Label>(path); -} - -Panel* GUINode::get_panel_node(NodePath const& path) const { - return _get_cast_node<Panel>(path); -} +#define CHILD_GET_FUNCTIONS(type, name) \ + type* GUINode::get_##name##_from_node(Node* node) { \ + return _cast_node<type>(node); \ + } \ + type* GUINode::get_##name##_from_nodepath(NodePath const& path) const { \ + return _cast_node<type>(get_node_internal(path)); \ + } -TextureProgressBar* GUINode::get_progress_bar_node(NodePath const& path) const { - return _get_cast_node<TextureProgressBar>(path); -} +APPLY_TO_CHILD_TYPES(CHILD_GET_FUNCTIONS) -TextureRect* GUINode::get_texture_rect_node(NodePath const& path) const { - return _get_cast_node<TextureRect>(path); -} +#undef CHILD_GET_FUNCTIONS -Ref<Texture2D> GUINode::get_texture_from_node(NodePath const& path) const { - Node* node = get_node_or_null(path); - ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to find node %s", path)); +Ref<Texture2D> GUINode::get_texture_from_node(Node* node) { + ERR_FAIL_NULL_V(node, nullptr); if (TextureRect const* texture_rect = Object::cast_to<TextureRect>(node); texture_rect != nullptr) { const Ref<Texture2D> texture = texture_rect->get_texture(); - ERR_FAIL_NULL_V_MSG(texture, nullptr, vformat("Failed to get Texture2D from TextureRect %s", path)); + ERR_FAIL_NULL_V_MSG(texture, nullptr, vformat("Failed to get Texture2D from TextureRect %s", node->get_name())); return texture; } else if (Button const* button = Object::cast_to<Button>(node); button != nullptr) { static const StringName theme_name_normal = "normal"; const Ref<StyleBox> stylebox = button->get_theme_stylebox(theme_name_normal); - ERR_FAIL_NULL_V_MSG(stylebox, nullptr, vformat("Failed to get StyleBox %s from Button %s", theme_name_normal, path)); + ERR_FAIL_NULL_V_MSG( + stylebox, nullptr, vformat("Failed to get StyleBox %s from Button %s", theme_name_normal, node->get_name()) + ); const Ref<StyleBoxTexture> stylebox_texture = stylebox; ERR_FAIL_NULL_V_MSG( stylebox_texture, nullptr, vformat( - "Failed to cast StyleBox %s from Button %s to type StyleBoxTexture", theme_name_normal, path + "Failed to cast StyleBox %s from Button %s to type StyleBoxTexture", theme_name_normal, node->get_name() ) ); const Ref<Texture2D> result = stylebox_texture->get_texture(); ERR_FAIL_NULL_V_MSG( - result, nullptr, vformat("Failed to get Texture2D from StyleBoxTexture %s from Button %s", theme_name_normal, path) + result, nullptr, + vformat("Failed to get Texture2D from StyleBoxTexture %s from Button %s", theme_name_normal, node->get_name()) ); return result; } - ERR_FAIL_V_MSG(nullptr, vformat("Failed to cast node %s to type TextureRect or Button", path)); + ERR_FAIL_V_MSG( + nullptr, vformat("Failed to cast node %s from type %s to TextureRect or Button", node->get_name(), node->get_class()) + ); +} + +Ref<Texture2D> GUINode::get_texture_from_nodepath(NodePath const& path) const { + return get_texture_from_node(get_node_internal(path)); } -template<std::derived_from<godot::Texture2D> T> -Ref<T> GUINode::_get_cast_texture_from_node(NodePath const& path) const { - const Ref<Texture2D> texture = get_texture_from_node(path); +template<std::derived_from<Texture2D> T> +static Ref<T> _cast_texture(Ref<Texture2D> const& texture) { ERR_FAIL_NULL_V(texture, nullptr); const Ref<T> result = texture; - ERR_FAIL_NULL_V_MSG(result, nullptr, vformat("Failed to cast Texture2D from %s to type %s", path, T::get_class_static())); + ERR_FAIL_NULL_V_MSG( + result, nullptr, vformat("Failed to cast Texture2D from type %s to %s", texture->get_class(), T::get_class_static()) + ); return result; } -Ref<GFXIconTexture> GUINode::get_gfx_icon_texture_from_node(NodePath const& path) const { - return _get_cast_texture_from_node<GFXIconTexture>(path); -} +#define TEXTURE_GET_FUNCTIONS(type, name) \ + Ref<type> GUINode::get_##name##_from_node(Node* node) { \ + return _cast_texture<type>(get_texture_from_node(node)); \ + } \ + Ref<type> GUINode::get_##name##_from_nodepath(NodePath const& path) const { \ + return _cast_texture<type>(get_texture_from_nodepath(path)); \ + } -Ref<GFXMaskedFlagTexture> GUINode::get_gfx_masked_flag_texture_from_node(NodePath const& path) const { - return _get_cast_texture_from_node<GFXMaskedFlagTexture>(path); -} +APPLY_TO_TEXTURE_TYPES(TEXTURE_GET_FUNCTIONS) -Ref<GFXPieChartTexture> GUINode::get_gfx_pie_chart_texture_from_node(NodePath const& path) const { - return _get_cast_texture_from_node<GFXPieChartTexture>(path); -} +#undef TEXTURE_GET_FUNCTIONS + +#undef APPLY_TO_CHILD_TYPES Error GUINode::hide_node(NodePath const& path) const { - CanvasItem* node = _get_cast_node<CanvasItem>(path); - if (node == nullptr) { - return FAILED; - } + CanvasItem* node = _cast_node<CanvasItem>(get_node_internal(path)); + ERR_FAIL_NULL_V(node, FAILED); node->hide(); return OK; } -Error GUINode::hide_nodes(Array const& paths) const { +Error GUINode::hide_nodes(TypedArray<NodePath> const& paths) const { Error ret = OK; for (int32_t i = 0; i < paths.size(); ++i) { if (hide_node(paths[i]) != OK) { diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index 1671547..e38ed1f 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -12,42 +12,54 @@ #include "openvic-extension/classes/GFXIconTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" namespace OpenVic { class GUINode : public godot::Control { GDCLASS(GUINode, godot::Control) - template<std::derived_from<godot::Node> T> - T* _get_cast_node(godot::NodePath const& path) const; - - template<std::derived_from<godot::Texture2D> T> - godot::Ref<T> _get_cast_texture_from_node(godot::NodePath const& path) const; - protected: static void _bind_methods(); public: - GUINode() = default; + GUINode(); + + static godot::Control* generate_gui_element( + godot::String const& gui_file, godot::String const& gui_element, godot::String const& name = "" + ); - godot::Error _add_gui_element(GUI::Element const* element, godot::String const& name); godot::Error add_gui_element( godot::String const& gui_file, godot::String const& gui_element, godot::String const& name = "" ); - godot::Button* get_button_node(godot::NodePath const& path) const; - godot::CheckBox* get_check_box_node(godot::NodePath const& path) const; - godot::Label* get_label_node(godot::NodePath const& path) const; - godot::Panel* get_panel_node(godot::NodePath const& path) const; - godot::TextureProgressBar* get_progress_bar_node(godot::NodePath const& path) const; - godot::TextureRect* get_texture_rect_node(godot::NodePath const& path) const; + static godot::Button* get_button_from_node(godot::Node* node); + static godot::CheckBox* get_check_box_from_node(godot::Node* node); + static godot::Label* get_label_from_node(godot::Node* node); + static godot::Panel* get_panel_from_node(godot::Node* node); + static godot::TextureProgressBar* get_progress_bar_from_node(godot::Node* node); + static godot::TextureRect* get_texture_rect_from_node(godot::Node* node); + static GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_node(godot::Node* node); + + godot::Button* get_button_from_nodepath(godot::NodePath const& path) const; + godot::CheckBox* get_check_box_from_nodepath(godot::NodePath const& path) const; + godot::Label* get_label_from_nodepath(godot::NodePath const& path) const; + godot::Panel* get_panel_from_nodepath(godot::NodePath const& path) const; + godot::TextureProgressBar* get_progress_bar_from_nodepath(godot::NodePath const& path) const; + godot::TextureRect* get_texture_rect_from_nodepath(godot::NodePath const& path) const; + GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_nodepath(godot::NodePath const& path) const; /* Helper functions to get textures from TextureRects and Buttons. */ - godot::Ref<godot::Texture2D> get_texture_from_node(godot::NodePath const& path) const; - godot::Ref<GFXIconTexture> get_gfx_icon_texture_from_node(godot::NodePath const& path) const; - godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture_from_node(godot::NodePath const& path) const; - godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture_from_node(godot::NodePath const& path) const; + static godot::Ref<godot::Texture2D> get_texture_from_node(godot::Node* node); + static godot::Ref<GFXIconTexture> get_gfx_icon_texture_from_node(godot::Node* node); + static godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture_from_node(godot::Node* node); + static godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture_from_node(godot::Node* node); + + godot::Ref<godot::Texture2D> get_texture_from_nodepath(godot::NodePath const& path) const; + godot::Ref<GFXIconTexture> get_gfx_icon_texture_from_nodepath(godot::NodePath const& path) const; + godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture_from_nodepath(godot::NodePath const& path) const; + godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture_from_nodepath(godot::NodePath const& path) const; godot::Error hide_node(godot::NodePath const& path) const; - godot::Error hide_nodes(godot::Array const& paths) const; + godot::Error hide_nodes(godot::TypedArray<godot::NodePath> const& paths) const; }; } diff --git a/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp new file mode 100644 index 0000000..ff88781 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp @@ -0,0 +1,177 @@ +#include "GUIOverlappingElementsBox.hpp" + +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace OpenVic; +using namespace godot; + +using OpenVic::Utilities::std_view_to_godot_string; + +Error GUIOverlappingElementsBox::_update_child_positions() { + ERR_FAIL_NULL_V(gui_overlapping_elements_box, FAILED); + const int32_t child_count = get_child_count(); + if (child_count <= 0) { + return OK; + } + + const auto _get_child = [this](int32_t index) -> Control* { return Object::cast_to<Control>(get_child(index)); }; + + const float box_width = get_size().x; + const float child_width = _get_child(0)->get_size().x; + + const float max_spacing = box_width / (child_count + 1); + const float default_spacing = child_width + gui_overlapping_elements_box->get_spacing().to_float(); + + const float spacing = std::min(max_spacing, default_spacing); + + const float total_width = spacing * (child_count - 1) + child_width; + + float starting_x = 0.0f; + Error err = OK; + using enum GUI::AlignedElement::format_t; + switch (gui_overlapping_elements_box->get_format()) { + case left: + break; + case centre: + starting_x = (box_width - total_width) / 2; + break; + case right: + starting_x = box_width - total_width; + break; + default: + UtilityFunctions::push_error( + "Invalid GUIOverlappingElementsBox alignment: ", + static_cast<int32_t>(gui_overlapping_elements_box->get_format()) + ); + err = FAILED; + } + + for (int32_t index = 0; index < child_count; ++index) { + _get_child(index)->set_position({ starting_x + spacing * index, 0.0f }); + } + + return err; +} + +void GUIOverlappingElementsBox::_bind_methods() { + OV_BIND_METHOD(GUIOverlappingElementsBox::clear); + OV_BIND_METHOD(GUIOverlappingElementsBox::clear_children); + OV_BIND_METHOD(GUIOverlappingElementsBox::set_child_count, { "new_count" }); + + OV_BIND_METHOD(GUIOverlappingElementsBox::get_gui_overlapping_elements_box_name); + + OV_BIND_METHOD( + GUIOverlappingElementsBox::set_gui_child_element_name, { "gui_child_element_file", "gui_child_element_name" } + ); + OV_BIND_METHOD(GUIOverlappingElementsBox::get_gui_child_element_name); +} + +void GUIOverlappingElementsBox::_notification(int what) { + if (what == NOTIFICATION_SORT_CHILDREN) { + _update_child_positions(); + } +} + +GUIOverlappingElementsBox::GUIOverlappingElementsBox() + : gui_overlapping_elements_box { nullptr }, gui_child_element { nullptr } {} + +void GUIOverlappingElementsBox::clear() { + clear_children(); + gui_child_element = nullptr; + gui_overlapping_elements_box = nullptr; +} + +void GUIOverlappingElementsBox::clear_children() { + set_child_count(0); +} + +Error GUIOverlappingElementsBox::set_child_count(int32_t new_count) { + ERR_FAIL_COND_V_MSG(new_count < 0, FAILED, "Child count must be non-negative"); + int32_t child_count = get_child_count(); + if (child_count == new_count) { + return OK; + } else if (child_count > new_count) { + do { + remove_child(get_child(--child_count)); + } while (child_count > new_count); + return OK; + } else { + ERR_FAIL_NULL_V_MSG( + gui_child_element, FAILED, vformat( + "GUIOverlappingElementsBox child element is null (child_count = %d, new_count = %d)", child_count, new_count + ) + ); + Error err = OK; + const String gui_child_element_name = std_view_to_godot_string(gui_child_element->get_name()) + "_"; + do { + Control* child = nullptr; + const String name = gui_child_element_name + itos(child_count); + if (!UITools::generate_gui_element(gui_child_element, name, child)) { + UtilityFunctions::push_error("Error generating GUIOverlappingElementsBox child element ", name); + err = FAILED; + } + ERR_FAIL_NULL_V_MSG( + child, FAILED, vformat( + "Failed to generate GUIOverlappingElementsBox child element %s (child_count = %d, new_count = %d)", + name, child_count, new_count + ) + ); + add_child(child); + child_count++; + } while (child_count < new_count); + return err; + } +} + +Error GUIOverlappingElementsBox::set_gui_overlapping_elements_box( + GUI::OverlappingElementsBox const* new_gui_overlapping_elements_box +) { + if (gui_overlapping_elements_box == new_gui_overlapping_elements_box) { + return OK; + } + gui_overlapping_elements_box = new_gui_overlapping_elements_box; + if (gui_overlapping_elements_box == nullptr) { + return OK; + } + + set_custom_minimum_size(Utilities::to_godot_fvec2(gui_overlapping_elements_box->get_size())); + queue_sort(); + return OK; +} + +String GUIOverlappingElementsBox::get_gui_overlapping_elements_box_name() const { + return gui_overlapping_elements_box != nullptr ? std_view_to_godot_string(gui_overlapping_elements_box->get_name()) : String {}; +} + +Error GUIOverlappingElementsBox::set_gui_child_element(GUI::Element const* new_gui_child_element) { + clear_children(); + gui_child_element = new_gui_child_element; + return OK; +} + +Error GUIOverlappingElementsBox::set_gui_child_element_name( + String const& gui_child_element_file, String const& gui_child_element_name +) { + if (gui_child_element_file.is_empty() && gui_child_element_name.is_empty()) { + return set_gui_child_element(nullptr); + } + ERR_FAIL_COND_V_MSG( + gui_child_element_file.is_empty(), FAILED, + vformat("GUI child element file name is empty but element name is not: %s", gui_child_element_name) + ); + ERR_FAIL_COND_V_MSG( + gui_child_element_name.is_empty(), FAILED, + vformat("GUI child element name is empty but file name is not: %s", gui_child_element_file) + ); + GUI::Element const* const new_gui_child_element = UITools::get_gui_element(gui_child_element_file, gui_child_element_name); + ERR_FAIL_NULL_V(new_gui_child_element, FAILED); + return set_gui_child_element(new_gui_child_element); +} + +String GUIOverlappingElementsBox::get_gui_child_element_name() const { + return gui_child_element != nullptr ? std_view_to_godot_string(gui_child_element->get_name()) : String {}; +} diff --git a/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.hpp b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.hpp new file mode 100644 index 0000000..5a420f2 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include <godot_cpp/classes/container.hpp> + +#include <openvic-simulation/interface/GUI.hpp> + +namespace OpenVic { + class GUIOverlappingElementsBox : public godot::Container { + GDCLASS(GUIOverlappingElementsBox, godot::Container) + + GUI::OverlappingElementsBox const* PROPERTY(gui_overlapping_elements_box); + GUI::Element const* PROPERTY(gui_child_element); + + godot::Error _update_child_positions(); + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUIOverlappingElementsBox(); + + /* Reset gui_overlapping_elements_box and gui_child_element to nullptr, and remove all child elements. */ + void clear(); + + /* Remove all child elements. */ + void clear_children(); + + /* Add or remove child elements to reach the specified count. */ + godot::Error set_child_count(int32_t new_count); + + /* Set the GUI::OverlappingElementsBox. This does not affect any existing child elements. */ + godot::Error set_gui_overlapping_elements_box(GUI::OverlappingElementsBox const* new_gui_overlapping_elements_box); + + /* Return the name of the GUI::OverlappingElementsBox, or an empty String if it's null. */ + godot::String get_gui_overlapping_elements_box_name() const; + + /* Set the child GUI::Element, removing all previous child elements (even if the child GUI::Element doesn't change). */ + godot::Error set_gui_child_element(GUI::Element const* new_gui_child_element); + + /* Search for a GUI::Element with the specfied name and, if successful, + * set the child element to it using set_gui_child_element. */ + godot::Error set_gui_child_element_name( + godot::String const& gui_child_element_file, godot::String const& gui_child_element_name + ); + + /* Return the name of the child GUI::Element, or an empty String if it's null. */ + godot::String get_gui_child_element_name() const; + }; +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 65bdc60..278365e 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -6,6 +6,7 @@ #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUINode.hpp" +#include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -46,6 +47,7 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class<GFXMaskedFlagTexture>(); ClassDB::register_class<GFXPieChartTexture>(); ClassDB::register_class<GUINode>(); + ClassDB::register_class<GUIOverlappingElementsBox>(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp index 8e9eb41..581c7fa 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.cpp +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -15,11 +15,6 @@ using OpenVic::Utilities::std_to_godot_string; void AssetManager::_bind_methods() { OV_BIND_METHOD(AssetManager::get_image, { "path" }); OV_BIND_METHOD(AssetManager::get_texture, { "path" }); - - OV_BIND_SMETHOD(AssetManager::make_icon, { "texture", "frame", "frame_count" }); - OV_BIND_METHOD(AssetManager::get_icon, { "texture", "frame", "frame_count" }); - - OV_BIND_METHOD(AssetManager::get_texture_or_icon, { "path", "frame", "frame_count" }); OV_BIND_METHOD(AssetManager::get_font, { "name" }); } @@ -96,46 +91,6 @@ Ref<ImageTexture> AssetManager::get_texture(StringName path) { } } -Ref<AtlasTexture> AssetManager::make_icon(Ref<Texture2D> texture, GFX::frame_t frame, GFX::frame_t frame_count) { - ERR_FAIL_NULL_V(texture, nullptr); - - 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--; - const Vector2i size = texture->get_size(); - const Rect2i region { frame * size.x / frame_count, 0, size.x / frame_count, size.y }; - - Ref<AtlasTexture> atlas; - atlas.instantiate(); - ERR_FAIL_NULL_V(atlas, nullptr); - atlas->set_atlas(texture); - atlas->set_region(region); - return atlas; -} - -Ref<AtlasTexture> AssetManager::get_icon(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { - Ref<ImageTexture> texture = get_texture(path); - ERR_FAIL_NULL_V(texture, nullptr); - return make_icon(texture, frame, frame_count); -} - -Ref<Texture2D> AssetManager::get_texture_or_icon(StringName path, GFX::frame_t frame, GFX::frame_t frame_count) { - if (frame_count < 2) { - if (frame > frame_count) { - UtilityFunctions::push_warning("Invalid frame index ", frame, " out of count ", frame_count); - } - return get_texture(path); - } else { - return get_icon(path, frame, frame_count); - } -} - Ref<Font> AssetManager::get_font(StringName name) { const font_map_t::const_iterator it = fonts.find(name); if (it != fonts.end()) { diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp index 625944d..e6a7664 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.hpp +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -44,17 +44,6 @@ namespace OpenVic { * or texture creation fails. */ godot::Ref<godot::ImageTexture> get_texture(godot::StringName path); - /* Extract the specified frame of the texture, which is treated as a single row of frame_count frames. */ - static godot::Ref<godot::AtlasTexture> make_icon( - godot::Ref<godot::Texture2D> texture, GFX::frame_t frame, GFX::frame_t frame_count - ); - - /* Load a texture as with get_texture, and extract the specified frame as with make_icon. */ - godot::Ref<godot::AtlasTexture> get_icon(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); - - /* Load a texture as with get_texture if frame_count <= 1 otherwise as with get_icon. */ - godot::Ref<godot::Texture2D> get_texture_or_icon(godot::StringName path, GFX::frame_t frame, GFX::frame_t frame_count); - /* Search for and load a font with the specified name from the game defines' font directory, first checking the * AssetManager's font cache in case it has already been loaded, and returning nullptr if font loading fails. */ godot::Ref<godot::Font> get_font(godot::StringName name); diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index a53ba54..0e64313 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -8,6 +8,8 @@ #include <openvic-simulation/utility/Logger.hpp> +#include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/LoadLocalisation.hpp" #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -48,7 +50,9 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::get_selected_province_index); OV_BIND_METHOD(GameSingleton::set_selected_province, { "index" }); - OV_BIND_METHOD(GameSingleton::expand_building, { "province_index", "building_type_identifier" }); + OV_BIND_METHOD(GameSingleton::get_province_building_count); + OV_BIND_METHOD(GameSingleton::get_province_building_identifier, { "building_index" }); + OV_BIND_METHOD(GameSingleton::expand_selected_province_building, { "building_index" }); OV_BIND_METHOD(GameSingleton::get_slave_pop_icon_index); OV_BIND_METHOD(GameSingleton::get_administrative_pop_icon_index); OV_BIND_METHOD(GameSingleton::get_rgo_owner_pop_icon_index); @@ -135,51 +139,23 @@ 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); } -template<std::derived_from<HasIdentifierAndColour> T> -static Array _distribution_to_pie_chart_array(fixed_point_map_t<T const*> const& dist) { - using entry_t = std::pair<T const*, fixed_point_t>; - std::vector<entry_t> sorted_dist; - sorted_dist.reserve(dist.size()); - for (entry_t const& entry : dist) { - if (entry.first != nullptr) { - sorted_dist.push_back(entry); - } else { - UtilityFunctions::push_error("Null distribution key with value ", entry.second.to_float()); - } - } - std::sort(sorted_dist.begin(), sorted_dist.end(), [](entry_t const& lhs, entry_t const& rhs) -> bool { - return lhs.second < rhs.second; - }); - static const StringName identifier_key = "identifier"; - static const StringName colour_key = "colour"; - static const StringName weight_key = "weight"; - Array array; - for (auto const& [key, val] : sorted_dist) { - Dictionary sub_dict; - sub_dict[identifier_key] = std_view_to_godot_string(key->get_identifier()); - sub_dict[colour_key] = Utilities::to_godot_color(key->get_colour()); - sub_dict[weight_key] = val.to_float(); - array.push_back(sub_dict); - } - return array; -} - 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_controller_key = "controller"; - static const StringName province_info_life_rating_key = "life_rating"; + static const StringName province_info_slave_status_key = "slave_status"; + static const StringName province_info_colony_status_key = "colony_status"; static const StringName province_info_terrain_type_key = "terrain_type"; + static const StringName province_info_life_rating_key = "life_rating"; + static const StringName province_info_controller_key = "controller"; + static const StringName province_info_rgo_name_key = "rgo_name"; + static const StringName province_info_rgo_icon_key = "rgo_icon"; static const StringName province_info_crime_name_key = "crime_name"; static const StringName province_info_crime_icon_key = "crime_icon"; 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_name_key = "rgo_name"; - static const StringName province_info_rgo_icon_key = "rgo_icon"; - static const StringName province_info_colony_status_key = "colony_status"; - static const StringName province_info_slave_status_key = "slave_status"; + static const StringName province_info_cores_key = "cores"; static const StringName province_info_buildings_key = "buildings"; Province const* province = game_manager.get_map().get_province_by_index(index); @@ -195,6 +171,17 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[province_info_region_key] = std_view_to_godot_string(region->get_identifier()); } + ret[province_info_slave_status_key] = province->get_slave(); + + ret[province_info_colony_status_key] = static_cast<int32_t>(province->get_colony_status()); + + TerrainType const* terrain_type = province->get_terrain_type(); + if (terrain_type != nullptr) { + ret[province_info_terrain_type_key] = std_view_to_godot_string(terrain_type->get_identifier()); + } + + ret[province_info_life_rating_key] = province->get_life_rating(); + Country const* controller = province->get_controller(); if (controller != nullptr) { ret[province_info_controller_key] = std_view_to_godot_string(controller->get_identifier()); @@ -206,37 +193,39 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { ret[province_info_rgo_icon_key] = static_cast<int32_t>(rgo->get_index()); } - ret[province_info_life_rating_key] = province->get_life_rating(); - - TerrainType const* terrain_type = province->get_terrain_type(); - if (terrain_type != nullptr) { - ret[province_info_terrain_type_key] = std_view_to_godot_string(terrain_type->get_identifier()); - } - Crime const* crime = province->get_crime(); if (crime != nullptr) { ret[province_info_crime_name_key] = std_view_to_godot_string(crime->get_identifier()); ret[province_info_crime_icon_key] = static_cast<int32_t>(crime->get_icon()); } - ret[province_info_colony_status_key] = static_cast<int32_t>(province->get_colony_status()); - ret[province_info_slave_status_key] = province->get_slave(); - ret[province_info_total_population_key] = province->get_total_population(); + fixed_point_map_t<PopType const*> const& pop_types = province->get_pop_type_distribution(); if (!pop_types.empty()) { - ret[province_info_pop_types_key] = _distribution_to_pie_chart_array(pop_types); + ret[province_info_pop_types_key] = GFXPieChartTexture::distribution_to_slices_array(pop_types); } + fixed_point_map_t<Ideology const*> const& ideologies = province->get_ideology_distribution(); if (!ideologies.empty()) { - ret[province_info_pop_ideologies_key] = _distribution_to_pie_chart_array(ideologies); + ret[province_info_pop_ideologies_key] = GFXPieChartTexture::distribution_to_slices_array(ideologies); } + fixed_point_map_t<Culture const*> const& cultures = province->get_culture_distribution(); if (!cultures.empty()) { - ret[province_info_pop_cultures_key] = _distribution_to_pie_chart_array(cultures); + ret[province_info_pop_cultures_key] = GFXPieChartTexture::distribution_to_slices_array(cultures); + } + + std::vector<Country const*> const& cores = province->get_cores(); + if (!cores.empty()) { + PackedStringArray cores_array; + cores_array.resize(cores.size()); + for (size_t idx = 0; idx < cores.size(); ++idx) { + cores_array[idx] = std_view_to_godot_string(cores[idx]->get_identifier()); + } + ret[province_info_cores_key] = cores_array; } - 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"; @@ -245,13 +234,14 @@ Dictionary GameSingleton::get_province_info_from_index(int32_t index) const { std::vector<BuildingInstance> const& buildings = province->get_buildings(); if (!buildings.empty()) { - Array buildings_array; + /* This system relies on the province buildings all being present in the right order. It will have to + * be changed if we want to support variable combinations and permutations of province buildings. */ + TypedArray<Dictionary> buildings_array; buildings_array.resize(buildings.size()); for (size_t idx = 0; idx < buildings.size(); ++idx) { BuildingInstance const& building = buildings[idx]; Dictionary building_dict; - building_dict[building_info_building_key] = std_view_to_godot_string(building.get_identifier()); building_dict[building_info_level_key] = static_cast<int32_t>(building.get_level()); building_dict[building_info_expansion_state_key] = static_cast<int32_t>(building.get_expansion_state()); building_dict[building_info_start_date_key] = std_to_godot_string(building.get_start_date().to_string()); @@ -321,14 +311,14 @@ Error GameSingleton::_update_colour_image() { return FAILED; } /* We reshape the list of colours into a square, as each texture dimensions cannot exceed 16384. */ - static constexpr int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * 4); - static constexpr int32_t colour_image_width = PROVINCE_INDEX_SQRT * sizeof(Mapmode::base_stripe_t) / sizeof(colour_t); + static constexpr int32_t PROVINCE_INDEX_SQRT = 1 << (sizeof(Province::index_t) * CHAR_BIT / 2); + static constexpr int32_t colour_image_width = PROVINCE_INDEX_SQRT * sizeof(Mapmode::base_stripe_t) / sizeof(colour_argb_t); /* Province count + null province, rounded up to next multiple of PROVINCE_INDEX_SQRT. * Rearranged from: (map.get_province_count() + 1) + (PROVINCE_INDEX_SQRT - 1) */ static const int32_t colour_image_height = (map.get_province_count() + PROVINCE_INDEX_SQRT) / PROVINCE_INDEX_SQRT; static PackedByteArray colour_data_array; - static const int64_t colour_data_array_size = colour_image_width * colour_image_height * sizeof(colour_t); + static const int64_t colour_data_array_size = colour_image_width * colour_image_height * sizeof(colour_argb_t); colour_data_array.resize(colour_data_array_size); Error err = OK; @@ -386,12 +376,25 @@ void GameSingleton::set_selected_province(int32_t index) { emit_signal("province_selected", index); } -Error GameSingleton::expand_building(int32_t province_index, String const& building_type_identifier) { - if (!game_manager.expand_building(province_index, godot_to_std_string(building_type_identifier))) { - UtilityFunctions::push_error("Failed to expand ", building_type_identifier, " at province index ", province_index); - return FAILED; +int32_t GameSingleton::get_province_building_count() const { + return game_manager.get_economy_manager().get_building_type_manager().get_province_building_types().size(); +} + +String GameSingleton::get_province_building_identifier(int32_t index) const { + std::vector<BuildingType const*> const& province_building_types = + game_manager.get_economy_manager().get_building_type_manager().get_province_building_types(); + ERR_FAIL_COND_V_MSG( + index < 0 || index >= province_building_types.size(), {}, vformat("Invalid province building index: %d", index) + ); + return std_view_to_godot_string(province_building_types[index]->get_identifier()); +} + +Error GameSingleton::expand_selected_province_building(int32_t building_index) { + const bool ret = game_manager.expand_selected_province_building(building_index); + if (!ret) { + UtilityFunctions::push_error("Failed to expand the currently selected province's building index ", building_index); } - return OK; + return ERR(ret); } int32_t GameSingleton::get_slave_pop_icon_index() const { @@ -493,7 +496,7 @@ Error GameSingleton::_load_map_images(bool flip_vertical) { Map::shape_pixel_t const* province_shape_data = game_manager.get_map().get_province_shape_image().data(); const Vector2i divided_dims = province_dims / image_subdivisions; - Array province_shape_images; + TypedArray<Image> province_shape_images; province_shape_images.resize(image_subdivisions.x * image_subdivisions.y); for (int32_t v = 0; v < image_subdivisions.y; ++v) { for (int32_t u = 0; u < image_subdivisions.x; ++u) { @@ -561,12 +564,14 @@ Error GameSingleton::_load_terrain_variants() { } const int32_t slice_size = sheet_width / SHEET_DIMS; - Array terrain_images; + TypedArray<Image> terrain_images; { - static constexpr colour_t TERRAIN_WATER_INDEX_COLOUR = 0xFFFFFF; - Ref<Image> water_image = Image::create(slice_size, slice_size, false, terrain_sheet->get_format()); + // TODO - load "map/terrain/water.dds" instead of using a solid colour (issue is that it's 512x512 + // while the texturesheet slices are 256x256, so they can't share a Texture2DArray) + const Ref<Image> water_image = Utilities::make_solid_colour_image( + { 0.1f, 0.1f, 0.5f }, slice_size, slice_size, terrain_sheet->get_format() + ); ERR_FAIL_NULL_V_EDMSG(water_image, FAILED, "Failed to create water terrain image"); - water_image->fill({ 0.1f, 0.1f, 0.5f }); terrain_images.append(water_image); } Error err = OK; diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index 5559f1b..1f3905e 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -7,8 +7,6 @@ #include <openvic-simulation/GameManager.hpp> #include <openvic-simulation/dataloader/Dataloader.hpp> -#include "openvic-extension/UIAdapter.hpp" - namespace OpenVic { class GameSingleton : public godot::Object { @@ -95,7 +93,9 @@ namespace OpenVic { int32_t get_selected_province_index() const; void set_selected_province(int32_t index); - godot::Error expand_building(int32_t province_index, godot::String const& building_type_identifier); + int32_t get_province_building_count() const; + godot::String get_province_building_identifier(int32_t building_index) const; + godot::Error expand_selected_province_building(int32_t building_index); int32_t get_slave_pop_icon_index() const; int32_t get_administrative_pop_icon_index() const; int32_t get_rgo_owner_pop_icon_index() const; diff --git a/extension/src/openvic-extension/UIAdapter.cpp b/extension/src/openvic-extension/utility/UITools.cpp index cbe898c..cba65a4 100644 --- a/extension/src/openvic-extension/UIAdapter.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -1,4 +1,4 @@ -#include "UIAdapter.hpp" +#include "UITools.hpp" #include <godot_cpp/classes/button.hpp> #include <godot_cpp/classes/check_box.hpp> @@ -14,38 +14,53 @@ #include "openvic-extension/classes/GFXIconTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; using namespace OpenVic; +using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string_name; -bool GodotGUIBuilder::generate_element( - GUI::Element const* element, String const& name, AssetManager& asset_manager, Control*& result -) { - if (element == nullptr) { - UtilityFunctions::push_error("Invalid element passed to GodotGUIBuilder - null!"); - return false; - } - static const std::map<std::string_view, bool (*)(GUI::Element const&, String const&, AssetManager&, Control*&)> 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 } +GFX::Sprite const* UITools::get_gfx_sprite(godot::String const& gfx_sprite) { + GameSingleton* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, nullptr); + GFX::Sprite const* sprite = game_singleton->get_game_manager().get_ui_manager().get_sprite_by_identifier( + godot_to_std_string(gfx_sprite) + ); + ERR_FAIL_NULL_V_MSG(sprite, nullptr, vformat("GFX sprite not found: %s", gfx_sprite)); + return sprite; +} + +GUI::Element const* UITools::get_gui_element(godot::String const& gui_file, godot::String const& gui_element) { + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, nullptr); + GUI::Scene const* scene = + game_singleton->get_game_manager().get_ui_manager().get_scene_by_identifier(godot_to_std_string(gui_file)); + ERR_FAIL_NULL_V_MSG(scene, nullptr, vformat("Failed to find GUI file %s", gui_file)); + GUI::Element const* element = scene->get_scene_element_by_identifier(godot_to_std_string(gui_element)); + ERR_FAIL_NULL_V_MSG(element, nullptr, vformat("Failed to find GUI element %s in GUI file %s", gui_element, gui_file)); + return element; +} + +/* GUI::Element tree -> godot::Control tree conversion code below: */ + +namespace OpenVic { + struct generate_gui_args_t { + GUI::Element const& element; + godot::String const& name; + AssetManager& asset_manager; + godot::Control*& result; + + constexpr generate_gui_args_t( + GUI::Element const& new_element, godot::String const& new_name, AssetManager& new_asset_manager, + godot::Control*& new_result + ) : element { new_element }, name { new_name }, asset_manager { new_asset_manager }, result { new_result } {} }; - const decltype(type_map)::const_iterator it = type_map.find(element->get_type()); - if (it != type_map.end()) { - return it->second(*element, name, asset_manager, result); - } else { - UtilityFunctions::push_error("Invalid GUI element type: ", std_view_to_godot_string(element->get_type())); - result = nullptr; - return false; - } } template<std::derived_from<Control> T> @@ -75,24 +90,23 @@ static T* new_control(GUI::Element const& element, String const& name) { std_view_to_godot_string(element.get_name())); } node->set_position(Utilities::to_godot_fvec2(element.get_position())); + node->set_h_size_flags(Control::SizeFlags::SIZE_SHRINK_BEGIN); + node->set_v_size_flags(Control::SizeFlags::SIZE_SHRINK_BEGIN); node->set_focus_mode(Control::FOCUS_NONE); return node; } -bool GodotGUIBuilder::generate_icon( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::Icon const& icon = static_cast<GUI::Icon const&>(element); +static bool generate_icon(generate_gui_args_t&& args) { + GUI::Icon const& icon = static_cast<GUI::Icon const&>(args.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<GFX::TextureSprite>()) { - TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, args.name); ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); GFX::TextureSprite const* texture_sprite = icon.get_sprite()->cast_to<GFX::TextureSprite>(); @@ -104,9 +118,9 @@ bool GodotGUIBuilder::generate_icon( ret = false; } - result = godot_texture_rect; + args.result = godot_texture_rect; } else if (icon.get_sprite()->is_type<GFX::MaskedFlag>()) { - TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, args.name); ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); GFX::MaskedFlag const* masked_flag = icon.get_sprite()->cast_to<GFX::MaskedFlag>(); @@ -118,16 +132,16 @@ bool GodotGUIBuilder::generate_icon( ret = false; } - result = godot_texture_rect; + args.result = godot_texture_rect; } else if (icon.get_sprite()->is_type<GFX::ProgressBar>()) { - TextureProgressBar* godot_progress_bar = new_control<TextureProgressBar>(icon, name); + TextureProgressBar* godot_progress_bar = new_control<TextureProgressBar>(icon, args.name); ERR_FAIL_NULL_V_MSG( godot_progress_bar, false, vformat("Failed to create TextureProgressBar for GUI icon %s", icon_name) ); const StringName back_texture_file = std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::ProgressBar>()->get_back_texture_file()); - const Ref<ImageTexture> back_texture = asset_manager.get_texture(back_texture_file); + const Ref<ImageTexture> back_texture = args.asset_manager.get_texture(back_texture_file); if (back_texture.is_valid()) { godot_progress_bar->set_under_texture(back_texture); } else { @@ -137,7 +151,7 @@ bool GodotGUIBuilder::generate_icon( const StringName progress_texture_file = std_view_to_godot_string_name(icon.get_sprite()->cast_to<GFX::ProgressBar>()->get_progress_texture_file()); - const Ref<ImageTexture> progress_texture = asset_manager.get_texture(progress_texture_file); + const Ref<ImageTexture> progress_texture = args.asset_manager.get_texture(progress_texture_file); if (progress_texture.is_valid()) { godot_progress_bar->set_progress_texture(progress_texture); } else { @@ -147,9 +161,9 @@ bool GodotGUIBuilder::generate_icon( ret = false; } - result = godot_progress_bar; + args.result = godot_progress_bar; } else if (icon.get_sprite()->is_type<GFX::PieChart>()) { - TextureRect* godot_texture_rect = new_control<TextureRect>(icon, name); + TextureRect* godot_texture_rect = new_control<TextureRect>(icon, args.name); ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); GFX::PieChart const* pie_chart = icon.get_sprite()->cast_to<GFX::PieChart>(); @@ -165,7 +179,7 @@ bool GodotGUIBuilder::generate_icon( ret = false; } - result = godot_texture_rect; + args.result = godot_texture_rect; } else if (icon.get_sprite()->is_type<GFX::LineChart>()) { } else { @@ -180,16 +194,13 @@ bool GodotGUIBuilder::generate_icon( return ret; } -bool GodotGUIBuilder::generate_button( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::Button const& button = static_cast<GUI::Button const&>(element); +static bool generate_button(generate_gui_args_t&& args) { + GUI::Button const& button = static_cast<GUI::Button const&>(args.element); // TODO - shortcut, sprite, text - result = nullptr; const String button_name = std_view_to_godot_string(button.get_name()); - Button* godot_button = new_control<Button>(button, name); + Button* godot_button = new_control<Button>(button, args.name); ERR_FAIL_NULL_V_MSG(godot_button, false, vformat("Failed to create Button for GUI button %s", button_name)); if (!button.get_text().empty()) { @@ -220,7 +231,7 @@ bool GodotGUIBuilder::generate_button( } if (texture.is_valid()) { - godot_button->set_size(texture->get_size()); + godot_button->set_custom_minimum_size(texture->get_size()); Ref<StyleBoxTexture> stylebox; stylebox.instantiate(); if (stylebox.is_valid()) { @@ -239,7 +250,7 @@ bool GodotGUIBuilder::generate_button( if (button.get_font() != nullptr) { const StringName font_file = std_view_to_godot_string_name(button.get_font()->get_fontname()); - const Ref<Font> font = asset_manager.get_font(font_file); + const Ref<Font> font = args.asset_manager.get_font(font_file); if (font.is_valid()) { godot_button->add_theme_font_override("font", font); } else { @@ -250,20 +261,17 @@ bool GodotGUIBuilder::generate_button( godot_button->add_theme_color_override("font_color", colour); } - result = godot_button; + args.result = godot_button; return ret; } -bool GodotGUIBuilder::generate_checkbox( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::Checkbox const& checkbox = static_cast<GUI::Checkbox const&>(element); +static bool generate_checkbox(generate_gui_args_t&& args) { + GUI::Checkbox const& checkbox = static_cast<GUI::Checkbox const&>(args.element); // TODO - shortcut, sprite, text - result = nullptr; const String checkbox_name = std_view_to_godot_string(checkbox.get_name()); - CheckBox* godot_checkbox = new_control<CheckBox>(checkbox, name); + CheckBox* godot_checkbox = new_control<CheckBox>(checkbox, args.name); ERR_FAIL_NULL_V_MSG(godot_checkbox, false, vformat("Failed to create CheckBox for GUI checkbox %s", checkbox_name)); bool ret = true; @@ -272,7 +280,7 @@ bool GodotGUIBuilder::generate_checkbox( if (texture_sprite != nullptr) { Ref<GFXIconTexture> icon_texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite, 1); if (icon_texture.is_valid()) { - godot_checkbox->set_size(icon_texture->get_size()); + godot_checkbox->set_custom_minimum_size(icon_texture->get_size()); godot_checkbox->add_theme_icon_override("unchecked", icon_texture); } else { UtilityFunctions::push_error("Failed to make unchecked GFXIconTexture for GUI checkbox ", checkbox_name); @@ -297,26 +305,22 @@ bool GodotGUIBuilder::generate_checkbox( ret = false; } - result = godot_checkbox; + args.result = godot_checkbox; return ret; } -bool GodotGUIBuilder::generate_text( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::Text const& text = static_cast<GUI::Text const&>(element); +static bool generate_text(generate_gui_args_t&& args) { + GUI::Text const& text = static_cast<GUI::Text const&>(args.element); - result = nullptr; const String text_name = std_view_to_godot_string(text.get_name()); - Label* godot_label = new_control<Label>(text, name); + Label* godot_label = new_control<Label>(text, args.name); ERR_FAIL_NULL_V_MSG(godot_label, false, vformat("Failed to create Label for GUI text %s", text_name)); godot_label->set_text(std_view_to_godot_string(text.get_text())); - godot_label->set_size(Utilities::to_godot_fvec2(text.get_max_size())); + godot_label->set_custom_minimum_size(Utilities::to_godot_fvec2(text.get_max_size())); using enum GUI::AlignedElement::format_t; - using enum HorizontalAlignment; static const std::map<GUI::AlignedElement::format_t, HorizontalAlignment> format_map { { left, HORIZONTAL_ALIGNMENT_LEFT }, { centre, HORIZONTAL_ALIGNMENT_CENTER }, @@ -333,7 +337,7 @@ bool GodotGUIBuilder::generate_text( bool ret = true; if (text.get_font() != nullptr) { const StringName font_file = std_view_to_godot_string_name(text.get_font()->get_fontname()); - const Ref<Font> font = asset_manager.get_font(font_file); + const Ref<Font> font = args.asset_manager.get_font(font_file); if (font.is_valid()) { godot_label->add_theme_font_override("font", font); } else { @@ -344,67 +348,59 @@ bool GodotGUIBuilder::generate_text( godot_label->add_theme_color_override("font_color", colour); } - result = godot_label; + args.result = godot_label; return ret; } -bool GodotGUIBuilder::generate_overlapping_elements( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::OverlappingElementsBox const& overlapping_elements = static_cast<GUI::OverlappingElementsBox const&>(element); +static bool generate_overlapping_elements(generate_gui_args_t&& args) { + GUI::OverlappingElementsBox const& overlapping_elements = static_cast<GUI::OverlappingElementsBox const&>(args.element); - result = nullptr; const String overlapping_elements_name = std_view_to_godot_string(overlapping_elements.get_name()); - ColorRect* godot_rect = new_control<ColorRect>(overlapping_elements, name); + GUIOverlappingElementsBox* box = new_control<GUIOverlappingElementsBox>(overlapping_elements, args.name); ERR_FAIL_NULL_V_MSG( - godot_rect, false, vformat("Failed to create ColorRect for GUI overlapping elements %s", overlapping_elements_name) + box, false, + vformat("Failed to create GUIOverlappingElementsBox for GUI overlapping elements %s", overlapping_elements_name) ); - - godot_rect->set_size(Utilities::to_godot_fvec2(overlapping_elements.get_size())); - godot_rect->set_color({ 0.0f, 0.5f, 1.0f, 0.2f }); - - result = godot_rect; - return true; + const bool ret = box->set_gui_overlapping_elements_box(&overlapping_elements) == OK; + args.result = box; + return ret; } -bool GodotGUIBuilder::generate_listbox( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::ListBox const& listbox = static_cast<GUI::ListBox const&>(element); +static bool generate_listbox(generate_gui_args_t&& args) { + GUI::ListBox const& listbox = static_cast<GUI::ListBox const&>(args.element); - result = nullptr; const String listbox_name = std_view_to_godot_string(listbox.get_name()); - ColorRect* godot_rect = new_control<ColorRect>(listbox, name); + ColorRect* godot_rect = new_control<ColorRect>(listbox, args.name); ERR_FAIL_NULL_V_MSG(godot_rect, false, vformat("Failed to create ColorRect for GUI listbox %s", listbox_name)); - godot_rect->set_size(Utilities::to_godot_fvec2(listbox.get_size())); + godot_rect->set_custom_minimum_size(Utilities::to_godot_fvec2(listbox.get_size())); godot_rect->set_color({ 1.0f, 0.5f, 0.0f, 0.2f }); - result = godot_rect; + args.result = godot_rect; return true; } -bool GodotGUIBuilder::generate_window( - GUI::Element const& element, String const& name, AssetManager& asset_manager, Control*& result -) { - GUI::Window const& window = static_cast<GUI::Window const&>(element); +/* Forward declaration for use in generate_window. */ +static bool generate_element(GUI::Element const* element, String const& name, AssetManager& asset_manager, Control*& result); + +static bool generate_window(generate_gui_args_t&& args) { + GUI::Window const& window = static_cast<GUI::Window const&>(args.element); // TODO - moveable, fullscreen, dontRender (disable visibility?) - result = nullptr; const String window_name = std_view_to_godot_string(window.get_name()); - Panel* godot_panel = new_control<Panel>(window, name); + Panel* godot_panel = new_control<Panel>(window, args.name); ERR_FAIL_NULL_V_MSG(godot_panel, false, vformat("Failed to create Panel for GUI window %s", window_name)); - godot_panel->set_size(Utilities::to_godot_fvec2(window.get_size())); + godot_panel->set_custom_minimum_size(Utilities::to_godot_fvec2(window.get_size())); godot_panel->set_self_modulate({ 1.0f, 1.0f, 1.0f, 0.0f }); bool ret = true; for (std::unique_ptr<GUI::Element> const& element : window.get_window_elements()) { Control* node = nullptr; - const bool element_ret = generate_element(element.get(), "", asset_manager, node); + const bool element_ret = generate_element(element.get(), "", args.asset_manager, node); if (node != nullptr) { godot_panel->add_child(node); } @@ -414,6 +410,41 @@ bool GodotGUIBuilder::generate_window( } } - result = godot_panel; + args.result = godot_panel; return ret; } + +static bool generate_element(GUI::Element const* element, String const& name, AssetManager& asset_manager, Control*& result) { + ERR_FAIL_NULL_V(element, false); + static const std::map<std::string_view, bool (*)(generate_gui_args_t&&)> 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, name, asset_manager, result }); + } else { + UtilityFunctions::push_error("Invalid GUI element type: ", std_view_to_godot_string(element->get_type())); + return false; + } +} + +bool UITools::generate_gui_element( + GUI::Element const* element, String const& name, Control*& result +) { + result = nullptr; + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, false); + return generate_element(element, name, *asset_manager, result); +} + +bool UITools::generate_gui_element( + godot::String const& gui_file, godot::String const& gui_element, godot::String const& name, godot::Control*& result +) { + return generate_gui_element(get_gui_element(gui_file, gui_element), name, result); +} diff --git a/extension/src/openvic-extension/utility/UITools.hpp b/extension/src/openvic-extension/utility/UITools.hpp new file mode 100644 index 0000000..65cf17a --- /dev/null +++ b/extension/src/openvic-extension/utility/UITools.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include <godot_cpp/classes/control.hpp> + +#include <openvic-simulation/interface/GFX.hpp> +#include <openvic-simulation/interface/GUI.hpp> + +namespace OpenVic::UITools { + GFX::Sprite const* get_gfx_sprite(godot::String const& gfx_sprite); + GUI::Element const* get_gui_element(godot::String const& gui_file, godot::String const& gui_element); + + bool generate_gui_element( + GUI::Element const* element, godot::String const& name, godot::Control*& result + ); + bool generate_gui_element( + godot::String const& gui_file, godot::String const& gui_element, godot::String const& name, godot::Control*& result + ); +} diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 099b5a9..8293e70 100644 --- a/extension/src/openvic-extension/utility/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -122,3 +122,18 @@ Ref<FontFile> Utilities::load_godot_font(String const& fnt_path, Ref<Image> cons } return font; } + +Ref<Image> Utilities::make_solid_colour_image(Color const& colour, int32_t width, int32_t height, Image::Format format) { + const Ref<Image> result = Image::create(width, height, false, format); + ERR_FAIL_NULL_V(result, nullptr); + result->fill(colour); + return result; +} + +Ref<ImageTexture> Utilities::make_solid_colour_texture(Color const& colour, int32_t width, int32_t height, Image::Format format) { + const Ref<Image> image = make_solid_colour_image(colour, width, height, format); + ERR_FAIL_NULL_V(image, nullptr); + const Ref<ImageTexture> result = ImageTexture::create_from_image(image); + ERR_FAIL_NULL_V(result, nullptr); + return result; +} diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 6eeb285..9bbc700 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -1,7 +1,7 @@ #pragma once #include <godot_cpp/classes/font_file.hpp> -#include <godot_cpp/classes/image.hpp> +#include <godot_cpp/classes/image_texture.hpp> #include <openvic-simulation/types/Colour.hpp> #include <openvic-simulation/types/Date.hpp> @@ -37,12 +37,8 @@ namespace OpenVic::Utilities { godot::String date_to_formatted_string(Date date); - inline godot::Color to_godot_color(colour_t colour) { - return { - colour_byte_to_float((colour >> 16) & 0xFF), - colour_byte_to_float((colour >> 8) & 0xFF), - colour_byte_to_float(colour & 0xFF) - }; + inline godot::Color to_godot_color(IsColour auto colour) { + return { colour.redf(), colour.greenf(), colour.bluef(), colour.alphaf() }; } inline godot::Vector2i to_godot_ivec2(ivec2_t vec) { @@ -63,4 +59,14 @@ namespace OpenVic::Utilities { /* Load a Font from anywhere on the machine, combining the ".fnt" file loaded from the given path with the * already-loaded image file containing the actual characters. */ godot::Ref<godot::FontFile> load_godot_font(godot::String const& fnt_path, godot::Ref<godot::Image> const& image); + + godot::Ref<godot::Image> make_solid_colour_image( + godot::Color const& colour, int32_t width, int32_t height, + godot::Image::Format format = godot::Image::Format::FORMAT_RGBA8 + ); + + godot::Ref<godot::ImageTexture> make_solid_colour_texture( + godot::Color const& colour, int32_t width, int32_t height, + godot::Image::Format format = godot::Image::Format::FORMAT_RGBA8 + ); } |