diff options
author | 2023-12-30 15:59:20 +0100 | |
---|---|---|
committer | 2023-12-30 15:59:20 +0100 | |
commit | c0cc6e202c33fb3889d0025b1b04148ae66545f2 (patch) | |
tree | 65ef49b0d12cf307269defeb0960f25ea442f777 /extension | |
parent | f2f42d1b65605876a6ec12626be55183b4681b88 (diff) |
Added button state textures + block colour progress bars
Diffstat (limited to 'extension')
17 files changed, 421 insertions, 69 deletions
diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation -Subproject bff91f78f9c5339079c10adfbf8232e5159c1a2 +Subproject 0a425fbe05d6138b753c0e4a7c06f06695bde8a diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp new file mode 100644 index 0000000..e6dff1f --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp @@ -0,0 +1,106 @@ +#include "GFXButtonStateTexture.hpp" + +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace OpenVic; +using namespace godot; + +void GFXButtonStateTexture::_bind_methods() { + OV_BIND_METHOD(GFXButtonStateTexture::set_button_state, { "new_button_state" }); + OV_BIND_METHOD(GFXButtonStateTexture::get_button_state); + + OV_BIND_SMETHOD(get_generate_state_image_func_name); + + OV_BIND_SMETHOD(button_state_to_theme_name, { "button_state" }); + OV_BIND_METHOD(GFXButtonStateTexture::get_button_state_theme); + + OV_BIND_METHOD(GFXButtonStateTexture::generate_state_image, { "source_image" }); + + BIND_ENUM_CONSTANT(HOVER); + BIND_ENUM_CONSTANT(PRESSED); + BIND_ENUM_CONSTANT(DISABLED); +} + +GFXButtonStateTexture::GFXButtonStateTexture() : button_state { HOVER } {} + +Ref<GFXButtonStateTexture> GFXButtonStateTexture::make_gfx_button_state_texture( + ButtonState button_state, Ref<Image> const& source_image +) { + Ref<GFXButtonStateTexture> button_state_texture; + button_state_texture.instantiate(); + ERR_FAIL_NULL_V(button_state_texture, nullptr); + button_state_texture->set_button_state(button_state); + if (source_image.is_valid()) { + ERR_FAIL_COND_V(button_state_texture->generate_state_image(source_image) != OK, nullptr); + } + return button_state_texture; +} + +void GFXButtonStateTexture::set_button_state(ButtonState new_button_state) { + ERR_FAIL_COND(new_button_state != HOVER && new_button_state != PRESSED && new_button_state != DISABLED); + button_state = new_button_state; +} + +Error GFXButtonStateTexture::generate_state_image(Ref<Image> const& source_image) { + ERR_FAIL_COND_V(source_image.is_null() || source_image->is_empty(), FAILED); + /* Whether we've already set the ImageTexture to an image of the right dimensions and format, + * and so can update it without creating and setting a new image, or not. */ + const bool can_update = state_image.is_valid() && state_image->get_size() == source_image->get_size() + && state_image->get_format() == source_image->get_format(); + if (!can_update) { + state_image = Image::create(source_image->get_width(), source_image->get_height(), false, source_image->get_format()); + ERR_FAIL_NULL_V(state_image, FAILED); + } + + static constexpr auto hover_colour = [](Color const& colour) -> Color { + return { std::min(colour.r + 0.1f, 1.0f), std::min(colour.g + 0.1f, 1.0f), std::min(colour.b + 0.1f, 1.0f), colour.a }; + }; + static constexpr auto pressed_colour = [](Color const& colour) -> Color { + return { std::max(colour.r - 0.1f, 0.0f), std::max(colour.g - 0.1f, 0.0f), std::max(colour.b - 0.1f, 0.0f), colour.a }; + }; + static constexpr auto disabled_colour = [](Color const& colour) -> Color { + const float luma = colour.get_luminance(); + return { luma, luma, luma, colour.a }; + }; + + const auto colour_func = button_state == HOVER ? hover_colour : button_state == PRESSED ? pressed_colour : disabled_colour; + + for (Vector2i point { 0, 0 }; point.y < state_image->get_height(); ++point.y) { + for (point.x = 0; point.x < state_image->get_width(); ++point.x) { + state_image->set_pixelv(point, colour_func(source_image->get_pixelv(point))); + } + } + + if (can_update) { + update(state_image); + } else { + set_image(state_image); + } + return OK; +} + +StringName const& GFXButtonStateTexture::get_generate_state_image_func_name() { + static const StringName generate_state_image_func_name = "generate_state_image"; + return generate_state_image_func_name; +} + +StringName const& GFXButtonStateTexture::button_state_to_theme_name(ButtonState button_state) { + static const StringName theme_name_hover = "hover"; + static const StringName theme_name_pressed = "pressed"; + static const StringName theme_name_disabled = "disabled"; + static const StringName theme_name_error = ""; + switch (button_state) { + case HOVER: + return theme_name_hover; + case PRESSED: + return theme_name_pressed; + case DISABLED: + return theme_name_disabled; + default: + return theme_name_error; + } +} + +StringName const& GFXButtonStateTexture::get_button_state_theme() const { + return button_state_to_theme_name(button_state); +} diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp new file mode 100644 index 0000000..32f4087 --- /dev/null +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <godot_cpp/classes/image_texture.hpp> + +#include <openvic-simulation/utility/Getters.hpp> + +namespace OpenVic { + class GFXButtonStateTexture : public godot::ImageTexture { + GDCLASS(GFXButtonStateTexture, godot::ImageTexture) + + public: + enum ButtonState { + HOVER, + PRESSED, + DISABLED + }; + + private: + ButtonState PROPERTY(button_state); + godot::Ref<godot::Image> state_image; + + protected: + static void _bind_methods(); + + public: + GFXButtonStateTexture(); + + /* Create a GFXButtonStateTexture using the specified godot::Image. Returns nullptr if generate_state_image fails. */ + static godot::Ref<GFXButtonStateTexture> make_gfx_button_state_texture( + ButtonState button_state, godot::Ref<godot::Image> const& source_image = nullptr + ); + + /* Set the ButtonState to be generated by this class (calling this does not trigger state image generation). */ + void set_button_state(ButtonState new_button_state); + + /* Generate a modified version of source_image and update the underlying godot::ImageTexture to use it. */ + godot::Error generate_state_image(godot::Ref<godot::Image> const& source_image); + + static godot::StringName const& get_generate_state_image_func_name(); + + static godot::StringName const& button_state_to_theme_name(ButtonState button_state); + godot::StringName const& get_button_state_theme() const; + }; +} + +VARIANT_ENUM_CAST(OpenVic::GFXButtonStateTexture::ButtonState); diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.cpp b/extension/src/openvic-extension/classes/GFXIconTexture.cpp index 895bf6b..5d29c07 100644 --- a/extension/src/openvic-extension/classes/GFXIconTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXIconTexture.cpp @@ -15,6 +15,11 @@ using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string_name; +StringName const& GFXIconTexture::_signal_image_updated() { + static const StringName signal_image_updated = "image_updated"; + return signal_image_updated; +} + void GFXIconTexture::_bind_methods() { OV_BIND_METHOD(GFXIconTexture::clear); @@ -26,16 +31,32 @@ void GFXIconTexture::_bind_methods() { OV_BIND_METHOD(GFXIconTexture::get_icon_count); ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_index"), "set_icon_index", "get_icon_index"); + + ADD_SIGNAL( + MethodInfo(_signal_image_updated(), PropertyInfo(Variant::OBJECT, "source_image", PROPERTY_HINT_RESOURCE_TYPE, "Image")) + ); } GFXIconTexture::GFXIconTexture() : gfx_texture_sprite { nullptr }, icon_index { GFX::NO_FRAMES }, icon_count { GFX::NO_FRAMES } {} -Ref<GFXIconTexture> GFXIconTexture::make_gfx_icon_texture(GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon) { +Ref<GFXIconTexture> GFXIconTexture::make_gfx_icon_texture( + GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon, + std::vector<Ref<GFXButtonStateTexture>> const& button_state_textures +) { Ref<GFXIconTexture> icon_texture; icon_texture.instantiate(); ERR_FAIL_NULL_V(icon_texture, nullptr); - icon_texture->set_gfx_texture_sprite(gfx_texture_sprite, icon); + + for (Ref<GFXButtonStateTexture> const& button_state_texture : button_state_textures) { + icon_texture->connect( + _signal_image_updated(), + Callable { *button_state_texture, GFXButtonStateTexture::get_generate_state_image_func_name() }, + CONNECT_PERSIST + ); + } + + ERR_FAIL_COND_V(icon_texture->set_gfx_texture_sprite(gfx_texture_sprite, icon) != OK, nullptr); return icon_texture; } @@ -57,9 +78,15 @@ Error GFXIconTexture::set_gfx_texture_sprite(GFX::TextureSprite const* new_gfx_t ERR_FAIL_NULL_V(asset_manager, FAILED); const StringName texture_file = std_view_to_godot_string_name(new_gfx_texture_sprite->get_texture_file()); + + /* Needed for GFXButtonStateTexture, AssetManager::get_texture will re-use this image from its internal cache. */ + const Ref<Image> image = asset_manager->get_image(texture_file); + ERR_FAIL_NULL_V_MSG(image, FAILED, vformat("Failed to load image: %s", texture_file)); + const Ref<ImageTexture> texture = asset_manager->get_texture(texture_file); ERR_FAIL_NULL_V_MSG(texture, FAILED, vformat("Failed to load texture: %s", texture_file)); + sprite_image = image; gfx_texture_sprite = new_gfx_texture_sprite; set_atlas(texture); icon_index = GFX::NO_FRAMES; @@ -98,6 +125,7 @@ Error GFXIconTexture::set_icon_index(int32_t new_icon_index) { } icon_index = GFX::NO_FRAMES; set_region({ {}, size }); + emit_signal(_signal_image_updated(), sprite_image); return OK; } if (GFX::NO_FRAMES < new_icon_index && new_icon_index <= icon_count) { @@ -111,5 +139,6 @@ Error GFXIconTexture::set_icon_index(int32_t new_icon_index) { } } set_region({ (icon_index - 1) * size.x / icon_count, 0, size.x / icon_count, size.y }); + emit_signal(_signal_image_updated(), sprite_image->get_region(get_region())); return OK; } diff --git a/extension/src/openvic-extension/classes/GFXIconTexture.hpp b/extension/src/openvic-extension/classes/GFXIconTexture.hpp index 176d855..06dac34 100644 --- a/extension/src/openvic-extension/classes/GFXIconTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXIconTexture.hpp @@ -4,6 +4,8 @@ #include <openvic-simulation/interface/GFX.hpp> +#include "openvic-extension/classes/GFXButtonStateTexture.hpp" + namespace OpenVic { class GFXIconTexture : public godot::AtlasTexture { GDCLASS(GFXIconTexture, godot::AtlasTexture) @@ -16,14 +18,22 @@ namespace OpenVic { GFX::frame_t PROPERTY(icon_index); GFX::frame_t PROPERTY(icon_count); + godot::Ref<godot::Image> sprite_image; + + static godot::StringName const& _signal_image_updated(); + protected: static void _bind_methods(); public: GFXIconTexture(); + /* Create a GFXIconTexture using the specified GFX::TextureSprite and icon index. Returns nullptr if + * set_gfx_texture_sprite fails. Connects the provided GFXButtonStateTextures (if any) to the + * GFXIconTexture's image_updated signal. */ static godot::Ref<GFXIconTexture> make_gfx_icon_texture( - GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES + GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES, + std::vector<godot::Ref<GFXButtonStateTexture>> const& button_state_textures = {} ); /* Discard the GFX::TextureSprite, atlas texture and icon index. */ diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp index 424be33..0a44e56 100644 --- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.cpp @@ -13,15 +13,22 @@ using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string_name; +StringName const& GFXMaskedFlagTexture::_signal_image_updated() { + static const StringName signal_image_updated = "image_updated"; + return signal_image_updated; +} + 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()) { + /* Whether we've already set the ImageTexture to an image of the right dimensions and format, + * and so can update it without creating and setting a new image, or not. */ + const bool can_update = combined_image.is_valid() && combined_image->get_size() == overlay_image->get_size() + && combined_image->get_format() == overlay_image->get_format(); + if (!can_update) { 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()) { @@ -55,6 +62,7 @@ Error GFXMaskedFlagTexture::_generate_combined_image() { } else { set_image(combined_image); } + emit_signal(_signal_image_updated(), combined_image); return OK; } @@ -68,14 +76,29 @@ void GFXMaskedFlagTexture::_bind_methods() { 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); + + ADD_SIGNAL( + MethodInfo(_signal_image_updated(), PropertyInfo(Variant::OBJECT, "source_image", PROPERTY_HINT_RESOURCE_TYPE, "Image")) + ); } 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> GFXMaskedFlagTexture::make_gfx_masked_flag_texture( + GFX::MaskedFlag const* gfx_masked_flag, std::vector<Ref<GFXButtonStateTexture>> const& button_state_textures +) { Ref<GFXMaskedFlagTexture> masked_flag_texture; masked_flag_texture.instantiate(); ERR_FAIL_NULL_V(masked_flag_texture, nullptr); + + for (Ref<GFXButtonStateTexture> const& button_state_texture : button_state_textures) { + masked_flag_texture->connect( + _signal_image_updated(), + Callable { *button_state_texture, GFXButtonStateTexture::get_generate_state_image_func_name() }, + CONNECT_PERSIST + ); + } + ERR_FAIL_COND_V(masked_flag_texture->set_gfx_masked_flag(gfx_masked_flag) != OK, nullptr); return masked_flag_texture; } diff --git a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp index f71a1d7..294b842 100644 --- a/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXMaskedFlagTexture.hpp @@ -5,6 +5,8 @@ #include <openvic-simulation/country/Country.hpp> #include <openvic-simulation/interface/GFX.hpp> +#include "openvic-extension/classes/GFXButtonStateTexture.hpp" + namespace OpenVic { class GFXMaskedFlagTexture : public godot::ImageTexture { GDCLASS(GFXMaskedFlagTexture, godot::ImageTexture) @@ -15,6 +17,8 @@ namespace OpenVic { godot::Ref<godot::Image> overlay_image, mask_image, flag_image, combined_image; + static godot::StringName const& _signal_image_updated(); + godot::Error _generate_combined_image(); protected: @@ -23,9 +27,12 @@ namespace OpenVic { 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); + /* Create a GFXMaskedFlagTexture using the specified GFX::MaskedFlag. Returns nullptr if gfx_masked_flag fails. + * Connects the provided GFXButtonStateTextures (if any) to the GFXMaskedFlagTexture's image_updated signal. */ + static godot::Ref<GFXMaskedFlagTexture> make_gfx_masked_flag_texture( + GFX::MaskedFlag const* gfx_masked_flag, + std::vector<godot::Ref<GFXButtonStateTexture>> const& button_state_textures = {} + ); /* 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. */ diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp index 63deeda..c9a2a72 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp @@ -12,6 +12,19 @@ using OpenVic::Utilities::godot_to_std_string; using OpenVic::Utilities::std_view_to_godot_string; using OpenVic::Utilities::std_view_to_godot_string_name; +StringName const& GFXPieChartTexture::_slice_identifier_key() { + static StringName const slice_identifier_key = "identifier"; + return slice_identifier_key; +} +StringName const& GFXPieChartTexture::_slice_colour_key() { + static StringName const slice_colour_key = "colour"; + return slice_colour_key; +} +StringName const& GFXPieChartTexture::_slice_weight_key() { + static StringName const slice_weight_key = "weight"; + return slice_weight_key; +} + static constexpr float PI = std::numbers::pi_v<float>; Error GFXPieChartTexture::_generate_pie_chart_image() { @@ -21,14 +34,13 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { 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 ( - pie_chart_image.is_null() || pie_chart_image->get_width() != pie_chart_size || - pie_chart_image->get_height() != pie_chart_size - ) { + /* Whether we've already set the ImageTexture to an image of the right dimensions, + * and so can update it without creating and setting a new image, or not. */ + const bool can_update = pie_chart_image.is_valid() && pie_chart_image->get_width() == pie_chart_size + && pie_chart_image->get_height() == pie_chart_size; + if (!can_update) { 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 }; @@ -74,17 +86,14 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { } Error GFXPieChartTexture::set_slices_array(TypedArray<Dictionary> 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]; ERR_CONTINUE_MSG( - !slice_dict.has(colour_key) || !slice_dict.has(weight_key), vformat("Invalid slice keys at index %d", i) + !slice_dict.has(_slice_colour_key()) || !slice_dict.has(_slice_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]); + slice_t slice = std::make_pair(slice_dict[_slice_colour_key()], slice_dict[_slice_weight_key()]); 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)); diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp index ad8e751..f8279e4 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -17,6 +17,10 @@ namespace OpenVic { float total_weight; godot::Ref<godot::Image> pie_chart_image; + static godot::StringName const& _slice_identifier_key(); + static godot::StringName const& _slice_colour_key(); + static godot::StringName const& _slice_weight_key(); + godot::Error _generate_pie_chart_image(); protected: @@ -34,34 +38,31 @@ namespace OpenVic { * 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 namespace godot; 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()) + entry.first == nullptr, 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; + TypedArray<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(); + Dictionary sub_dict; + sub_dict[_slice_identifier_key()] = Utilities::std_view_to_godot_string(key->get_identifier()); + sub_dict[_slice_colour_key()] = Utilities::to_godot_color(key->get_colour()); + sub_dict[_slice_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. */ + /* Create a GFXPieChartTexture using the specified GFX::PieChart. Returns nullptr if 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. diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index 043f65d..89701e0 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -49,6 +49,9 @@ void GUINode::_bind_methods() { } GUINode::GUINode() { + set_anchors_and_offsets_preset(PRESET_FULL_RECT); + set_h_grow_direction(GROW_DIRECTION_BOTH); + set_v_grow_direction(GROW_DIRECTION_BOTH); set_mouse_filter(MOUSE_FILTER_IGNORE); } diff --git a/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp index ff88781..921f633 100644 --- a/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp +++ b/extension/src/openvic-extension/classes/GUIOverlappingElementsBox.cpp @@ -120,6 +120,15 @@ Error GUIOverlappingElementsBox::set_child_count(int32_t new_count) { name, child_count, new_count ) ); + + static const StringName set_z_index_func_name = "set_z_index"; + static const StringName mouse_entered_signal_name = "mouse_entered"; + static const StringName mouse_exited_signal_name = "mouse_exited"; + + /* Move the child element in front of its neighbours when moused-over. */ + child->connect(mouse_entered_signal_name, Callable { child, set_z_index_func_name }.bind(1), CONNECT_PERSIST); + child->connect(mouse_exited_signal_name, Callable { child, set_z_index_func_name }.bind(0), CONNECT_PERSIST); + add_child(child); child_count++; } while (child_count < new_count); diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 278365e..046bc60 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -2,6 +2,7 @@ #include <godot_cpp/classes/engine.hpp> +#include "openvic-extension/classes/GFXButtonStateTexture.hpp" #include "openvic-extension/classes/GFXIconTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" @@ -43,6 +44,7 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { Engine::get_singleton()->register_singleton("AssetManager", AssetManager::get_singleton()); ClassDB::register_class<MapMesh>(); + ClassDB::register_class<GFXButtonStateTexture>(); ClassDB::register_class<GFXIconTexture>(); ClassDB::register_class<GFXMaskedFlagTexture>(); ClassDB::register_class<GFXPieChartTexture>(); diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 7ad0db0..e10efb3 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -25,6 +25,21 @@ using OpenVic::Utilities::std_view_to_godot_string; /* Maximum width or height a GPU texture can have. */ static constexpr int32_t GPU_DIM_LIMIT = 0x3FFF; +/* StringNames cannot be constructed until Godot has called StringName::setup(), + * so we must use these wrapper functions to delay their initialisation. */ +StringName const& GameSingleton::_signal_gamestate_updated() { + static const StringName signal_gamestate_updated = "gamestate_updated"; + return signal_gamestate_updated; +} +StringName const& GameSingleton::_signal_province_selected() { + static const StringName signal_province_selected = "province_selected"; + return signal_province_selected; +} +StringName const& GameSingleton::_signal_clock_state_changed() { + static const StringName signal_clock_state_changed = "clock_state_changed"; + return signal_clock_state_changed; +} + void GameSingleton::_bind_methods() { OV_BIND_SMETHOD(setup_logger); @@ -70,24 +85,31 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::get_longform_date); OV_BIND_METHOD(GameSingleton::try_tick); - ADD_SIGNAL(MethodInfo("state_updated")); - ADD_SIGNAL(MethodInfo("province_selected", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo(_signal_gamestate_updated())); + ADD_SIGNAL(MethodInfo(_signal_province_selected(), PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo(_signal_clock_state_changed())); } GameSingleton* GameSingleton::get_singleton() { return singleton; } -void GameSingleton::_on_state_updated() { +void GameSingleton::_on_gamestate_updated() { _update_colour_image(); - emit_signal("state_updated"); + emit_signal(_signal_gamestate_updated()); +} + +void GameSingleton::_on_clock_state_changed() { + emit_signal(_signal_clock_state_changed()); } /* REQUIREMENTS: * MAP-21, MAP-23, MAP-25, MAP-32, MAP-33, MAP-34 */ GameSingleton::GameSingleton() - : game_manager { std::bind(&GameSingleton::_on_state_updated, this) } { + : game_manager { + std::bind(&GameSingleton::_on_gamestate_updated, this), std::bind(&GameSingleton::_on_clock_state_changed, this) + } { ERR_FAIL_COND(singleton != nullptr); singleton = this; } @@ -360,7 +382,7 @@ int32_t GameSingleton::get_selected_province_index() const { void GameSingleton::set_selected_province(int32_t index) { game_manager.get_map().set_selected_province(index); _update_colour_image(); - emit_signal("province_selected", index); + emit_signal(_signal_province_selected(), index); } int32_t GameSingleton::get_province_building_count() const { @@ -411,35 +433,35 @@ String GameSingleton::float_to_formatted_string(float val) { } void GameSingleton::set_paused(bool paused) { - game_manager.get_clock().is_paused = paused; + game_manager.get_simulation_clock().set_paused(paused); } void GameSingleton::toggle_paused() { - game_manager.get_clock().is_paused = !game_manager.get_clock().is_paused; + game_manager.get_simulation_clock().toggle_paused(); } bool GameSingleton::is_paused() const { - return game_manager.get_clock().is_paused; + return game_manager.get_simulation_clock().is_paused(); } void GameSingleton::increase_speed() { - game_manager.get_clock().increase_simulation_speed(); + game_manager.get_simulation_clock().increase_simulation_speed(); } void GameSingleton::decrease_speed() { - game_manager.get_clock().decrease_simulation_speed(); + game_manager.get_simulation_clock().decrease_simulation_speed(); } int32_t GameSingleton::get_speed() const { - return game_manager.get_clock().get_simulation_speed(); + return game_manager.get_simulation_clock().get_simulation_speed(); } bool GameSingleton::can_increase_speed() const { - return game_manager.get_clock().can_increase_simulation_speed(); + return game_manager.get_simulation_clock().can_increase_simulation_speed(); } bool GameSingleton::can_decrease_speed() const { - return game_manager.get_clock().can_decrease_simulation_speed(); + return game_manager.get_simulation_clock().can_decrease_simulation_speed(); } String GameSingleton::get_longform_date() const { @@ -447,7 +469,7 @@ String GameSingleton::get_longform_date() const { } void GameSingleton::try_tick() { - game_manager.get_clock().conditionally_advance_game(); + game_manager.get_simulation_clock().conditionally_advance_game(); } Error GameSingleton::_load_map_images(bool flip_vertical) { diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index 56f3c25..5622688 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -25,6 +25,10 @@ namespace OpenVic { godot::Ref<godot::Texture2DArray> terrain_texture; std::map<Country const*, std::map<godot::StringName, godot::Ref<godot::Image>>> flag_image_map; + static godot::StringName const& _signal_gamestate_updated(); + static godot::StringName const& _signal_province_selected(); + static godot::StringName const& _signal_clock_state_changed(); + godot::Error _generate_terrain_texture_array(); godot::Error _load_map_images(bool flip_vertical); godot::Error _load_terrain_variants(); @@ -32,7 +36,8 @@ namespace OpenVic { /* Generate the province_colour_texture from the current mapmode. */ godot::Error _update_colour_image(); - void _on_state_updated(); + void _on_gamestate_updated(); + void _on_clock_state_changed(); protected: static void _bind_methods(); diff --git a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp index 6469820..8860105 100644 --- a/extension/src/openvic-extension/singletons/LoadLocalisation.cpp +++ b/extension/src/openvic-extension/singletons/LoadLocalisation.cpp @@ -30,7 +30,7 @@ LoadLocalisation::~LoadLocalisation() { _singleton = nullptr; } -Error LoadLocalisation::_load_file(String const& file_path, Ref<Translation> translation) const { +Error LoadLocalisation::_load_file(String const& file_path, Ref<Translation> const& translation) const { const Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::ModeFlags::READ); Error err = FileAccess::get_open_error(); ERR_FAIL_COND_V_MSG( diff --git a/extension/src/openvic-extension/singletons/LoadLocalisation.hpp b/extension/src/openvic-extension/singletons/LoadLocalisation.hpp index aeee076..9e39746 100644 --- a/extension/src/openvic-extension/singletons/LoadLocalisation.hpp +++ b/extension/src/openvic-extension/singletons/LoadLocalisation.hpp @@ -12,7 +12,7 @@ namespace OpenVic { godot::Ref<godot::Translation> translations[Dataloader::_LocaleCount]; - godot::Error _load_file(godot::String const& file_path, godot::Ref<godot::Translation> translation) const; + godot::Error _load_file(godot::String const& file_path, godot::Ref<godot::Translation> const& translation) const; godot::Ref<godot::Translation> _get_translation(godot::String const& locale) const; protected: diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index 67ea8f5..409a3ba 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -11,6 +11,7 @@ #include <godot_cpp/classes/theme.hpp> #include <godot_cpp/variant/utility_functions.hpp> +#include "openvic-extension/classes/GFXButtonStateTexture.hpp" #include "openvic-extension/classes/GFXIconTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" @@ -138,28 +139,75 @@ static bool generate_icon(generate_gui_args_t&& args) { 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 = args.asset_manager.get_texture(back_texture_file); + GFX::ProgressBar const* progress_bar = icon.get_sprite()->cast_to<GFX::ProgressBar>(); + + Ref<ImageTexture> back_texture; + if (!progress_bar->get_back_texture_file().empty()) { + const StringName back_texture_file = std_view_to_godot_string_name(progress_bar->get_back_texture_file()); + back_texture = args.asset_manager.get_texture(back_texture_file); + if (back_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to load progress bar sprite back texture ", back_texture_file, " for GUI icon ", icon_name + ); + ret = false; + } + } + if (back_texture.is_null()) { + const Color back_colour = Utilities::to_godot_color(progress_bar->get_back_colour()); + back_texture = Utilities::make_solid_colour_texture( + back_colour, progress_bar->get_size().x, progress_bar->get_size().y + ); + if (back_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to generate progress bar sprite ", back_colour, " back texture for GUI icon ", icon_name + ); + ret = false; + } + } if (back_texture.is_valid()) { godot_progress_bar->set_under_texture(back_texture); } else { - UtilityFunctions::push_error("Failed to load progress bar base sprite ", back_texture_file, " for GUI icon ", icon_name); + UtilityFunctions::push_error( + "Failed to create and set progress bar sprite back texture for GUI icon ", icon_name + ); ret = false; } - 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 = args.asset_manager.get_texture(progress_texture_file); + Ref<ImageTexture> progress_texture; + if (!progress_bar->get_progress_texture_file().empty()) { + const StringName progress_texture_file = std_view_to_godot_string_name(progress_bar->get_progress_texture_file()); + progress_texture = args.asset_manager.get_texture(progress_texture_file); + if (progress_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to load progress bar sprite progress texture ", progress_texture_file, " for GUI icon ", icon_name + ); + ret = false; + } + } + if (progress_texture.is_null()) { + const Color progress_colour = Utilities::to_godot_color(progress_bar->get_progress_colour()); + progress_texture = Utilities::make_solid_colour_texture( + progress_colour, progress_bar->get_size().x, progress_bar->get_size().y + ); + if (progress_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to generate progress bar sprite ", progress_colour, " progress texture for GUI icon ", icon_name + ); + ret = false; + } + } if (progress_texture.is_valid()) { godot_progress_bar->set_progress_texture(progress_texture); } else { UtilityFunctions::push_error( - "Failed to load progress bar base sprite ", progress_texture_file, " for GUI icon ", icon_name + "Failed to create and set progress bar sprite progress texture for GUI icon ", icon_name ); ret = false; } + // TODO - work out why progress bar is missing bottom border pixel (e.g. province building expansion bar) + godot_progress_bar->set_custom_minimum_size(Utilities::to_godot_fvec2(static_cast<fvec2_t>(progress_bar->get_size()))); + args.result = godot_progress_bar; } else if (icon.get_sprite()->is_type<GFX::PieChart>()) { TextureRect* godot_texture_rect = new_control<TextureRect>(icon, args.name); @@ -202,23 +250,43 @@ static bool generate_button(generate_gui_args_t&& args) { 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)); + godot_button->set_mouse_filter(Control::MOUSE_FILTER_PASS); + if (!button.get_text().empty()) { godot_button->set_text(std_view_to_godot_string(button.get_text())); } bool ret = true; + + using enum GFXButtonStateTexture::ButtonState; + static constexpr std::array<GFXButtonStateTexture::ButtonState, 3> button_states { HOVER, PRESSED, DISABLED }; + + std::vector<Ref<GFXButtonStateTexture>> button_state_textures; + for (GFXButtonStateTexture::ButtonState button_state : button_states) { + Ref<GFXButtonStateTexture> button_state_texture = GFXButtonStateTexture::make_gfx_button_state_texture(button_state); + if (button_state_texture.is_valid()) { + button_state_textures.push_back(button_state_texture); + } else { + UtilityFunctions::push_error( + "Failed to make ", GFXButtonStateTexture::button_state_to_theme_name(button_state), + " GFXButtonStateTexture for GUI button ", button_name + ); + ret = false; + } + } + if (button.get_sprite() != nullptr) { Ref<Texture2D> texture; if (button.get_sprite()->is_type<GFX::TextureSprite>()) { GFX::TextureSprite const* texture_sprite = button.get_sprite()->cast_to<GFX::TextureSprite>(); - texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite); + texture = GFXIconTexture::make_gfx_icon_texture(texture_sprite, 0, button_state_textures); if (texture.is_null()) { UtilityFunctions::push_error("Failed to make GFXIconTexture for GUI button ", button_name); ret = false; } } else if (button.get_sprite()->is_type<GFX::MaskedFlag>()) { GFX::MaskedFlag const* masked_flag = button.get_sprite()->cast_to<GFX::MaskedFlag>(); - texture = GFXMaskedFlagTexture::make_gfx_masked_flag_texture(masked_flag); + texture = GFXMaskedFlagTexture::make_gfx_masked_flag_texture(masked_flag, button_state_textures); if (texture.is_null()) { UtilityFunctions::push_error("Failed to make GFXMaskedFlagTexture for GUI button ", button_name); ret = false; @@ -231,15 +299,21 @@ static bool generate_button(generate_gui_args_t&& args) { if (texture.is_valid()) { godot_button->set_custom_minimum_size(texture->get_size()); - Ref<StyleBoxTexture> stylebox; - stylebox.instantiate(); - if (stylebox.is_valid()) { - static const StringName theme_name_normal = "normal"; + + const auto add_stylebox = [godot_button, &button_name](StringName const& theme_name, Ref<Texture2D> const& texture) -> bool { + Ref<StyleBoxTexture> stylebox; + stylebox.instantiate(); + ERR_FAIL_NULL_V(stylebox, false); stylebox->set_texture(texture); - godot_button->add_theme_stylebox_override(theme_name_normal, stylebox); - } else { - UtilityFunctions::push_error("Failed to load instantiate texture stylebox for GUI button ", button_name); - ret = false; + godot_button->add_theme_stylebox_override(theme_name, stylebox); + return true; + }; + + static const StringName theme_name_normal = "normal"; + ret &= add_stylebox(theme_name_normal, texture); + + for (Ref<GFXButtonStateTexture> const& button_state_texture : button_state_textures) { + ret &= add_stylebox(button_state_texture->get_button_state_theme(), button_state_texture); } } } else { @@ -256,8 +330,14 @@ static bool generate_button(generate_gui_args_t&& args) { UtilityFunctions::push_error("Failed to load font for GUI button ", button_name); ret = false; } + + static const std::vector<StringName> button_font_themes { + "font_color", "font_hover_color", "font_hover_pressed_color", "font_pressed_color", "font_disabled_color" + }; const Color colour = Utilities::to_godot_color(button.get_font()->get_colour()); - godot_button->add_theme_color_override("font_color", colour); + for (StringName const& theme_name : button_font_themes) { + godot_button->add_theme_color_override(theme_name, colour); + } } args.result = godot_button; |