diff options
Diffstat (limited to 'extension')
32 files changed, 1481 insertions, 424 deletions
diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp index 417566d..5d955a3 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp @@ -7,6 +7,7 @@ using namespace godot; using namespace OpenVic; +using namespace OpenVic::Utilities::literals; StringName const& GFXPieChartTexture::_slice_identifier_key() { static const StringName slice_identifier_key = "identifier"; @@ -21,7 +22,35 @@ StringName const& GFXPieChartTexture::_slice_weight_key() { return slice_weight_key; } -static constexpr float TWO_PI = 2.0f * std::numbers::pi_v<float>; +GFXPieChartTexture::slice_t const* GFXPieChartTexture::get_slice(Vector2 const& position) const { + if (slices.empty() || position.length_squared() > 1.0_real) { + return nullptr; + } + + static constexpr float TWO_PI = 2.0f * std::numbers::pi_v<float>; + + /* Calculate the anti-clockwise angle between the point and the centre of the image. + * The y coordinate is negated as the image coordinate system's y increases downwards. */ + float theta = atan2(-position.y, position.x); + if (theta < 0.0f) { + theta += TWO_PI; + } + + /* Rescale angle so that total_weight is a full rotation. */ + theta *= total_weight / TWO_PI; + + /* Find the slice theta lies in. */ + for (slice_t const& slice : slices) { + theta -= slice.weight; + + if (theta <= 0.0f) { + return &slice; + } + } + + /* Default to the first slice in case theta never reaches 0 due to floating point inaccuracy. */ + return &slices.front(); +} Error GFXPieChartTexture::_generate_pie_chart_image() { ERR_FAIL_NULL_V(gfx_pie_chart, FAILED); @@ -30,60 +59,37 @@ 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(); + const int32_t pie_chart_radius = gfx_pie_chart->get_size(); + const int32_t pie_chart_diameter = 2 * pie_chart_radius; /* 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; + const bool can_update = pie_chart_image.is_valid() && pie_chart_image->get_width() == pie_chart_diameter + && pie_chart_image->get_height() == pie_chart_diameter; if (!can_update) { - pie_chart_image = Image::create(pie_chart_size, pie_chart_size, false, Image::FORMAT_RGBA8); + pie_chart_image = Image::create(pie_chart_diameter, pie_chart_diameter, false, Image::FORMAT_RGBA8); ERR_FAIL_NULL_V(pie_chart_image, FAILED); } 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) { - - /* Calculate the anti-clockwise angle between the point and the centre of the image. - * The y coordinate is negated as the image coordinate system's y increases downwards. */ - float theta = atan2(-offset.y, offset.x); - if (theta < 0.0f) { - theta += TWO_PI; - } - - /* Rescale angle so that total_weight is a full rotation. */ - theta *= total_weight / TWO_PI; + for (Vector2i point { 0, 0 }; point.y < pie_chart_diameter; ++point.y) { - /* Default to the first colour in case theta never reaches 0 due to floating point inaccuracy. */ - Color colour = slices.front().first; + for (point.x = 0; point.x < pie_chart_diameter; ++point.x) { - /* Find the slice theta lies in. */ - for (slice_t const& slice : slices) { - theta -= slice.second; + Vector2 offset = point; + // Move to the centre of the pixel + offset += Vector2 { 0.5_real, 0.5_real }; + // Normalise to [0, 2] + offset /= pie_chart_radius; + // Translate to [-1, 1] + offset -= Vector2 { 1.0_real, 1.0_real }; - if (theta <= 0.0f) { - colour = slice.first; - break; - } - } + slice_t const* slice = get_slice(offset); - pie_chart_image->set_pixelv(point, colour); - } else { - pie_chart_image->set_pixelv(point, background_colour); - } + pie_chart_image->set_pixelv(point, slice != nullptr ? slice->colour : background_colour); } } } else { @@ -107,12 +113,15 @@ Error GFXPieChartTexture::set_slices_array(godot_pie_chart_data_t const& new_sli for (int32_t i = 0; i < new_slices.size(); ++i) { Dictionary const& slice_dict = new_slices[i]; ERR_CONTINUE_MSG( - !slice_dict.has(_slice_colour_key()) || !slice_dict.has(_slice_weight_key()), + !slice_dict.has(_slice_identifier_key()) || !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[_slice_colour_key()], slice_dict[_slice_weight_key()]); - if (slice.second > 0.0f) { - total_weight += slice.second; + const slice_t slice { + slice_dict[_slice_identifier_key()], slice_dict[_slice_colour_key()], slice_dict[_slice_weight_key()] + }; + if (slice.weight > 0.0f) { + total_weight += slice.weight; slices.push_back(slice); } } diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp index 9642f4e..3610efb 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -10,11 +10,17 @@ namespace OpenVic { class GFXPieChartTexture : public godot::ImageTexture { GDCLASS(GFXPieChartTexture, godot::ImageTexture) - using slice_t = std::pair<godot::Color, float>; + public: + struct slice_t { + godot::String name; + godot::Color colour; + float weight; + }; + private: GFX::PieChart const* PROPERTY(gfx_pie_chart); std::vector<slice_t> slices; - float total_weight; + float PROPERTY(total_weight); godot::Ref<godot::Image> pie_chart_image; static godot::StringName const& _slice_identifier_key(); @@ -29,6 +35,9 @@ namespace OpenVic { public: GFXPieChartTexture(); + // Position must be centred and normalised so that coords are in [-1, 1]. + slice_t const* get_slice(godot::Vector2 const& position) const; + using godot_pie_chart_data_t = godot::TypedArray<godot::Dictionary>; /* Set slices given an Array of Dictionaries, each with the following key-value entries: diff --git a/extension/src/openvic-extension/classes/GUIButton.cpp b/extension/src/openvic-extension/classes/GUIButton.cpp new file mode 100644 index 0000000..e35d67a --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIButton.cpp @@ -0,0 +1,109 @@ +#include "GUIButton.hpp" + +#include <array> + +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +GUI_TOOLTIP_IMPLEMENTATIONS(GUIButton) + +void GUIButton::_bind_methods() { + GUI_TOOLTIP_BIND_METHODS(GUIButton) +} + +void GUIButton::_notification(int what) { + _tooltip_notification(what); +} + +GUIButton::GUIButton() : tooltip_active { false } {} + +Error GUIButton::set_gfx_button_state_having_texture(Ref<GFXButtonStateHavingTexture> const& texture) { + ERR_FAIL_NULL_V(texture, FAILED); + + Error err = OK; + + set_custom_minimum_size(texture->get_size()); + + { + Ref<StyleBoxTexture> stylebox = AssetManager::make_stylebox_texture(texture); + + if (stylebox.is_valid()) { + static const StringName normal_theme = "normal"; + + add_theme_stylebox_override(normal_theme, stylebox); + } else { + UtilityFunctions::push_error("Failed to make StyleBoxTexture for GUIButton ", get_name()); + + err = FAILED; + } + } + + using enum GFXButtonStateTexture::ButtonState; + + for (GFXButtonStateTexture::ButtonState button_state : { HOVER, PRESSED, DISABLED }) { + Ref<GFXButtonStateTexture> button_state_texture = texture->get_button_state_texture(button_state); + + if (button_state_texture.is_valid()) { + Ref<StyleBoxTexture> stylebox = AssetManager::make_stylebox_texture(button_state_texture); + + if (stylebox.is_valid()) { + add_theme_stylebox_override(button_state_texture->get_button_state_name(), stylebox); + } else { + UtilityFunctions::push_error( + "Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state), + " StyleBoxTexture for GUIButton ", get_name() + ); + + err = FAILED; + } + } else { + UtilityFunctions::push_error( + "Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state), + " GFXButtonStateTexture for GUIButton ", get_name() + ); + + err = FAILED; + } + } + + return err; +} + +Error GUIButton::set_gfx_font(GFX::Font const* gfx_font) { + ERR_FAIL_NULL_V(gfx_font, FAILED); + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + + Error err = OK; + + const StringName font_file = Utilities::std_to_godot_string(gfx_font->get_fontname()); + const Ref<Font> font = asset_manager->get_font(font_file); + + if (font.is_valid()) { + static const StringName font_theme = "font"; + + add_theme_font_override(font_theme, font); + } else { + UtilityFunctions::push_error("Failed to load font \"", font_file, "\" for GUIButton ", get_name()); + + err = FAILED; + } + + static const std::array<StringName, 5> 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(gfx_font->get_colour()); + + for (StringName const& theme_name : button_font_themes) { + add_theme_color_override(theme_name, colour); + } + + return err; +} diff --git a/extension/src/openvic-extension/classes/GUIButton.hpp b/extension/src/openvic-extension/classes/GUIButton.hpp new file mode 100644 index 0000000..3873a4d --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIButton.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <godot_cpp/classes/button.hpp> + +#include <openvic-simulation/interface/GFXSprite.hpp> + +#include "openvic-extension/classes/GFXButtonStateTexture.hpp" +#include "openvic-extension/classes/GUIHasTooltip.hpp" + +namespace OpenVic { + class GUIButton : public godot::Button { + GDCLASS(GUIButton, godot::Button) + + GUI_TOOLTIP_DEFINITIONS + + protected: + static void _bind_methods(); + + void _notification(int what); + + godot::Error set_gfx_button_state_having_texture(godot::Ref<GFXButtonStateHavingTexture> const& texture); + + public: + GUIButton(); + + godot::Error set_gfx_font(GFX::Font const* gfx_font); + }; +} diff --git a/extension/src/openvic-extension/classes/GUIHasTooltip.hpp b/extension/src/openvic-extension/classes/GUIHasTooltip.hpp new file mode 100644 index 0000000..22413ec --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIHasTooltip.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include <godot_cpp/classes/control.hpp> +#include <godot_cpp/variant/string.hpp> +#include <godot_cpp/variant/utility_functions.hpp> +#include <godot_cpp/variant/vector2.hpp> + +#include <openvic-simulation/utility/Getters.hpp> + +#include "openvic-extension/singletons/MenuSingleton.hpp" +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +/* To add tooltip functionality to a class: + * - the class must be derived from Control. + * - add GUI_TOOLTIP_DEFINITIONS to the class definition, bearing in mind that it leaves visibility as private. + * - add GUI_TOOLTIP_IMPLEMENTATIONS(CLASS) to the class' source file. + * - add GUI_TOOLTIP_BIND_METHODS(CLASS) to the class' _bind_methods implementation. + * - call _tooltip_notification from the class' _notification method. + * - initialise tooltip_active to false in the class' constructor. */ + +#define GUI_TOOLTIP_DEFINITIONS \ + public: \ + void set_tooltip_string_and_substitution_dict( \ + godot::String const& new_tooltip_string, godot::Dictionary const& new_tooltip_substitution_dict \ + ); \ + void set_tooltip_string(godot::String const& new_tooltip_string); \ + void set_tooltip_substitution_dict(godot::Dictionary const& new_tooltip_substitution_dict); \ + void clear_tooltip(); \ + private: \ + godot::String PROPERTY(tooltip_string); \ + godot::Dictionary PROPERTY(tooltip_substitution_dict); \ + bool PROPERTY_CUSTOM_PREFIX(tooltip_active, is); \ + void _tooltip_notification(int what); \ + void _set_tooltip_active(bool new_tooltip_active); \ + void _set_tooltip_visibility(bool visible); + +#define GUI_TOOLTIP_IMPLEMENTATIONS(CLASS) \ + void CLASS::set_tooltip_string_and_substitution_dict( \ + String const& new_tooltip_string, Dictionary const& new_tooltip_substitution_dict \ + ) { \ + if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \ + UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \ + } \ + if (tooltip_string != new_tooltip_string || tooltip_substitution_dict != new_tooltip_substitution_dict) { \ + tooltip_string = new_tooltip_string; \ + tooltip_substitution_dict = new_tooltip_substitution_dict; \ + if (tooltip_active) { \ + _set_tooltip_visibility(!tooltip_string.is_empty()); \ + } \ + } \ + } \ + void CLASS::set_tooltip_string(String const& new_tooltip_string) { \ + if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \ + UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \ + } \ + if (tooltip_string != new_tooltip_string) { \ + tooltip_string = new_tooltip_string; \ + if (tooltip_active) { \ + _set_tooltip_visibility(!tooltip_string.is_empty()); \ + } \ + } \ + } \ + void CLASS::set_tooltip_substitution_dict(Dictionary const& new_tooltip_substitution_dict) { \ + if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \ + UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \ + } \ + if (tooltip_substitution_dict != new_tooltip_substitution_dict) { \ + tooltip_substitution_dict = new_tooltip_substitution_dict; \ + if (tooltip_active) { \ + _set_tooltip_visibility(!tooltip_string.is_empty()); \ + } \ + } \ + } \ + void CLASS::clear_tooltip() { \ + set_tooltip_string_and_substitution_dict({}, {}); \ + } \ + void CLASS::_tooltip_notification(int what) { \ + if (what == NOTIFICATION_MOUSE_ENTER_SELF) { \ + _set_tooltip_active(true); \ + } else if (what == NOTIFICATION_MOUSE_EXIT_SELF) { \ + _set_tooltip_active(false); \ + } \ + } \ + void CLASS::_set_tooltip_active(bool new_tooltip_active) { \ + if (tooltip_active != new_tooltip_active) { \ + tooltip_active = new_tooltip_active; \ + if (!tooltip_string.is_empty()) { \ + _set_tooltip_visibility(tooltip_active); \ + } \ + } \ + } \ + void CLASS::_set_tooltip_visibility(bool visible) { \ + MenuSingleton* menu_singleton = MenuSingleton::get_singleton(); \ + ERR_FAIL_NULL(menu_singleton); \ + if (visible) { \ + menu_singleton->show_control_tooltip(tooltip_string, tooltip_substitution_dict, this); \ + } else { \ + menu_singleton->hide_tooltip(); \ + } \ + } + +#define GUI_TOOLTIP_BIND_METHODS(CLASS) \ + OV_BIND_METHOD(CLASS::get_tooltip_string); \ + OV_BIND_METHOD(CLASS::set_tooltip_string, { "new_tooltip_string" }); \ + OV_BIND_METHOD(CLASS::get_tooltip_substitution_dict); \ + OV_BIND_METHOD(CLASS::set_tooltip_substitution_dict, { "new_tooltip_substitution_dict" }); \ + OV_BIND_METHOD( \ + CLASS::set_tooltip_string_and_substitution_dict, { "new_tooltip_string", "new_tooltip_substitution_dict" } \ + ); \ + OV_BIND_METHOD(CLASS::clear_tooltip); \ + OV_BIND_METHOD(CLASS::is_tooltip_active); \ + ADD_PROPERTY( \ + PropertyInfo(Variant::STRING, "tooltip_string", PROPERTY_HINT_MULTILINE_TEXT), \ + "set_tooltip_string", "get_tooltip_string" \ + ); \ + ADD_PROPERTY( \ + PropertyInfo(Variant::DICTIONARY, "tooltip_substitution_dict"), \ + "set_tooltip_substitution_dict", "get_tooltip_substitution_dict" \ + ); \ + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tooltip_active"), "", "is_tooltip_active"); diff --git a/extension/src/openvic-extension/classes/GUIIcon.cpp b/extension/src/openvic-extension/classes/GUIIcon.cpp new file mode 100644 index 0000000..68e7ec4 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIIcon.cpp @@ -0,0 +1,82 @@ +#include "GUIIcon.hpp" + +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace godot; +using namespace OpenVic; + +void GUIIcon::_bind_methods() { + OV_BIND_METHOD(GUIIcon::get_gfx_sprite_texture); + + OV_BIND_METHOD(GUIIcon::set_gfx_texture_sprite_name, { "gfx_texture_sprite_name", "icon" }, DEFVAL(GFX::NO_FRAMES)); + OV_BIND_METHOD(GUIIcon::get_gfx_texture_sprite_name); + + OV_BIND_METHOD(GUIIcon::set_icon_index, { "icon_index" }); + OV_BIND_METHOD(GUIIcon::get_icon_index); + + OV_BIND_METHOD(GUIIcon::set_toggled_icon, { "toggled" }); +} + +Error GUIIcon::set_gfx_texture_sprite(GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon) { + const bool needs_setting = gfx_sprite_texture.is_null(); + + if (needs_setting) { + gfx_sprite_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + } + + const Error err = gfx_sprite_texture->set_gfx_texture_sprite(gfx_texture_sprite, icon); + + if (needs_setting) { + set_texture(gfx_sprite_texture); + } + + return err; +} + +Ref<GFXSpriteTexture> GUIIcon::get_gfx_sprite_texture() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, nullptr); + + return gfx_sprite_texture; +} + +Error GUIIcon::set_gfx_texture_sprite_name(String const& gfx_texture_sprite_name, GFX::frame_t icon) { + const bool needs_setting = gfx_sprite_texture.is_null(); + + if (needs_setting) { + gfx_sprite_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + } + + const Error err = gfx_sprite_texture->set_gfx_texture_sprite_name(gfx_texture_sprite_name, icon); + + if (needs_setting) { + set_texture(gfx_sprite_texture); + } + + return err; +} + +String GUIIcon::get_gfx_texture_sprite_name() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, {}); + + return gfx_sprite_texture->get_gfx_texture_sprite_name(); +} + +Error GUIIcon::set_icon_index(GFX::frame_t icon_index) const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->set_icon_index(icon_index); +} + +GFX::frame_t GUIIcon::get_icon_index() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->get_icon_index(); +} + +Error GUIIcon::set_toggled_icon(bool toggled) const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->set_toggled_icon(toggled); +} diff --git a/extension/src/openvic-extension/classes/GUIIcon.hpp b/extension/src/openvic-extension/classes/GUIIcon.hpp new file mode 100644 index 0000000..e8e3014 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIIcon.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUITextureRect.hpp" + +namespace OpenVic { + class GUIIcon : public GUITextureRect { + GDCLASS(GUIIcon, GUITextureRect) + + godot::Ref<GFXSpriteTexture> gfx_sprite_texture; + + protected: + static void _bind_methods(); + + public: + godot::Error set_gfx_texture_sprite( + GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES + ); + + godot::Ref<GFXSpriteTexture> get_gfx_sprite_texture() const; + + godot::Error set_gfx_texture_sprite_name( + godot::String const& gfx_texture_sprite_name, GFX::frame_t icon = GFX::NO_FRAMES + ); + + godot::String get_gfx_texture_sprite_name() const; + + godot::Error set_icon_index(GFX::frame_t icon_index) const; + + GFX::frame_t get_icon_index() const; + + godot::Error set_toggled_icon(bool toggled) const; + }; +} diff --git a/extension/src/openvic-extension/classes/GUIIconButton.cpp b/extension/src/openvic-extension/classes/GUIIconButton.cpp new file mode 100644 index 0000000..1bba947 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIIconButton.cpp @@ -0,0 +1,86 @@ +#include "GUIIconButton.hpp" + +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace godot; +using namespace OpenVic; + +void GUIIconButton::_bind_methods() { + OV_BIND_METHOD(GUIIconButton::get_gfx_sprite_texture); + + OV_BIND_METHOD(GUIIconButton::set_gfx_texture_sprite_name, { "gfx_texture_sprite_name", "icon" }, DEFVAL(GFX::NO_FRAMES)); + OV_BIND_METHOD(GUIIconButton::get_gfx_texture_sprite_name); + + OV_BIND_METHOD(GUIIconButton::set_icon_index, { "icon_index" }); + OV_BIND_METHOD(GUIIconButton::get_icon_index); + + OV_BIND_METHOD(GUIIconButton::set_toggled_icon, { "toggled" }); +} + +Error GUIIconButton::set_gfx_texture_sprite(GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon) { + const bool needs_setting = gfx_sprite_texture.is_null(); + + if (needs_setting) { + gfx_sprite_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + } + + Error err = gfx_sprite_texture->set_gfx_texture_sprite(gfx_texture_sprite, icon); + + if (needs_setting && set_gfx_button_state_having_texture(gfx_sprite_texture) != OK) { + err = FAILED; + } + + return err; +} + +Ref<GFXSpriteTexture> GUIIconButton::get_gfx_sprite_texture() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, nullptr); + + return gfx_sprite_texture; +} + +Error GUIIconButton::set_gfx_texture_sprite_name(String const& gfx_texture_sprite_name, GFX::frame_t icon) { + const bool needs_setting = gfx_sprite_texture.is_null(); + + if (needs_setting) { + gfx_sprite_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + } + + Error err = gfx_sprite_texture->set_gfx_texture_sprite_name(gfx_texture_sprite_name, icon); + + if (needs_setting && set_gfx_button_state_having_texture(gfx_sprite_texture) != OK) { + err = FAILED; + } + + return err; +} + +String GUIIconButton::get_gfx_texture_sprite_name() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, {}); + + return gfx_sprite_texture->get_gfx_texture_sprite_name(); +} + +Error GUIIconButton::set_icon_index(GFX::frame_t icon_index) const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->set_icon_index(icon_index); +} + +GFX::frame_t GUIIconButton::get_icon_index() const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->get_icon_index(); +} + +Error GUIIconButton::set_toggled_icon(bool toggled) const { + ERR_FAIL_NULL_V(gfx_sprite_texture, FAILED); + + return gfx_sprite_texture->set_toggled_icon(toggled); +} + +void GUIIconButton::_toggled(bool toggled_on) { + set_toggled_icon(toggled_on); +} diff --git a/extension/src/openvic-extension/classes/GUIIconButton.hpp b/extension/src/openvic-extension/classes/GUIIconButton.hpp new file mode 100644 index 0000000..10fc179 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIIconButton.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUIButton.hpp" + +namespace OpenVic { + class GUIIconButton : public GUIButton { + GDCLASS(GUIIconButton, GUIButton) + + godot::Ref<GFXSpriteTexture> gfx_sprite_texture; + + protected: + static void _bind_methods(); + + public: + godot::Error set_gfx_texture_sprite( + GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon = GFX::NO_FRAMES + ); + + godot::Ref<GFXSpriteTexture> get_gfx_sprite_texture() const; + + godot::Error set_gfx_texture_sprite_name( + godot::String const& gfx_texture_sprite_name, GFX::frame_t icon = GFX::NO_FRAMES + ); + + godot::String get_gfx_texture_sprite_name() const; + + godot::Error set_icon_index(GFX::frame_t icon_index) const; + + GFX::frame_t get_icon_index() const; + + godot::Error set_toggled_icon(bool toggled) const; + + void _toggled(bool toggled_on) override; + }; +} diff --git a/extension/src/openvic-extension/classes/GUILabel.cpp b/extension/src/openvic-extension/classes/GUILabel.cpp index 9fd6b60..732dec2 100644 --- a/extension/src/openvic-extension/classes/GUILabel.cpp +++ b/extension/src/openvic-extension/classes/GUILabel.cpp @@ -14,7 +14,30 @@ using namespace OpenVic::Utilities::literals; static constexpr int32_t DEFAULT_FONT_SIZE = 16; +GUI_TOOLTIP_IMPLEMENTATIONS(GUILabel) + +String const& GUILabel::get_colour_marker() { + static const String COLOUR_MARKER = String::chr(0xA7); // § + return COLOUR_MARKER; +} + +String const& GUILabel::get_currency_marker() { + static const String CURRENCY_MARKER = String::chr(0xA4); // ¤ + return CURRENCY_MARKER; +} + +String const& GUILabel::get_substitution_marker() { + static const String SUBSTITUTION_MARKER = String::chr(0x24); // $ + return SUBSTITUTION_MARKER; +} + void GUILabel::_bind_methods() { + GUI_TOOLTIP_BIND_METHODS(GUILabel) + + OV_BIND_SMETHOD(get_colour_marker); + OV_BIND_SMETHOD(get_currency_marker); + OV_BIND_SMETHOD(get_substitution_marker); + OV_BIND_METHOD(GUILabel::clear); OV_BIND_METHOD(GUILabel::get_gui_text_name); @@ -80,6 +103,8 @@ void GUILabel::_bind_methods() { } void GUILabel::_notification(int what) { + _tooltip_notification(what); + switch (what) { case NOTIFICATION_RESIZED: case NOTIFICATION_TRANSLATION_CHANGED: { @@ -146,7 +171,8 @@ void GUILabel::_notification(int what) { } GUILabel::GUILabel() - : gui_text { nullptr }, + : tooltip_active { false }, + gui_text { nullptr }, text {}, substitution_dict {}, horizontal_alignment { HORIZONTAL_ALIGNMENT_LEFT }, @@ -454,29 +480,30 @@ void GUILabel::_update_lines() { } String GUILabel::generate_substituted_text(String const& base_text) const { - static const String SUBSTITUTION_MARKER = String::chr(0x24); // $ - String result; int64_t start_pos = 0; int64_t marker_start_pos; - while ((marker_start_pos = base_text.find(SUBSTITUTION_MARKER, start_pos)) != -1) { + while ((marker_start_pos = base_text.find(get_substitution_marker(), start_pos)) != -1) { result += base_text.substr(start_pos, marker_start_pos - start_pos); - int64_t marker_end_pos = base_text.find(SUBSTITUTION_MARKER, marker_start_pos + SUBSTITUTION_MARKER.length()); + int64_t marker_end_pos = base_text.find( + get_substitution_marker(), marker_start_pos + get_substitution_marker().length() + ); if (marker_end_pos == -1) { marker_end_pos = base_text.length(); } String key = base_text.substr( - marker_start_pos + SUBSTITUTION_MARKER.length(), marker_end_pos - marker_start_pos - SUBSTITUTION_MARKER.length() + marker_start_pos + get_substitution_marker().length(), + marker_end_pos - marker_start_pos - get_substitution_marker().length() ); String value = substitution_dict.get(key, String {}); // Use the un-substituted key if no value is found or the value is empty result += value.is_empty() ? key : is_auto_translating() ? tr(value) : value; - start_pos = marker_end_pos + SUBSTITUTION_MARKER.length(); + start_pos = marker_end_pos + get_substitution_marker().length(); } if (start_pos < base_text.length()) { @@ -489,25 +516,23 @@ String GUILabel::generate_substituted_text(String const& base_text) const { std::pair<String, GUILabel::colour_instructions_t> GUILabel::generate_display_text_and_colour_instructions( String const& substituted_text ) const { - static const String COLOUR_MARKER = String::chr(0xA7); // § - String result; colour_instructions_t colour_instructions; int64_t start_pos = 0; int64_t marker_pos; - while ((marker_pos = substituted_text.find(COLOUR_MARKER, start_pos)) != -1) { + while ((marker_pos = substituted_text.find(get_colour_marker(), start_pos)) != -1) { result += substituted_text.substr(start_pos, marker_pos - start_pos); - if (marker_pos + COLOUR_MARKER.length() < substituted_text.length()) { - const char32_t colour_code = substituted_text[marker_pos + COLOUR_MARKER.length()]; + if (marker_pos + get_colour_marker().length() < substituted_text.length()) { + const char32_t colour_code = substituted_text[marker_pos + get_colour_marker().length()]; // Check that the colour code can be safely cast to a char if (colour_code >> sizeof(char) * CHAR_BIT == 0) { colour_instructions.emplace_back(result.length(), static_cast<char>(colour_code)); } - start_pos = marker_pos + COLOUR_MARKER.length() + 1; + start_pos = marker_pos + get_colour_marker().length() + 1; } else { return { std::move(result), std::move(colour_instructions) }; } @@ -588,8 +613,6 @@ void GUILabel::separate_lines( void GUILabel::separate_currency_segments( String const& string, Color const& colour, line_t& line ) const { - static const String CURRENCY_MARKER = String::chr(0xA4); // ¤ - const auto push_string_segment = [this, &string, &colour, &line](int64_t start, int64_t end) -> void { String substring = string.substr(start, end - start); const real_t width = get_string_width(substring); @@ -602,7 +625,7 @@ void GUILabel::separate_currency_segments( const real_t currency_width = currency_texture.is_valid() ? currency_texture->get_width() : 0.0_real; - while ((marker_pos = string.find(CURRENCY_MARKER, start_pos)) != -1) { + while ((marker_pos = string.find(get_currency_marker(), start_pos)) != -1) { if (start_pos < marker_pos) { push_string_segment(start_pos, marker_pos); } @@ -610,7 +633,7 @@ void GUILabel::separate_currency_segments( line.segments.push_back(currency_segment_t {}); line.width += currency_width; - start_pos = marker_pos + CURRENCY_MARKER.length(); + start_pos = marker_pos + get_currency_marker().length(); } if (start_pos < string.length()) { diff --git a/extension/src/openvic-extension/classes/GUILabel.hpp b/extension/src/openvic-extension/classes/GUILabel.hpp index e0982b2..102ad94 100644 --- a/extension/src/openvic-extension/classes/GUILabel.hpp +++ b/extension/src/openvic-extension/classes/GUILabel.hpp @@ -8,11 +8,14 @@ #include <openvic-simulation/interface/GUI.hpp> #include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUIHasTooltip.hpp" namespace OpenVic { class GUILabel : public godot::Control { GDCLASS(GUILabel, godot::Control) + GUI_TOOLTIP_DEFINITIONS + using colour_instructions_t = std::vector<std::pair<int64_t, char>>; GUI::Text const* PROPERTY(gui_text); @@ -55,6 +58,10 @@ namespace OpenVic { void _notification(int what); public: + static godot::String const& get_colour_marker(); + static godot::String const& get_currency_marker(); + static godot::String const& get_substitution_marker(); + GUILabel(); /* Reset gui_text to nullptr and reset current text. */ diff --git a/extension/src/openvic-extension/classes/GUIListBox.cpp b/extension/src/openvic-extension/classes/GUIListBox.cpp index 9165b14..958807f 100644 --- a/extension/src/openvic-extension/classes/GUIListBox.cpp +++ b/extension/src/openvic-extension/classes/GUIListBox.cpp @@ -146,7 +146,7 @@ Vector2 GUIListBox::_get_minimum_size() const { } } -void GUIListBox::_gui_input(godot::Ref<godot::InputEvent> const& event) { +void GUIListBox::_gui_input(Ref<InputEvent> const& event) { ERR_FAIL_NULL(event); if (scrollbar == nullptr) { diff --git a/extension/src/openvic-extension/classes/GUIMaskedFlag.cpp b/extension/src/openvic-extension/classes/GUIMaskedFlag.cpp new file mode 100644 index 0000000..968cebb --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIMaskedFlag.cpp @@ -0,0 +1,88 @@ +#include "GUIMaskedFlag.hpp" + +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace godot; +using namespace OpenVic; + +void GUIMaskedFlag::_bind_methods() { + OV_BIND_METHOD(GUIMaskedFlag::get_gfx_masked_flag_texture); + + OV_BIND_METHOD(GUIMaskedFlag::set_gfx_masked_flag_name, { "gfx_masked_flag_name" }); + OV_BIND_METHOD(GUIMaskedFlag::get_gfx_masked_flag_name); + + OV_BIND_METHOD(GUIMaskedFlag::set_flag_country_name_and_type, { "flag_country_name", "flag_type" }); + OV_BIND_METHOD(GUIMaskedFlag::set_flag_country_name, { "flag_country_name" }); + OV_BIND_METHOD(GUIMaskedFlag::get_flag_country_name); + OV_BIND_METHOD(GUIMaskedFlag::get_flag_type); +} + +Error GUIMaskedFlag::set_gfx_masked_flag(GFX::MaskedFlag const* gfx_masked_flag) { + const bool needs_setting = gfx_masked_flag_texture.is_null(); + + if (needs_setting) { + gfx_masked_flag_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + } + + const Error err = gfx_masked_flag_texture->set_gfx_masked_flag(gfx_masked_flag); + + if (needs_setting) { + set_texture(gfx_masked_flag_texture); + } + + return err; +} + +Ref<GFXMaskedFlagTexture> GUIMaskedFlag::get_gfx_masked_flag_texture() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, nullptr); + + return gfx_masked_flag_texture; +} + +Error GUIMaskedFlag::set_gfx_masked_flag_name(String const& gfx_masked_flag_name) { + const bool needs_setting = gfx_masked_flag_texture.is_null(); + + if (needs_setting) { + gfx_masked_flag_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + } + + const Error err = gfx_masked_flag_texture->set_gfx_masked_flag_name(gfx_masked_flag_name); + + if (needs_setting) { + set_texture(gfx_masked_flag_texture); + } + + return err; +} + +String GUIMaskedFlag::get_gfx_masked_flag_name() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_gfx_masked_flag_name(); +} + +Error GUIMaskedFlag::set_flag_country_name_and_type(String const& flag_country_name, StringName const& flag_type) const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + + return gfx_masked_flag_texture->set_flag_country_name_and_type(flag_country_name, flag_type); +} + +Error GUIMaskedFlag::set_flag_country_name(String const& flag_country_name) const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + + return gfx_masked_flag_texture->set_flag_country_name(flag_country_name); +} + +String GUIMaskedFlag::get_flag_country_name() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_flag_country_name(); +} + +String GUIMaskedFlag::get_flag_type() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_flag_type(); +} diff --git a/extension/src/openvic-extension/classes/GUIMaskedFlag.hpp b/extension/src/openvic-extension/classes/GUIMaskedFlag.hpp new file mode 100644 index 0000000..4bdc6c1 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIMaskedFlag.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" +#include "openvic-extension/classes/GUITextureRect.hpp" + +namespace OpenVic { + class GUIMaskedFlag : public GUITextureRect { + GDCLASS(GUIMaskedFlag, GUITextureRect) + + godot::Ref<GFXMaskedFlagTexture> gfx_masked_flag_texture; + + protected: + static void _bind_methods(); + + public: + godot::Error set_gfx_masked_flag(GFX::MaskedFlag const* gfx_masked_flag); + + godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture() const; + + godot::Error set_gfx_masked_flag_name(godot::String const& gfx_masked_flag_name); + + godot::String get_gfx_masked_flag_name() const; + + godot::Error set_flag_country_name_and_type( + godot::String const& flag_country_name, godot::StringName const& flag_type + ) const; + + godot::Error set_flag_country_name(godot::String const& flag_country_name) const; + + godot::String get_flag_country_name() const; + + godot::String get_flag_type() const; + }; +} diff --git a/extension/src/openvic-extension/classes/GUIMaskedFlagButton.cpp b/extension/src/openvic-extension/classes/GUIMaskedFlagButton.cpp new file mode 100644 index 0000000..8c3c1f8 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIMaskedFlagButton.cpp @@ -0,0 +1,88 @@ +#include "GUIMaskedFlagButton.hpp" + +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace godot; +using namespace OpenVic; + +void GUIMaskedFlagButton::_bind_methods() { + OV_BIND_METHOD(GUIMaskedFlagButton::get_gfx_masked_flag_texture); + + OV_BIND_METHOD(GUIMaskedFlagButton::set_gfx_masked_flag_name, { "gfx_masked_flag_name" }); + OV_BIND_METHOD(GUIMaskedFlagButton::get_gfx_masked_flag_name); + + OV_BIND_METHOD(GUIMaskedFlagButton::set_flag_country_name_and_type, { "flag_country_name", "flag_type" }); + OV_BIND_METHOD(GUIMaskedFlagButton::set_flag_country_name, { "flag_country_name" }); + OV_BIND_METHOD(GUIMaskedFlagButton::get_flag_country_name); + OV_BIND_METHOD(GUIMaskedFlagButton::get_flag_type); +} + +Error GUIMaskedFlagButton::set_gfx_masked_flag(GFX::MaskedFlag const* gfx_masked_flag) { + const bool needs_setting = gfx_masked_flag_texture.is_null(); + + if (needs_setting) { + gfx_masked_flag_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + } + + Error err = gfx_masked_flag_texture->set_gfx_masked_flag(gfx_masked_flag); + + if (needs_setting && set_gfx_button_state_having_texture(gfx_masked_flag_texture) != OK) { + err = FAILED; + } + + return err; +} + +Ref<GFXMaskedFlagTexture> GUIMaskedFlagButton::get_gfx_masked_flag_texture() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, nullptr); + + return gfx_masked_flag_texture; +} + +Error GUIMaskedFlagButton::set_gfx_masked_flag_name(String const& gfx_masked_flag_name) { + const bool needs_setting = gfx_masked_flag_texture.is_null(); + + if (needs_setting) { + gfx_masked_flag_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + } + + Error err = gfx_masked_flag_texture->set_gfx_masked_flag_name(gfx_masked_flag_name); + + if (needs_setting && set_gfx_button_state_having_texture(gfx_masked_flag_texture) != OK) { + err = FAILED; + } + + return err; +} + +String GUIMaskedFlagButton::get_gfx_masked_flag_name() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_gfx_masked_flag_name(); +} + +Error GUIMaskedFlagButton::set_flag_country_name_and_type(String const& flag_country_name, StringName const& flag_type) const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + + return gfx_masked_flag_texture->set_flag_country_name_and_type(flag_country_name, flag_type); +} + +Error GUIMaskedFlagButton::set_flag_country_name(String const& flag_country_name) const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, FAILED); + + return gfx_masked_flag_texture->set_flag_country_name(flag_country_name); +} + +String GUIMaskedFlagButton::get_flag_country_name() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_flag_country_name(); +} + +String GUIMaskedFlagButton::get_flag_type() const { + ERR_FAIL_NULL_V(gfx_masked_flag_texture, {}); + + return gfx_masked_flag_texture->get_flag_type(); +} diff --git a/extension/src/openvic-extension/classes/GUIMaskedFlagButton.hpp b/extension/src/openvic-extension/classes/GUIMaskedFlagButton.hpp new file mode 100644 index 0000000..131c93d --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIMaskedFlagButton.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" +#include "openvic-extension/classes/GUIButton.hpp" + +namespace OpenVic { + class GUIMaskedFlagButton : public GUIButton { + GDCLASS(GUIMaskedFlagButton, GUIButton) + + godot::Ref<GFXMaskedFlagTexture> gfx_masked_flag_texture; + + protected: + static void _bind_methods(); + + public: + godot::Error set_gfx_masked_flag(GFX::MaskedFlag const* gfx_masked_flag); + + godot::Ref<GFXMaskedFlagTexture> get_gfx_masked_flag_texture() const; + + godot::Error set_gfx_masked_flag_name(godot::String const& gfx_masked_flag_name); + + godot::String get_gfx_masked_flag_name() const; + + godot::Error set_flag_country_name_and_type( + godot::String const& flag_country_name, godot::StringName const& flag_type + ) const; + + godot::Error set_flag_country_name(godot::String const& flag_country_name) const; + + godot::String get_flag_country_name() const; + + godot::String get_flag_type() const; + }; +} diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index 25ef821..59bad92 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -3,7 +3,6 @@ #include <limits> #include <godot_cpp/classes/bit_map.hpp> -#include <godot_cpp/classes/button.hpp> #include <godot_cpp/classes/canvas_item.hpp> #include <godot_cpp/classes/check_box.hpp> #include <godot_cpp/classes/control.hpp> @@ -17,7 +16,6 @@ #include <godot_cpp/classes/style_box_texture.hpp> #include <godot_cpp/classes/texture2d.hpp> #include <godot_cpp/classes/texture_progress_bar.hpp> -#include <godot_cpp/classes/texture_rect.hpp> #include <godot_cpp/core/defs.hpp> #include <godot_cpp/core/error_macros.hpp> #include <godot_cpp/core/object.hpp> @@ -39,21 +37,19 @@ using namespace godot; using namespace OpenVic; #define APPLY_TO_CHILD_TYPES(F) \ - F(Button, button) \ + F(GUIIconButton, gui_icon_button) \ + F(GUIMaskedFlagButton, gui_masked_flag_button) \ F(GUILabel, gui_label) \ F(Panel, panel) \ - F(TextureProgressBar, progress_bar) \ - F(TextureRect, texture_rect) \ + F(GUIProgressBar, gui_progress_bar) \ + F(GUIIcon, gui_icon) \ + F(GUIMaskedFlag, gui_masked_flag) \ + F(GUIPieChart, gui_pie_chart) \ F(GUIOverlappingElementsBox, gui_overlapping_elements_box) \ F(GUIScrollbar, gui_scrollbar) \ F(GUIListBox, gui_listbox) \ F(LineEdit, line_edit) -#define APPLY_TO_TEXTURE_TYPES(F) \ - F(GFXSpriteTexture, gfx_sprite_texture) \ - F(GFXMaskedFlagTexture, gfx_masked_flag_texture) \ - F(GFXPieChartTexture, gfx_pie_chart_texture) - void GUINode::_bind_methods() { OV_BIND_SMETHOD(generate_gui_element, { "gui_scene", "gui_element", "name" }, DEFVAL(String {})); OV_BIND_METHOD(GUINode::add_gui_element, { "gui_scene", "gui_element", "name" }, DEFVAL(String {})); @@ -74,13 +70,11 @@ void GUINode::_bind_methods() { APPLY_TO_CHILD_TYPES(GET_BINDINGS) +#undef 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) - -#undef GET_BINDINGS - OV_BIND_METHOD(GUINode::hide_node, { "path" }); OV_BIND_METHOD(GUINode::hide_nodes, { "paths" }); @@ -151,33 +145,37 @@ APPLY_TO_CHILD_TYPES(CHILD_GET_FUNCTIONS) #undef CHILD_GET_FUNCTIONS +#undef APPLY_TO_CHILD_TYPES + 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", node->get_name())); return texture; - } else if (Button const* button = Object::cast_to<Button>(node); button != nullptr) { + } else if (GUIButton const* button = Object::cast_to<GUIButton>(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, node->get_name()) + stylebox, nullptr, vformat("Failed to get StyleBox %s from GUIButton %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, node->get_name() + "Failed to cast StyleBox %s from GUIButton %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, node->get_name()) + vformat("Failed to get Texture2D from StyleBoxTexture %s from GUIButton %s", theme_name_normal, node->get_name()) ); return result; } ERR_FAIL_V_MSG( - nullptr, vformat("Failed to cast node %s from type %s to TextureRect or Button", node->get_name(), node->get_class()) + nullptr, vformat( + "Failed to cast node %s from type %s to TextureRect or GUIButton", node->get_name(), node->get_class() + ) ); } @@ -185,30 +183,6 @@ Ref<Texture2D> GUINode::get_texture_from_nodepath(NodePath const& path) const { return get_texture_from_node(get_node_internal(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 type %s to %s", texture->get_class(), T::get_class_static()) - ); - return result; -} - -#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)); \ - } - -APPLY_TO_TEXTURE_TYPES(TEXTURE_GET_FUNCTIONS) - -#undef TEXTURE_GET_FUNCTIONS - -#undef APPLY_TO_CHILD_TYPES - Error GUINode::hide_node(NodePath const& path) const { CanvasItem* node = _cast_node<CanvasItem>(get_node_internal(path)); ERR_FAIL_NULL_V(node, FAILED); diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index 73ca92b..453263a 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -1,7 +1,6 @@ #pragma once #include <godot_cpp/classes/bit_map.hpp> -#include <godot_cpp/classes/button.hpp> #include <godot_cpp/classes/control.hpp> #include <godot_cpp/classes/image.hpp> #include <godot_cpp/classes/input_event.hpp> @@ -10,20 +9,21 @@ #include <godot_cpp/classes/panel.hpp> #include <godot_cpp/classes/ref.hpp> #include <godot_cpp/classes/texture2d.hpp> -#include <godot_cpp/classes/texture_progress_bar.hpp> -#include <godot_cpp/classes/texture_rect.hpp> #include <godot_cpp/templates/vector.hpp> #include <godot_cpp/variant/node_path.hpp> #include <godot_cpp/variant/rect2.hpp> #include <godot_cpp/variant/string.hpp> #include <godot_cpp/variant/vector2.hpp> -#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" -#include "openvic-extension/classes/GFXPieChartTexture.hpp" -#include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUIIcon.hpp" +#include "openvic-extension/classes/GUIIconButton.hpp" #include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" +#include "openvic-extension/classes/GUIMaskedFlag.hpp" +#include "openvic-extension/classes/GUIMaskedFlagButton.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIPieChart.hpp" +#include "openvic-extension/classes/GUIProgressBar.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" namespace OpenVic { @@ -51,36 +51,35 @@ namespace OpenVic { static godot::Vector2 get_gui_position(godot::String const& gui_scene, godot::String const& gui_position); - static godot::Button* get_button_from_node(godot::Node* node); + static GUIIconButton* get_gui_icon_button_from_node(godot::Node* node); + static GUIMaskedFlagButton* get_gui_masked_flag_button_from_node(godot::Node* node); static GUILabel* get_gui_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 GUIProgressBar* get_gui_progress_bar_from_node(godot::Node* node); + static GUIIcon* get_gui_icon_from_node(godot::Node* node); + static GUIMaskedFlag* get_gui_masked_flag_from_node(godot::Node* node); + static GUIPieChart* get_gui_pie_chart_from_node(godot::Node* node); static GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_node(godot::Node* node); static GUIScrollbar* get_gui_scrollbar_from_node(godot::Node* node); static GUIListBox* get_gui_listbox_from_node(godot::Node* node); static godot::LineEdit* get_line_edit_from_node(godot::Node* node); - godot::Button* get_button_from_nodepath(godot::NodePath const& path) const; + GUIIconButton* get_gui_icon_button_from_nodepath(godot::NodePath const& path) const; + GUIMaskedFlagButton* get_gui_masked_flag_button_from_nodepath(godot::NodePath const& path) const; GUILabel* get_gui_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; + GUIProgressBar* get_gui_progress_bar_from_nodepath(godot::NodePath const& path) const; + GUIIcon* get_gui_icon_from_nodepath(godot::NodePath const& path) const; + GUIMaskedFlag* get_gui_masked_flag_from_nodepath(godot::NodePath const& path) const; + GUIPieChart* get_gui_pie_chart_from_nodepath(godot::NodePath const& path) const; GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_nodepath(godot::NodePath const& path) const; GUIScrollbar* get_gui_scrollbar_from_nodepath(godot::NodePath const& path) const; GUIListBox* get_gui_listbox_from_nodepath(godot::NodePath const& path) const; godot::LineEdit* get_line_edit_from_nodepath(godot::NodePath const& path) const; - /* Helper functions to get textures from TextureRects and Buttons. */ + /* Helper functions to get textures from TextureRects and GUIButtons. */ static godot::Ref<godot::Texture2D> get_texture_from_node(godot::Node* node); - static godot::Ref<GFXSpriteTexture> get_gfx_sprite_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<GFXSpriteTexture> get_gfx_sprite_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::TypedArray<godot::NodePath> const& paths) const; diff --git a/extension/src/openvic-extension/classes/GUIPieChart.cpp b/extension/src/openvic-extension/classes/GUIPieChart.cpp new file mode 100644 index 0000000..0688dd3 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIPieChart.cpp @@ -0,0 +1,142 @@ +#include "GUIPieChart.hpp" + +#include <godot_cpp/classes/input_event_mouse_motion.hpp> + +#include "openvic-extension/classes/GUILabel.hpp" +#include "openvic-extension/singletons/MenuSingleton.hpp" +#include "openvic-extension/utility/ClassBindings.hpp" + +using namespace godot; +using namespace OpenVic; +using namespace OpenVic::Utilities::literals; + +void GUIPieChart::_update_tooltip() { + MenuSingleton* menu_singleton = MenuSingleton::get_singleton(); + ERR_FAIL_NULL(menu_singleton); + + if (gfx_pie_chart_texture.is_valid()) { + GFXPieChartTexture::slice_t const* slice = gfx_pie_chart_texture->get_slice(tooltip_position); + + if (slice != nullptr) { + static const String tooltip_identifier_key = "ID"; + static const String tooltip_percent_key = "PC"; + // "§Y$ID$§!: $PC$%" + static const String tooltip_string = + GUILabel::get_colour_marker() + String { "Y" } + GUILabel::get_substitution_marker() + tooltip_identifier_key + + GUILabel::get_substitution_marker() + GUILabel::get_colour_marker() + "!: " + + GUILabel::get_substitution_marker() + tooltip_percent_key + GUILabel::get_substitution_marker() + "%"; + + Dictionary substitution_dict; + substitution_dict[tooltip_identifier_key] = slice->name; + + float percent = slice->weight * 100.0f; + if (gfx_pie_chart_texture->get_total_weight() > 0.0f) { + percent /= gfx_pie_chart_texture->get_total_weight(); + } + substitution_dict[tooltip_percent_key] = Utilities::float_to_string_dp(percent, 2); + + menu_singleton->show_control_tooltip(tooltip_string, substitution_dict, this); + + tooltip_active = true; + return; + } + } + + menu_singleton->hide_tooltip(); + tooltip_active = false; +} + +void GUIPieChart::_bind_methods() { + OV_BIND_METHOD(GUIPieChart::get_gfx_pie_chart_texture); + OV_BIND_METHOD(GUIPieChart::set_gfx_pie_chart_name, { "gfx_pie_chart_name" }); + OV_BIND_METHOD(GUIPieChart::get_gfx_pie_chart_name); + OV_BIND_METHOD(GUIPieChart::set_slices_array, { "new_slices" }); +} + +static const Vector2 disabled_tooltip_position { -1.0_real, -1.0_real }; + +void GUIPieChart::_notification(int what) { + if (what == NOTIFICATION_MOUSE_EXIT_SELF) { + tooltip_position = disabled_tooltip_position; + + _update_tooltip(); + } +} + +void GUIPieChart::_gui_input(Ref<InputEvent> const& event) { + Ref<InputEventMouseMotion> mm = event; + + if (mm.is_valid()) { + tooltip_position = mm->get_position() * 2.0_real / get_size() - Vector2 { 1.0_real, 1.0_real }; + + _update_tooltip(); + } +} + +GUIPieChart::GUIPieChart() : tooltip_active { false }, tooltip_position { disabled_tooltip_position } {} + +Error GUIPieChart::set_gfx_pie_chart(GFX::PieChart const* gfx_pie_chart) { + const bool needs_setting = gfx_pie_chart_texture.is_null(); + + if (needs_setting) { + gfx_pie_chart_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_pie_chart_texture, FAILED); + } + + const Error err = gfx_pie_chart_texture->set_gfx_pie_chart(gfx_pie_chart); + + if (needs_setting) { + set_texture(gfx_pie_chart_texture); + } + + if (tooltip_active) { + _update_tooltip(); + } + + return err; +} + +Ref<GFXPieChartTexture> GUIPieChart::get_gfx_pie_chart_texture() const { + ERR_FAIL_NULL_V(gfx_pie_chart_texture, nullptr); + + return gfx_pie_chart_texture; +} + +Error GUIPieChart::set_gfx_pie_chart_name(String const& gfx_pie_chart_name) { + const bool needs_setting = gfx_pie_chart_texture.is_null(); + + if (needs_setting) { + gfx_pie_chart_texture.instantiate(); + ERR_FAIL_NULL_V(gfx_pie_chart_texture, FAILED); + } + + const Error err = gfx_pie_chart_texture->set_gfx_pie_chart_name(gfx_pie_chart_name); + + if (needs_setting) { + set_texture(gfx_pie_chart_texture); + } + + if (tooltip_active) { + _update_tooltip(); + } + + return err; +} + +String GUIPieChart::get_gfx_pie_chart_name() const { + ERR_FAIL_NULL_V(gfx_pie_chart_texture, {}); + + return gfx_pie_chart_texture->get_gfx_pie_chart_name(); +} + +Error GUIPieChart::set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices) { + ERR_FAIL_NULL_V(gfx_pie_chart_texture, FAILED); + + const Error err = gfx_pie_chart_texture->set_slices_array(new_slices); + + if (tooltip_active) { + _update_tooltip(); + } + + return err; +} diff --git a/extension/src/openvic-extension/classes/GUIPieChart.hpp b/extension/src/openvic-extension/classes/GUIPieChart.hpp new file mode 100644 index 0000000..3356dba --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIPieChart.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include <godot_cpp/classes/texture_rect.hpp> + +#include <openvic-simulation/interface/GFXSprite.hpp> + +#include "openvic-extension/classes/GFXPieChartTexture.hpp" + +namespace OpenVic { + class GUIPieChart : public godot::TextureRect { + GDCLASS(GUIPieChart, godot::TextureRect) + + godot::Ref<GFXPieChartTexture> gfx_pie_chart_texture; + + bool tooltip_active; + godot::Vector2 tooltip_position; + + void _update_tooltip(); + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + void _gui_input(godot::Ref<godot::InputEvent> const& event) override; + + GUIPieChart(); + + godot::Error set_gfx_pie_chart(GFX::PieChart const* gfx_pie_chart); + + godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture() const; + + godot::Error set_gfx_pie_chart_name(godot::String const& gfx_pie_chart_name); + + godot::String get_gfx_pie_chart_name() const; + + godot::Error set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices); + }; +} diff --git a/extension/src/openvic-extension/classes/GUIProgressBar.cpp b/extension/src/openvic-extension/classes/GUIProgressBar.cpp new file mode 100644 index 0000000..d13f455 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIProgressBar.cpp @@ -0,0 +1,114 @@ +#include "GUIProgressBar.hpp" + +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +GUI_TOOLTIP_IMPLEMENTATIONS(GUIProgressBar) + +void GUIProgressBar::_bind_methods() { + GUI_TOOLTIP_BIND_METHODS(GUIProgressBar) +} + +void GUIProgressBar::_notification(int what) { + _tooltip_notification(what); +} + +GUIProgressBar::GUIProgressBar() : tooltip_active { false } {} + +Error GUIProgressBar::set_gfx_progress_bar(GFX::ProgressBar const* progress_bar) { + ERR_FAIL_NULL_V(progress_bar, FAILED); + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + + Error err = OK; + + static constexpr double MIN_VALUE = 0.0, MAX_VALUE = 1.0; + static constexpr uint32_t STEPS = 100; + + set_nine_patch_stretch(true); + set_step((MAX_VALUE - MIN_VALUE) / STEPS); + set_min(MIN_VALUE); + set_max(MAX_VALUE); + + using enum AssetManager::LoadFlags; + + Ref<ImageTexture> back_texture; + if (!progress_bar->get_back_texture_file().empty()) { + const StringName back_texture_file = Utilities::std_to_godot_string(progress_bar->get_back_texture_file()); + back_texture = asset_manager->get_texture(back_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y); + if (back_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to load sprite back texture ", back_texture_file, " for GUIProgressBar ", get_name() + ); + err = FAILED; + } + } + 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 sprite ", back_colour, " back texture for GUIProgressBar ", get_name() + ); + err = FAILED; + } + } + if (back_texture.is_valid()) { + set_under_texture(back_texture); + } else { + UtilityFunctions::push_error( + "Failed to create and set sprite back texture for GUIProgressBar ", get_name() + ); + err = FAILED; + } + + Ref<ImageTexture> progress_texture; + if (!progress_bar->get_progress_texture_file().empty()) { + const StringName progress_texture_file = + Utilities::std_to_godot_string(progress_bar->get_progress_texture_file()); + progress_texture = asset_manager->get_texture(progress_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y); + if (progress_texture.is_null()) { + UtilityFunctions::push_error( + "Failed to load sprite progress texture ", progress_texture_file, " for GUIProgressBar ", + get_name() + ); + err = FAILED; + } + } + 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 sprite ", progress_colour, " progress texture for GUIProgressBar ", + get_name() + ); + err = FAILED; + } + } + if (progress_texture.is_valid()) { + set_progress_texture(progress_texture); + } else { + UtilityFunctions::push_error( + "Failed to create and set sprite progress texture for GUIProgressBar ", get_name() + ); + err = FAILED; + } + + // TODO - work out why progress bar is missing bottom border pixel (e.g. province building expansion bar) + set_custom_minimum_size( + Utilities::to_godot_fvec2(static_cast<fvec2_t>(progress_bar->get_size())) + ); + + return err; +} diff --git a/extension/src/openvic-extension/classes/GUIProgressBar.hpp b/extension/src/openvic-extension/classes/GUIProgressBar.hpp new file mode 100644 index 0000000..11b677a --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIProgressBar.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include <godot_cpp/classes/texture_progress_bar.hpp> + +#include "openvic-simulation/interface/GFXSprite.hpp" +#include "openvic-extension/classes/GUIHasTooltip.hpp" + +namespace OpenVic { + class GUIProgressBar : public godot::TextureProgressBar { + GDCLASS(GUIProgressBar, godot::TextureProgressBar) + + GUI_TOOLTIP_DEFINITIONS + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUIProgressBar(); + + godot::Error set_gfx_progress_bar(GFX::ProgressBar const* progress_bar); + }; +} diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp index ddcba7c..6e310d7 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -18,7 +18,11 @@ StringName const& GUIScrollbar::signal_value_changed() { return signal_value_changed; } +GUI_TOOLTIP_IMPLEMENTATIONS(GUIScrollbar) + void GUIScrollbar::_bind_methods() { + GUI_TOOLTIP_BIND_METHODS(GUIScrollbar) + OV_BIND_METHOD(GUIScrollbar::emit_value_changed); OV_BIND_METHOD(GUIScrollbar::reset); OV_BIND_METHOD(GUIScrollbar::clear); @@ -49,7 +53,7 @@ void GUIScrollbar::_bind_methods() { ADD_SIGNAL(MethodInfo(signal_value_changed(), PropertyInfo(Variant::INT, "value"))); } -GUIScrollbar::GUIScrollbar() { +GUIScrollbar::GUIScrollbar() : tooltip_active { false } { /* Anything which the constructor might not have default initialised will be set by clear(). */ clear(); } @@ -587,11 +591,16 @@ void GUIScrollbar::_gui_input(Ref<InputEvent> const& event) { hover_more = !hover_more; queue_redraw(); } + + _set_tooltip_active(hover_slider || hover_track || hover_less || hover_more); + return; } } void GUIScrollbar::_notification(int what) { + // GUIScrollbar doesn't use _tooltip_notification, as we don't want to show tooltips when hovering over transparent parts. + switch (what) { case NOTIFICATION_VISIBILITY_CHANGED: case NOTIFICATION_MOUSE_EXIT: { @@ -602,6 +611,10 @@ void GUIScrollbar::_notification(int what) { queue_redraw(); } break; + case NOTIFICATION_MOUSE_EXIT_SELF: { + _set_tooltip_active(false); + } break; + /* Pressing (and holding) less and more buttons. */ case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { const double delta = get_physics_process_delta_time(); diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp index 16b2e00..d9b22f1 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.hpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp @@ -6,11 +6,14 @@ #include <openvic-simulation/interface/GUI.hpp> #include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUIHasTooltip.hpp" namespace OpenVic { class GUIScrollbar : public godot::Control { GDCLASS(GUIScrollbar, godot::Control) + GUI_TOOLTIP_DEFINITIONS + GUI::Scrollbar const* PROPERTY(gui_scrollbar); godot::Ref<GFXSpriteTexture> slider_texture; @@ -74,6 +77,7 @@ namespace OpenVic { protected: static void _bind_methods(); + void _notification(int what); public: diff --git a/extension/src/openvic-extension/classes/GUITextureRect.cpp b/extension/src/openvic-extension/classes/GUITextureRect.cpp new file mode 100644 index 0000000..fba9b19 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUITextureRect.cpp @@ -0,0 +1,16 @@ +#include "GUITextureRect.hpp" + +using namespace godot; +using namespace OpenVic; + +GUI_TOOLTIP_IMPLEMENTATIONS(GUITextureRect) + +void GUITextureRect::_bind_methods() { + GUI_TOOLTIP_BIND_METHODS(GUITextureRect) +} + +void GUITextureRect::_notification(int what) { + _tooltip_notification(what); +} + +GUITextureRect::GUITextureRect() : tooltip_active { false } {} diff --git a/extension/src/openvic-extension/classes/GUITextureRect.hpp b/extension/src/openvic-extension/classes/GUITextureRect.hpp new file mode 100644 index 0000000..6fc8123 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUITextureRect.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <godot_cpp/classes/texture_rect.hpp> + +#include "openvic-extension/classes/GUIHasTooltip.hpp" + +namespace OpenVic { + class GUITextureRect : public godot::TextureRect { + GDCLASS(GUITextureRect, godot::TextureRect) + + GUI_TOOLTIP_DEFINITIONS + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUITextureRect(); + }; +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index bd50e34..7927b22 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -6,11 +6,19 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIButton.hpp" +#include "openvic-extension/classes/GUIIcon.hpp" +#include "openvic-extension/classes/GUIIconButton.hpp" #include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" +#include "openvic-extension/classes/GUIMaskedFlag.hpp" +#include "openvic-extension/classes/GUIMaskedFlagButton.hpp" #include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIPieChart.hpp" +#include "openvic-extension/classes/GUIProgressBar.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" +#include "openvic-extension/classes/GUITextureRect.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -76,11 +84,23 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class<GFXMaskedFlagTexture>(); ClassDB::register_class<GFXPieChartTexture>(); + ClassDB::register_class<GUIButton>(); ClassDB::register_class<GUILabel>(); ClassDB::register_class<GUIListBox>(); ClassDB::register_class<GUINode>(); ClassDB::register_class<GUIOverlappingElementsBox>(); + ClassDB::register_class<GUIPieChart>(); + ClassDB::register_class<GUIProgressBar>(); ClassDB::register_class<GUIScrollbar>(); + ClassDB::register_class<GUITextureRect>(); + + /* Depend on GUITextureRect */ + ClassDB::register_class<GUIIcon>(); + ClassDB::register_class<GUIMaskedFlag>(); + + /* Depend on GUIButton */ + ClassDB::register_class<GUIIconButton>(); + ClassDB::register_class<GUIMaskedFlagButton>(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp index d17edd0..3b37c8c 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.cpp +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -120,6 +120,29 @@ Ref<ImageTexture> AssetManager::get_texture(StringName const& path, LoadFlags lo } } +Ref<StyleBoxTexture> AssetManager::make_stylebox_texture(Ref<Texture2D> const& texture, Vector2 const& border) { + ERR_FAIL_NULL_V(texture, nullptr); + + Ref<StyleBoxTexture> stylebox; + stylebox.instantiate(); + ERR_FAIL_NULL_V(stylebox, nullptr); + + stylebox->set_texture(texture); + + static const StringName changed_signal = "changed"; + static const StringName emit_changed_func = "emit_changed"; + texture->connect(changed_signal, Callable { *stylebox, emit_changed_func }, Object::CONNECT_PERSIST); + + if (border != Vector2 {}) { + stylebox->set_texture_margin(SIDE_LEFT, border.x); + stylebox->set_texture_margin(SIDE_RIGHT, border.x); + stylebox->set_texture_margin(SIDE_TOP, border.y); + stylebox->set_texture_margin(SIDE_BOTTOM, border.y); + } + + return stylebox; +} + Ref<FontFile> AssetManager::get_font(StringName const& name) { const font_map_t::const_iterator it = fonts.find(name); if (it != fonts.end()) { diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp index deca309..96cb880 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.hpp +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -3,7 +3,9 @@ #include <godot_cpp/classes/atlas_texture.hpp> #include <godot_cpp/classes/font_file.hpp> #include <godot_cpp/classes/image_texture.hpp> +#include <godot_cpp/classes/style_box_texture.hpp> #include <godot_cpp/core/class_db.hpp> +#include <godot_cpp/variant/vector2.hpp> #include <openvic-simulation/interface/GFXSprite.hpp> @@ -68,6 +70,10 @@ namespace OpenVic { godot::StringName const& path, LoadFlags load_flags = LOAD_FLAG_CACHE_TEXTURE ); + static godot::Ref<godot::StyleBoxTexture> make_stylebox_texture( + godot::Ref<godot::Texture2D> const& texture, godot::Vector2 const& border = {} + ); + /* Search for and load a font with the specified name from the game defines' font directory, first checking the * AssetManager's font cache in case it has already been loaded, and returning nullptr if font loading fails. */ godot::Ref<godot::FontFile> get_font(godot::StringName const& name); diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp index 7a5f47f..367462b 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp @@ -30,6 +30,10 @@ StringName const& MenuSingleton::_signal_search_cache_changed() { static const StringName signal_search_cache_changed = "search_cache_changed"; return signal_search_cache_changed; } +StringName const& MenuSingleton::_signal_update_tooltip() { + static const StringName signal_update_tooltip = "update_tooltip"; + return signal_update_tooltip; +} String MenuSingleton::get_state_name(State const& state) const { StateSet const& state_set = state.get_state_set(); @@ -103,6 +107,16 @@ String MenuSingleton::get_country_adjective(CountryInstance const& country) cons } void MenuSingleton::_bind_methods() { + /* TOOLTIP */ + OV_BIND_METHOD(MenuSingleton::show_tooltip, { "text", "substitution_dict", "position" }); + OV_BIND_METHOD(MenuSingleton::show_control_tooltip, { "text", "substitution_dict", "control" }); + OV_BIND_METHOD(MenuSingleton::hide_tooltip); + + ADD_SIGNAL(MethodInfo( + _signal_update_tooltip(), PropertyInfo(Variant::STRING, "text"), + PropertyInfo(Variant::DICTIONARY, "substitution_dict"), PropertyInfo(Variant::VECTOR2, "position") + )); + /* PROVINCE OVERVIEW PANEL */ OV_BIND_METHOD(MenuSingleton::get_province_info_from_index, { "index" }); OV_BIND_METHOD(MenuSingleton::get_province_building_count); @@ -205,6 +219,25 @@ MenuSingleton::~MenuSingleton() { singleton = nullptr; } +/* TOOLTIP */ + +void MenuSingleton::show_tooltip(String const& text, Dictionary const& substitution_dict, Vector2 const& position) { + emit_signal(_signal_update_tooltip(), text, substitution_dict, position); +} + +void MenuSingleton::show_control_tooltip(String const& text, Dictionary const& substitution_dict, Control const* control) { + ERR_FAIL_NULL(control); + + using namespace OpenVic::Utilities::literals; + static const Vector2 offset { 0.0_real, 64.0_real }; + + show_tooltip(text, substitution_dict, control->get_global_position() + offset); +} + +void MenuSingleton::hide_tooltip() { + show_tooltip({}, {}, {}); +} + /* PROVINCE OVERVIEW PANEL */ static TypedArray<Dictionary> _make_buildings_dict_array( @@ -561,7 +594,7 @@ Error MenuSingleton::generate_search_cache() { return OK; } -void MenuSingleton::update_search_results(godot::String const& text) { +void MenuSingleton::update_search_results(String const& text) { // Sanatise input const String search_text = text.strip_edges().to_lower(); diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp index 190e3ea..022bce5 100644 --- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp +++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp @@ -1,5 +1,6 @@ #pragma once +#include <godot_cpp/classes/control.hpp> #include <godot_cpp/classes/image.hpp> #include <openvic-simulation/pop/Pop.hpp> @@ -95,6 +96,10 @@ namespace OpenVic { static godot::StringName const& _signal_population_menu_pops_changed(); /* Emitted when the collection of possible search results changes. */ static godot::StringName const& _signal_search_cache_changed(); + /* Emitted when the current tooltip changes. Arguments: text (godot::String), substitution_dict (godot::Dictionary), + * position (godot::Vector2). If text is empty then the tooltip will be hidden, otherwise the text will be shown at + * the given position. */ + static godot::StringName const& _signal_update_tooltip(); godot::String get_state_name(State const& state) const; godot::String get_country_name(CountryInstance const& country) const; @@ -110,6 +115,15 @@ namespace OpenVic { MenuSingleton(); ~MenuSingleton(); + /* TOOLTIP */ + void show_tooltip( + godot::String const& text, godot::Dictionary const& substitution_dict, godot::Vector2 const& position + ); + void show_control_tooltip( + godot::String const& text, godot::Dictionary const& substitution_dict, godot::Control const* control + ); + void hide_tooltip(); + /* PROVINCE OVERVIEW PANEL */ /* Get info to display in Province Overview Panel, packaged in a Dictionary using StringName constants as keys. */ godot::Dictionary get_province_info_from_index(int32_t index) const; diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index 723fb24..4bd537d 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -1,23 +1,23 @@ #include "UITools.hpp" -#include <godot_cpp/classes/button.hpp> #include <godot_cpp/classes/color_rect.hpp> #include <godot_cpp/classes/line_edit.hpp> #include <godot_cpp/classes/panel.hpp> #include <godot_cpp/classes/style_box_empty.hpp> #include <godot_cpp/classes/style_box_texture.hpp> -#include <godot_cpp/classes/texture_progress_bar.hpp> -#include <godot_cpp/classes/texture_rect.hpp> #include <godot_cpp/classes/theme.hpp> #include <godot_cpp/variant/utility_functions.hpp> -#include "openvic-extension/classes/GFXButtonStateTexture.hpp" -#include "openvic-extension/classes/GFXSpriteTexture.hpp" -#include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" -#include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIButton.hpp" +#include "openvic-extension/classes/GUIIcon.hpp" +#include "openvic-extension/classes/GUIIconButton.hpp" #include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" +#include "openvic-extension/classes/GUIMaskedFlag.hpp" +#include "openvic-extension/classes/GUIMaskedFlagButton.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIPieChart.hpp" +#include "openvic-extension/classes/GUIProgressBar.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" @@ -113,29 +113,6 @@ static bool new_control(T*& node, GUI::Element const& element, String const& nam return ret; } -static bool add_theme_stylebox( - Control* control, StringName const& theme_name, Ref<Texture2D> const& texture, Vector2 border = {} -) { - Ref<StyleBoxTexture> stylebox; - stylebox.instantiate(); - ERR_FAIL_NULL_V(stylebox, false); - stylebox->set_texture(texture); - - static const StringName changed_signal = "changed"; - static const StringName emit_changed_func = "emit_changed"; - texture->connect(changed_signal, Callable { *stylebox, emit_changed_func }, Object::CONNECT_PERSIST); - - if (border != Vector2 {}) { - stylebox->set_texture_margin(SIDE_LEFT, border.x); - stylebox->set_texture_margin(SIDE_RIGHT, border.x); - stylebox->set_texture_margin(SIDE_TOP, border.y); - stylebox->set_texture_margin(SIDE_BOTTOM, border.y); - } - - control->add_theme_stylebox_override(theme_name, stylebox); - return true; -}; - static bool generate_icon(generate_gui_args_t&& args) { using namespace OpenVic::Utilities::literals; @@ -146,154 +123,70 @@ static bool generate_icon(generate_gui_args_t&& args) { /* Change to use sprite type to choose Godot node type! */ bool ret = true; if (icon.get_sprite() != nullptr) { - if (icon.get_sprite()->is_type<GFX::IconTextureSprite>()) { - TextureRect* godot_texture_rect = nullptr; - ret &= new_control(godot_texture_rect, icon, args.name); - ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); + if (GFX::IconTextureSprite const* texture_sprite = icon.get_sprite()->cast_to<GFX::IconTextureSprite>()) { + GUIIcon* gui_icon = nullptr; + ret &= new_control(gui_icon, icon, args.name); + ERR_FAIL_NULL_V_MSG( + gui_icon, false, vformat("Failed to create GUIIcon for GUI icon %s", icon_name) + ); - godot_texture_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + gui_icon->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - GFX::IconTextureSprite const* texture_sprite = icon.get_sprite()->cast_to<GFX::IconTextureSprite>(); - Ref<GFXSpriteTexture> texture = GFXSpriteTexture::make_gfx_sprite_texture(texture_sprite, icon.get_frame()); - if (texture.is_valid()) { - godot_texture_rect->set_texture(texture); - } else { - UtilityFunctions::push_error("Failed to make GFXSpriteTexture for GUI icon ", icon_name); + if (gui_icon->set_gfx_texture_sprite(texture_sprite, icon.get_frame()) != OK) { + UtilityFunctions::push_error("Error setting up GUIIcon for GUI icon ", icon_name); ret = false; } const float scale = icon.get_scale(); - godot_texture_rect->set_scale({ scale, scale }); - - args.result = godot_texture_rect; - } else if (icon.get_sprite()->is_type<GFX::MaskedFlag>()) { - TextureRect* godot_texture_rect = nullptr; - ret &= new_control(godot_texture_rect, icon, args.name); - ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); - - GFX::MaskedFlag const* masked_flag = icon.get_sprite()->cast_to<GFX::MaskedFlag>(); - Ref<GFXMaskedFlagTexture> texture = GFXMaskedFlagTexture::make_gfx_masked_flag_texture(masked_flag); - if (texture.is_valid()) { - godot_texture_rect->set_texture(texture); - } else { - UtilityFunctions::push_error("Failed to make GFXMaskedFlagTexture for GUI icon ", icon_name); - ret = false; - } + gui_icon->set_scale({ scale, scale }); - args.result = godot_texture_rect; - } else if (icon.get_sprite()->is_type<GFX::ProgressBar>()) { - TextureProgressBar* godot_progress_bar = nullptr; - ret &= new_control(godot_progress_bar, icon, args.name); + args.result = gui_icon; + } else if (GFX::MaskedFlag const* masked_flag = icon.get_sprite()->cast_to<GFX::MaskedFlag>()) { + GUIMaskedFlag* gui_masked_flag = nullptr; + ret &= new_control(gui_masked_flag, icon, args.name); ERR_FAIL_NULL_V_MSG( - godot_progress_bar, false, vformat("Failed to create TextureProgressBar for GUI icon %s", icon_name) + gui_masked_flag, false, vformat("Failed to create GUIMaskedFlag for GUI icon %s", icon_name) ); - static constexpr double MIN_VALUE = 0.0, MAX_VALUE = 1.0; - static constexpr uint32_t STEPS = 100; - - godot_progress_bar->set_nine_patch_stretch(true); - godot_progress_bar->set_step((MAX_VALUE - MIN_VALUE) / STEPS); - godot_progress_bar->set_min(MIN_VALUE); - godot_progress_bar->set_max(MAX_VALUE); - - GFX::ProgressBar const* progress_bar = icon.get_sprite()->cast_to<GFX::ProgressBar>(); - - using enum AssetManager::LoadFlags; - - Ref<ImageTexture> back_texture; - if (!progress_bar->get_back_texture_file().empty()) { - const StringName back_texture_file = Utilities::std_to_godot_string(progress_bar->get_back_texture_file()); - back_texture = args.asset_manager.get_texture(back_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y); - 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 create and set progress bar sprite back texture for GUI icon ", icon_name - ); + if (gui_masked_flag->set_gfx_masked_flag(masked_flag) != OK) { + UtilityFunctions::push_error("Error setting up GUIMaskedFlag for GUI icon ", icon_name); ret = false; } - Ref<ImageTexture> progress_texture; - if (!progress_bar->get_progress_texture_file().empty()) { - const StringName progress_texture_file = - Utilities::std_to_godot_string(progress_bar->get_progress_texture_file()); - progress_texture = - args.asset_manager.get_texture(progress_texture_file, LOAD_FLAG_CACHE_TEXTURE | LOAD_FLAG_FLIP_Y); - 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 create and set progress bar sprite progress texture for GUI icon ", icon_name - ); + args.result = gui_masked_flag; + } else if (GFX::ProgressBar const* progress_bar = icon.get_sprite()->cast_to<GFX::ProgressBar>()) { + GUIProgressBar* gui_progress_bar = nullptr; + ret &= new_control(gui_progress_bar, icon, args.name); + ERR_FAIL_NULL_V_MSG( + gui_progress_bar, false, vformat("Failed to create GUIProgressBar for GUI icon %s", icon_name) + ); + + if (gui_progress_bar->set_gfx_progress_bar(progress_bar) != OK) { + UtilityFunctions::push_error("Error setting up GUIProgressBar 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 = gui_progress_bar; + } else if (GFX::PieChart const* pie_chart = icon.get_sprite()->cast_to<GFX::PieChart>()) { + GUIPieChart* gui_pie_chart = nullptr; + ret &= new_control(gui_pie_chart, icon, args.name); + ERR_FAIL_NULL_V_MSG( + gui_pie_chart, false, vformat("Failed to create GUIPieChart for GUI icon %s", icon_name) ); - args.result = godot_progress_bar; - } else if (icon.get_sprite()->is_type<GFX::PieChart>()) { - TextureRect* godot_texture_rect = nullptr; - ret &= new_control(godot_texture_rect, icon, args.name); - ERR_FAIL_NULL_V_MSG(godot_texture_rect, false, vformat("Failed to create TextureRect for GUI icon %s", icon_name)); - - GFX::PieChart const* pie_chart = icon.get_sprite()->cast_to<GFX::PieChart>(); - Ref<GFXPieChartTexture> texture = GFXPieChartTexture::make_gfx_pie_chart_texture(pie_chart); - if (texture.is_valid()) { - godot_texture_rect->set_texture(texture); - // TODO - work out why this is needed - Vector2 pos = godot_texture_rect->get_position(); - pos.x -= texture->get_width() / 2.0_real; - godot_texture_rect->set_position(pos); + if (gui_pie_chart->set_gfx_pie_chart(pie_chart) == OK) { + // For some reason pie charts are defined by their top-centre position, so we need to subtract + // half the width from the x-coordinate to fix this and position the GUIPieChart correctly. + Vector2 pos = gui_pie_chart->get_position(); + pos.x -= gui_pie_chart->get_gfx_pie_chart_texture()->get_width() / 2.0_real; + gui_pie_chart->set_position(pos); } else { - UtilityFunctions::push_error("Failed to make GFXPieChartTexture for GUI icon ", icon_name); + UtilityFunctions::push_error("Error setting up GUIPieChart for GUI icon ", icon_name); ret = false; } - args.result = godot_texture_rect; - } else if (icon.get_sprite()->is_type<GFX::LineChart>()) { + args.result = gui_pie_chart; + } else if (GFX::LineChart const* line_chart = icon.get_sprite()->cast_to<GFX::LineChart>()) { // TODO - generate line chart } else { UtilityFunctions::push_error( @@ -327,86 +220,53 @@ static bool generate_button(generate_gui_args_t&& args) { // TODO - shortcut, clicksound, rotation (?) const String button_name = Utilities::std_to_godot_string(button.get_name()); - Button* godot_button = nullptr; - bool ret = new_control(godot_button, button, args.name); - ERR_FAIL_NULL_V_MSG(godot_button, false, vformat("Failed to create Button for GUI button %s", button_name)); + ERR_FAIL_NULL_V_MSG(button.get_sprite(), false, vformat("Null sprite for GUI button %s", button_name)); - godot_button->set_mouse_filter(Control::MOUSE_FILTER_PASS); + GUIButton* gui_button = nullptr; + bool ret = true; - godot_button->set_text(Utilities::std_to_godot_string(button.get_text())); + if (GFX::IconTextureSprite const* texture_sprite = button.get_sprite()->cast_to<GFX::IconTextureSprite>()) { + GUIIconButton* gui_icon_button = nullptr; + ret &= new_control(gui_icon_button, button, args.name); + ERR_FAIL_NULL_V_MSG(gui_icon_button, false, vformat("Failed to create GUIIconButton for GUI button %s", button_name)); - if (button.get_sprite() != nullptr) { - Ref<GFXButtonStateHavingTexture> texture; - if (button.get_sprite()->is_type<GFX::IconTextureSprite>()) { - GFX::IconTextureSprite const* texture_sprite = button.get_sprite()->cast_to<GFX::IconTextureSprite>(); - texture = GFXSpriteTexture::make_gfx_sprite_texture(texture_sprite); - if (texture.is_null()) { - UtilityFunctions::push_error("Failed to make GFXSpriteTexture 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); - if (texture.is_null()) { - UtilityFunctions::push_error("Failed to make GFXMaskedFlagTexture for GUI button ", button_name); - ret = false; - } - } else { - UtilityFunctions::push_error( - "Invalid sprite type ", Utilities::std_to_godot_string(button.get_sprite()->get_type()), - " for GUI button ", button_name - ); + if (gui_icon_button->set_gfx_texture_sprite(texture_sprite) != OK) { + UtilityFunctions::push_error("Error setting up GUIIconButton for GUI button ", button_name); ret = false; } - if (texture.is_valid()) { - godot_button->set_custom_minimum_size(texture->get_size()); - - static const StringName normal_theme = "normal"; - ret &= add_theme_stylebox(godot_button, normal_theme, texture); - - using enum GFXButtonStateTexture::ButtonState; - for (GFXButtonStateTexture::ButtonState button_state : { HOVER, PRESSED, DISABLED }) { - Ref<GFXButtonStateTexture> button_state_texture = texture->get_button_state_texture(button_state); - if (button_state_texture.is_valid()) { - ret &= add_theme_stylebox( - godot_button, button_state_texture->get_button_state_name(), button_state_texture - ); - } else { - UtilityFunctions::push_error( - "Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state), - " GFXButtonStateTexture for GUI button ", button_name - ); - ret = false; - } - } + gui_button = gui_icon_button; + } else if (GFX::MaskedFlag const* masked_flag = button.get_sprite()->cast_to<GFX::MaskedFlag>()) { + GUIMaskedFlagButton* gui_masked_flag_button = nullptr; + ret &= new_control(gui_masked_flag_button, button, args.name); + ERR_FAIL_NULL_V_MSG( + gui_masked_flag_button, false, vformat("Failed to create GUIMaskedFlagButton for GUI button %s", button_name) + ); + + if (gui_masked_flag_button->set_gfx_masked_flag(masked_flag) != OK) { + UtilityFunctions::push_error("Error setting up GUIMaskedFlagButton for GUI button ", button_name); + ret = false; } + + gui_button = gui_masked_flag_button; } else { - UtilityFunctions::push_error("Null sprite for GUI button ", button_name); - ret = false; + ERR_FAIL_V_MSG( + false, vformat( + "Invalid sprite type %s for GUI button %s", Utilities::std_to_godot_string(button.get_sprite()->get_type()), + button_name + ) + ); } - if (button.get_font() != nullptr) { - const StringName font_file = Utilities::std_to_godot_string(button.get_font()->get_fontname()); - const Ref<Font> font = args.asset_manager.get_font(font_file); - if (font.is_valid()) { - static const StringName font_theme = "font"; - godot_button->add_theme_font_override(font_theme, font); - } else { - UtilityFunctions::push_error("Failed to load font \"", font_file, "\" for GUI button ", button_name); - ret = false; - } + gui_button->set_mouse_filter(Control::MOUSE_FILTER_PASS); - 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()); - for (StringName const& theme_name : button_font_themes) { - godot_button->add_theme_color_override(theme_name, colour); - } + gui_button->set_text(Utilities::std_to_godot_string(button.get_text())); + + if (button.get_font() != nullptr) { + ret &= gui_button->set_gfx_font(button.get_font()) == OK; } - args.result = godot_button; + args.result = gui_button; return ret; } @@ -416,86 +276,37 @@ static bool generate_checkbox(generate_gui_args_t&& args) { // TODO - shortcut const String checkbox_name = Utilities::std_to_godot_string(checkbox.get_name()); - Button* godot_button = nullptr; - bool ret = new_control(godot_button, checkbox, args.name); - ERR_FAIL_NULL_V_MSG(godot_button, false, vformat("Failed to create Button for GUI checkbutton %s", checkbox_name)); - - godot_button->set_text(Utilities::std_to_godot_string(checkbox.get_text())); + ERR_FAIL_NULL_V_MSG(checkbox.get_sprite(), false, vformat("Null sprite for GUI checkbox %s", checkbox_name)); - godot_button->set_toggle_mode(true); + GFX::IconTextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to<GFX::IconTextureSprite>(); - if (checkbox.get_sprite() != nullptr) { - GFX::IconTextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to<GFX::IconTextureSprite>(); - - if (texture_sprite != nullptr) { - Ref<GFXSpriteTexture> texture = GFXSpriteTexture::make_gfx_sprite_texture(texture_sprite); + ERR_FAIL_NULL_V_MSG( + texture_sprite, false, vformat( + "Invalid sprite type %s for GUI checkbox %s", Utilities::std_to_godot_string(checkbox.get_sprite()->get_type()), + checkbox_name + ) + ); - if (texture.is_valid()) { - godot_button->set_custom_minimum_size(texture->get_size()); + GUIIconButton* gui_icon_button = nullptr; + bool ret = new_control(gui_icon_button, checkbox, args.name); + ERR_FAIL_NULL_V_MSG( + gui_icon_button, false, vformat("Failed to create GUIIconButton for GUI checkbox %s", checkbox_name) + ); - if (texture->get_icon_count() > 1) { - static const StringName toggled_signal = "toggled"; - static const StringName set_toggled_icon_func = "set_toggled_icon"; - godot_button->connect( - toggled_signal, Callable { *texture, set_toggled_icon_func }, Object::CONNECT_PERSIST - ); - } + gui_icon_button->set_toggle_mode(true); - static const StringName normal_theme = "normal"; - ret &= add_theme_stylebox(godot_button, normal_theme, texture); - - using enum GFXButtonStateTexture::ButtonState; - for (GFXButtonStateTexture::ButtonState button_state : { HOVER, PRESSED, DISABLED }) { - Ref<GFXButtonStateTexture> button_state_texture = texture->get_button_state_texture(button_state); - if (button_state_texture.is_valid()) { - ret &= add_theme_stylebox( - godot_button, button_state_texture->get_button_state_name(), button_state_texture - ); - } else { - UtilityFunctions::push_error( - "Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state), - " GFXButtonStateTexture for GUI checkbox ", checkbox_name - ); - ret = false; - } - } - } else { - UtilityFunctions::push_error("Failed to make GFXSpriteTexture for GUI checkbox ", checkbox_name); - ret = false; - } - } else { - UtilityFunctions::push_error( - "Invalid sprite type ", Utilities::std_to_godot_string(checkbox.get_sprite()->get_type()), - " for GUI checkbox ", checkbox_name - ); - ret = false; - } - } else { - UtilityFunctions::push_error("Null sprite for GUI checkbox ", checkbox_name); + if (gui_icon_button->set_gfx_texture_sprite(texture_sprite) != OK) { + UtilityFunctions::push_error("Error setting up GUIIconButton for GUI checkbox ", checkbox_name); ret = false; } - if (checkbox.get_font() != nullptr) { - const StringName font_file = Utilities::std_to_godot_string(checkbox.get_font()->get_fontname()); - const Ref<Font> font = args.asset_manager.get_font(font_file); - if (font.is_valid()) { - static const StringName font_theme = "font"; - godot_button->add_theme_font_override(font_theme, font); - } else { - UtilityFunctions::push_error("Failed to load font \"", font_file, "\" for GUI checkbox ", checkbox_name); - ret = false; - } + gui_icon_button->set_text(Utilities::std_to_godot_string(checkbox.get_text())); - static const std::vector<StringName> checkbox_font_themes { - "font_color", "font_hover_color", "font_hover_pressed_color", "font_pressed_color", "font_disabled_color" - }; - const Color colour = Utilities::to_godot_color(checkbox.get_font()->get_colour()); - for (StringName const& theme_name : checkbox_font_themes) { - godot_button->add_theme_color_override(theme_name, colour); - } + if (checkbox.get_font() != nullptr) { + ret &= gui_icon_button->set_gfx_font(checkbox.get_font()) == OK; } - args.result = godot_button; + args.result = gui_icon_button; return ret; } @@ -615,8 +426,14 @@ static bool generate_texteditbox(generate_gui_args_t&& args) { Ref<ImageTexture> texture = args.asset_manager.get_texture(texture_file); if (texture.is_valid()) { - static const StringName normal_theme = "normal"; - ret &= add_theme_stylebox(godot_line_edit, normal_theme, texture, border_size); + Ref<StyleBoxTexture> stylebox = AssetManager::make_stylebox_texture(texture, border_size); + if (stylebox.is_valid()) { + static const StringName normal_theme = "normal"; + godot_line_edit->add_theme_stylebox_override(normal_theme, stylebox); + } else { + UtilityFunctions::push_error("Failed to make StyleBoxTexture for GUI button ", text_edit_box_name); + ret = false; + } } else { UtilityFunctions::push_error( "Failed to load texture \"", texture_file, "\" for text edit box \"", text_edit_box_name, "\"" @@ -673,7 +490,16 @@ static bool generate_window(generate_gui_args_t&& args) { ERR_FAIL_NULL_V_MSG(godot_panel, false, vformat("Failed to create Panel for GUI window %s", window_name)); godot_panel->set_custom_minimum_size(Utilities::to_godot_fvec2(window.get_size())); - godot_panel->set_self_modulate({ 1.0_real, 1.0_real, 1.0_real, 0.0_real }); + + Ref<StyleBoxEmpty> stylebox_empty; + stylebox_empty.instantiate(); + if (stylebox_empty.is_valid()) { + static const StringName panel_theme = "panel"; + godot_panel->add_theme_stylebox_override(panel_theme, stylebox_empty); + } else { + UtilityFunctions::push_error("Failed to create empty style box for background of GUI window ", window_name); + ret = false; + } for (std::unique_ptr<GUI::Element> const& element : window.get_window_elements()) { Control* node = nullptr; |