aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension/classes
diff options
context:
space:
mode:
Diffstat (limited to 'extension/src/openvic-extension/classes')
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp183
-rw-r--r--extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp55
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.cpp167
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.hpp49
-rw-r--r--extension/src/openvic-extension/classes/GUINode.cpp161
-rw-r--r--extension/src/openvic-extension/classes/GUINode.hpp53
6 files changed, 668 insertions, 0 deletions
diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
new file mode 100644
index 0000000..3636855
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp
@@ -0,0 +1,183 @@
+#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/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;
+
+Error GFXMaskedFlagTexture::_generate_combined_image() {
+ ERR_FAIL_NULL_V(overlay_image, FAILED);
+ bool can_update = true;
+ if (combined_image.is_null() || combined_image->get_size() != overlay_image->get_size()) {
+ combined_image = Image::create(
+ overlay_image->get_width(), overlay_image->get_height(), false, overlay_image->get_format()
+ );
+ ERR_FAIL_NULL_V(combined_image, FAILED);
+ can_update = false;
+ }
+
+ if (mask_image.is_valid() && flag_image.is_valid()) {
+ const Vector2i centre_translation = (mask_image->get_size() - combined_image->get_size()) / 2;
+ for (Vector2i combined_image_point { 0, 0 }; combined_image_point.y < combined_image->get_height(); ++combined_image_point.y) {
+ for (combined_image_point.x = 0; combined_image_point.x < combined_image->get_width(); ++combined_image_point.x) {
+ const Color overlay_image_colour = overlay_image->get_pixelv(combined_image_point);
+ // Translate to mask_image coordinates, keeping the centres of each image aligned.
+ const Vector2i mask_image_point = combined_image_point + centre_translation;
+ if (
+ 0 <= mask_image_point.x && mask_image_point.x < mask_image->get_width() &&
+ 0 <= mask_image_point.y && mask_image_point.y < mask_image->get_height()
+ ) {
+ const Color mask_image_colour = mask_image->get_pixelv(mask_image_point);
+ // Rescale from mask_image to flag_image coordinates.
+ const Vector2i flag_image_point = mask_image_point * flag_image->get_size() / mask_image->get_size();
+ Color flag_image_colour = flag_image->get_pixelv(flag_image_point);
+ flag_image_colour.a = mask_image_colour.a;
+ combined_image->set_pixelv(combined_image_point, flag_image_colour.blend(overlay_image_colour));
+ } else {
+ combined_image->set_pixelv(combined_image_point, overlay_image_colour);
+ }
+ }
+ }
+ } else {
+ combined_image->blit_rect(overlay_image, overlay_image->get_used_rect(), {});
+ }
+
+ if (can_update) {
+ update(combined_image);
+ } else {
+ set_image(combined_image);
+ }
+ return OK;
+}
+
+void GFXMaskedFlagTexture::_bind_methods() {
+ OV_BIND_METHOD(GFXMaskedFlagTexture::clear);
+
+ OV_BIND_METHOD(GFXMaskedFlagTexture::set_gfx_masked_flag_name, { "gfx_masked_flag_name" });
+ 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::get_flag_country_name);
+ OV_BIND_METHOD(GFXMaskedFlagTexture::get_flag_type);
+}
+
+GFXMaskedFlagTexture::GFXMaskedFlagTexture() : gfx_masked_flag { nullptr }, flag_country { nullptr } {}
+
+Ref<GFXMaskedFlagTexture> GFXMaskedFlagTexture::make_gfx_masked_flag_texture(GFX::MaskedFlag const* gfx_masked_flag) {
+ Ref<GFXMaskedFlagTexture> masked_flag_texture;
+ masked_flag_texture.instantiate();
+ ERR_FAIL_NULL_V(masked_flag_texture, nullptr);
+ if (masked_flag_texture->set_gfx_masked_flag(gfx_masked_flag) == OK) {
+ return masked_flag_texture;
+ } else {
+ return nullptr;
+ }
+}
+
+void GFXMaskedFlagTexture::clear() {
+ gfx_masked_flag = nullptr;
+ flag_country = nullptr;
+ flag_type = String {};
+
+ overlay_image.unref();
+ mask_image.unref();
+ flag_image.unref();
+}
+
+Error GFXMaskedFlagTexture::set_gfx_masked_flag(GFX::MaskedFlag const* new_gfx_masked_flag) {
+ if (gfx_masked_flag == new_gfx_masked_flag) {
+ return OK;
+ }
+ if (new_gfx_masked_flag == nullptr) {
+ clear();
+ return OK;
+ }
+ AssetManager* asset_manager = AssetManager::get_singleton();
+ ERR_FAIL_NULL_V(asset_manager, FAILED);
+
+ const StringName overlay_file = std_view_to_godot_string_name(new_gfx_masked_flag->get_overlay_file());
+ const Ref<Image> new_overlay_image = asset_manager->get_image(overlay_file);
+ ERR_FAIL_NULL_V_MSG(new_overlay_image, FAILED, vformat("Failed to load flag overlay image: %s", overlay_file));
+
+ const StringName mask_file = std_view_to_godot_string_name(new_gfx_masked_flag->get_mask_file());
+ const Ref<Image> new_mask_image = asset_manager->get_image(mask_file);
+ ERR_FAIL_NULL_V_MSG(new_mask_image, FAILED, vformat("Failed to load flag mask image: %s", mask_file));
+
+ gfx_masked_flag = new_gfx_masked_flag;
+ overlay_image = new_overlay_image;
+ mask_image = new_mask_image;
+
+ return _generate_combined_image();
+}
+
+Error GFXMaskedFlagTexture::set_gfx_masked_flag_name(String const& gfx_masked_flag_name) {
+ 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::MaskedFlag const* new_masked_flag = sprite->cast_to<GFX::MaskedFlag>();
+ ERR_FAIL_NULL_V_MSG(
+ new_masked_flag, FAILED, vformat(
+ "Invalid type for GFX sprite %s: %s (expected %s)", gfx_masked_flag_name,
+ std_view_to_godot_string(sprite->get_type()), std_view_to_godot_string(GFX::MaskedFlag::get_type_static())
+ )
+ );
+ return set_gfx_masked_flag(new_masked_flag);
+}
+
+String GFXMaskedFlagTexture::get_gfx_masked_flag_name() const {
+ return gfx_masked_flag != nullptr ? std_view_to_godot_string(gfx_masked_flag->get_name()) : String {};
+}
+
+Error GFXMaskedFlagTexture::set_flag_country_and_type(Country const* new_flag_country, StringName const& new_flag_type) {
+ if (flag_country == new_flag_country && flag_type == new_flag_type) {
+ return OK;
+ }
+ if (new_flag_country != nullptr) {
+ GameSingleton* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, FAILED);
+
+ const Ref<Image> new_flag_image = game_singleton->get_flag_image(new_flag_country, new_flag_type);
+ ERR_FAIL_NULL_V(new_flag_image, FAILED);
+
+ flag_country = new_flag_country;
+ flag_type = new_flag_type;
+ flag_image = new_flag_image;
+ } else {
+ flag_country = nullptr;
+ flag_type = String {};
+ flag_image.unref();
+ }
+ return _generate_combined_image();
+}
+
+Error GFXMaskedFlagTexture::set_flag_country_name_and_type(String const& new_flag_country_name, StringName const& new_flag_type) {
+ if (new_flag_country_name.is_empty()) {
+ return set_flag_country_and_type(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_and_type(new_flag_country, new_flag_type);
+}
+
+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
new file mode 100644
index 0000000..be3b15a
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <godot_cpp/classes/image_texture.hpp>
+
+#include <openvic-simulation/country/Country.hpp>
+#include <openvic-simulation/interface/GFX.hpp>
+
+namespace OpenVic {
+ class GFXMaskedFlagTexture : public godot::ImageTexture {
+ GDCLASS(GFXMaskedFlagTexture, godot::ImageTexture)
+
+ GFX::MaskedFlag const* PROPERTY(gfx_masked_flag);
+ Country const* PROPERTY(flag_country);
+ godot::StringName PROPERTY(flag_type);
+
+ godot::Ref<godot::Image> overlay_image, mask_image, flag_image, combined_image;
+
+ godot::Error _generate_combined_image();
+
+ protected:
+ static void _bind_methods();
+
+ public:
+ GFXMaskedFlagTexture();
+
+ /* Create a GFXMaskedFlagTexture using the specific GFX::MaskedFlag.
+ * Returns nullptr if setting gfx_masked_flag fails. */
+ static godot::Ref<GFXMaskedFlagTexture> make_gfx_masked_flag_texture(GFX::MaskedFlag const* gfx_masked_flag);
+
+ /* Reset gfx_masked_flag, flag_country and flag_type to nullptr/an empty string, and unreference all images.
+ * This does not affect the godot::ImageTexture, which cannot be reset to a null or empty image. */
+ void clear();
+
+ /* Set the GFX::MaskedFlag, load its overlay and mask textures, and regenerate the combined image. */
+ godot::Error set_gfx_masked_flag(GFX::MaskedFlag const* new_gfx_masked_flag);
+
+ /* 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 */
+ 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. */
+ godot::Error set_flag_country_and_type(Country const* new_flag_country, godot::StringName const& new_flag_type);
+
+ /* Look up the country with the specified identifier, then call set_flag_country_and_type with the country and
+ * specified flag_type as arguments. */
+ godot::Error set_flag_country_name_and_type(
+ 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 */
+ 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
new file mode 100644
index 0000000..6fe2fe0
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp
@@ -0,0 +1,167 @@
+#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"
+
+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;
+
+#define 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;
+ }
+ const int32_t pie_chart_size = 2 * gfx_pie_chart->get_size();
+ bool can_update = true;
+ if (
+ pie_chart_image.is_null() || pie_chart_image->get_width() != pie_chart_size ||
+ pie_chart_image->get_height() != pie_chart_size
+ ) {
+ pie_chart_image = Image::create(pie_chart_size, pie_chart_size, false, Image::FORMAT_RGBA8);
+ ERR_FAIL_NULL_V(pie_chart_image, FAILED);
+ can_update = false;
+ }
+
+ static const Color background_colour { 0.0f, 0.0f, 0.0f, 0.0f };
+ if (!slices.empty()) {
+ const float pie_chart_radius = gfx_pie_chart->get_size();
+ const Vector2 centre_translation = Vector2 { 0.5f, 0.5f } - static_cast<Vector2>(pie_chart_image->get_size()) * 0.5f;
+ for (Vector2i point { 0, 0 }; point.y < pie_chart_image->get_height(); ++point.y) {
+ for (point.x = 0; point.x < pie_chart_image->get_width(); ++point.x) {
+ const Vector2 offset = centre_translation + point;
+ if (offset.length() <= pie_chart_radius) {
+ float theta = 0.5f * PI + atan2(offset.y, offset.x);
+ if (theta < 0.0f) {
+ theta += 2.0f * PI;
+ }
+ /* Rescale angle so that total_weight is a full rotation. */
+ theta *= total_weight / (2.0f * PI);
+ Color colour = slices.front().first;
+ /* Find the slice theta lies in. */
+ for (slice_t const& slice : slices) {
+ if (theta <= slice.second) {
+ colour = slice.first;
+ break;
+ } else {
+ theta -= slice.second;
+ }
+ }
+ pie_chart_image->set_pixelv(point, colour);
+ } else {
+ pie_chart_image->set_pixelv(point, background_colour);
+ }
+ }
+ }
+ } else {
+ pie_chart_image->fill(background_colour);
+ }
+
+ if (can_update) {
+ update(pie_chart_image);
+ } else {
+ set_image(pie_chart_image);
+ }
+ return OK;
+}
+
+Error GFXPieChartTexture::set_slices(Array const& new_slices) {
+ static const StringName colour_key = "colour";
+ static const StringName weight_key = "weight";
+
+ slices.clear();
+ 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;
+ }
+ 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;
+ }
+ total_weight += slice.second;
+ slices.emplace_back(std::move(slice));
+ }
+ return _generate_pie_chart_image();
+}
+
+void GFXPieChartTexture::_bind_methods() {
+ OV_BIND_METHOD(GFXPieChartTexture::clear);
+
+ 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" });
+}
+
+GFXPieChartTexture::GFXPieChartTexture() : total_weight { 0.0f } {}
+
+Ref<GFXPieChartTexture> GFXPieChartTexture::make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart) {
+ Ref<GFXPieChartTexture> pie_chart_texture;
+ pie_chart_texture.instantiate();
+ ERR_FAIL_NULL_V(pie_chart_texture, nullptr);
+ if (pie_chart_texture->set_gfx_pie_chart(gfx_pie_chart) == OK) {
+ return pie_chart_texture;
+ } else {
+ return nullptr;
+ }
+}
+
+void GFXPieChartTexture::clear() {
+ gfx_pie_chart = nullptr;
+ slices.clear();
+ total_weight = 0.0f;
+
+ pie_chart_image.unref();
+}
+
+Error GFXPieChartTexture::set_gfx_pie_chart(GFX::PieChart const* new_gfx_pie_chart) {
+ if (gfx_pie_chart == new_gfx_pie_chart) {
+ return OK;
+ }
+ if (new_gfx_pie_chart == nullptr) {
+ clear();
+ return OK;
+ }
+
+ gfx_pie_chart = new_gfx_pie_chart;
+
+ return _generate_pie_chart_image();
+}
+
+Error GFXPieChartTexture::set_gfx_pie_chart_name(String const& gfx_pie_chart_name) {
+ 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::PieChart const* new_pie_chart = sprite->cast_to<GFX::PieChart>();
+ ERR_FAIL_NULL_V_MSG(
+ new_pie_chart, FAILED, vformat(
+ "Invalid type for GFX sprite %s: %s (expected %s)", gfx_pie_chart_name,
+ std_view_to_godot_string(sprite->get_type()), std_view_to_godot_string(GFX::PieChart::get_type_static())
+ )
+ );
+ return set_gfx_pie_chart(new_pie_chart);
+}
+
+String GFXPieChartTexture::get_gfx_pie_chart_name() const {
+ return gfx_pie_chart != nullptr ? std_view_to_godot_string(gfx_pie_chart->get_name()) : String {};
+}
diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
new file mode 100644
index 0000000..315b00e
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <godot_cpp/classes/image_texture.hpp>
+
+#include <openvic-simulation/interface/GFX.hpp>
+
+namespace OpenVic {
+ class GFXPieChartTexture : public godot::ImageTexture {
+ GDCLASS(GFXPieChartTexture, godot::ImageTexture)
+
+ using slice_t = std::pair<godot::Color, float>;
+
+ GFX::PieChart const* PROPERTY(gfx_pie_chart);
+ std::vector<slice_t> slices;
+ float total_weight;
+ godot::Ref<godot::Image> pie_chart_image;
+
+ godot::Error _generate_pie_chart_image();
+
+ protected:
+ static void _bind_methods();
+
+ public:
+ GFXPieChartTexture();
+
+ /* Set slices given new_slices, an Array of Dictionaries, each with the following keys:
+ * - colour: Color
+ * - weight: float
+ */
+ godot::Error set_slices(godot::Array const& new_slices);
+
+ /* Create a GFXPieChartTexture using the specific GFX::PieChart.
+ * Returns nullptr if setting gfx_pie_chart fails. */
+ static godot::Ref<GFXPieChartTexture> make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart);
+
+ /* Reset gfx_pie_chart, flag_country and flag_type to nullptr/an empty string, and unreference all images.
+ * This does not affect the godot::ImageTexture, which cannot be reset to a null or empty image. */
+ void clear();
+
+ /* Set the GFX::PieChart and regenerate the pie chart image. */
+ godot::Error set_gfx_pie_chart(GFX::PieChart const* new_gfx_pie_chart);
+
+ /* 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 */
+ 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
new file mode 100644
index 0000000..1d55c54
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GUINode.cpp
@@ -0,0 +1,161 @@
+#include "GUINode.hpp"
+
+#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/Utilities.hpp"
+
+using namespace godot;
+using namespace OpenVic;
+
+using OpenVic::Utilities::godot_to_std_string;
+using OpenVic::Utilities::std_view_to_godot_string;
+
+void GUINode::_bind_methods() {
+ 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" });
+
+ 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" });
+
+ 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);
+ 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()));
+ err = FAILED;
+ }
+ if (result != nullptr) {
+ add_child(result);
+ }
+ 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));
+ 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()));
+ 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);
+}
+
+TextureProgressBar* GUINode::get_progress_bar_node(NodePath const& path) const {
+ return _get_cast_node<TextureProgressBar>(path);
+}
+
+TextureRect* GUINode::get_texture_rect_node(NodePath const& path) const {
+ return _get_cast_node<TextureRect>(path);
+}
+
+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));
+ 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));
+ 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));
+ 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
+ )
+ );
+ 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)
+ );
+ return result;
+ }
+ ERR_FAIL_V_MSG(nullptr, vformat("Failed to cast node %s to type TextureRect or Button", 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);
+ 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()));
+ return result;
+}
+
+Ref<GFXIconTexture> GUINode::get_gfx_icon_texture_from_node(NodePath const& path) const {
+ return _get_cast_texture_from_node<GFXIconTexture>(path);
+}
+
+Ref<GFXMaskedFlagTexture> GUINode::get_gfx_masked_flag_texture_from_node(NodePath const& path) const {
+ return _get_cast_texture_from_node<GFXMaskedFlagTexture>(path);
+}
+
+Ref<GFXPieChartTexture> GUINode::get_gfx_pie_chart_texture_from_node(NodePath const& path) const {
+ return _get_cast_texture_from_node<GFXPieChartTexture>(path);
+}
+
+Error GUINode::hide_node(NodePath const& path) const {
+ CanvasItem* node = _get_cast_node<CanvasItem>(path);
+ if (node == nullptr) {
+ return FAILED;
+ }
+ node->hide();
+ return OK;
+}
+
+Error GUINode::hide_nodes(Array const& paths) const {
+ Error ret = OK;
+ for (int32_t i = 0; i < paths.size(); ++i) {
+ if (hide_node(paths[i]) != OK) {
+ ret = FAILED;
+ }
+ }
+ return ret;
+}
diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp
new file mode 100644
index 0000000..1671547
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GUINode.hpp
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <godot_cpp/classes/button.hpp>
+#include <godot_cpp/classes/check_box.hpp>
+#include <godot_cpp/classes/label.hpp>
+#include <godot_cpp/classes/panel.hpp>
+#include <godot_cpp/classes/texture_progress_bar.hpp>
+#include <godot_cpp/classes/texture_rect.hpp>
+
+#include <openvic-simulation/interface/GUI.hpp>
+
+#include "openvic-extension/classes/GFXIconTexture.hpp"
+#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp"
+#include "openvic-extension/classes/GFXPieChartTexture.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;
+
+ 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;
+
+ /* 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;
+
+ godot::Error hide_node(godot::NodePath const& path) const;
+ godot::Error hide_nodes(godot::Array const& paths) const;
+ };
+}