diff options
author | hop311 <hop3114@gmail.com> | 2023-12-19 00:38:54 +0100 |
---|---|---|
committer | hop311 <hop3114@gmail.com> | 2023-12-25 19:06:13 +0100 |
commit | 4e9764ee29fb7b453862835d5aa3a081b0f9a269 (patch) | |
tree | a59c5b960a706a383b8ebd1dbcfb704067a5b51b /extension/src/openvic-extension/classes | |
parent | d26c990d9a5596a3ef3b32ba1cb0f99950cd6d34 (diff) |
Back to UI Work
- UIAdapter -> UITools with cleaner API
- GUIOverlappingElementsBox (for core and modifier icons)
- Improved GUINode API
- Province building slots
- TypeHints for files in the GameSession folder
- Incorporate SIM strong colour types
Diffstat (limited to 'extension/src/openvic-extension/classes')
10 files changed, 439 insertions, 148 deletions
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; + }; +} |