From 843edde55306e3fbdb5e37ef9b7c09c7b53f50c4 Mon Sep 17 00:00:00 2001 From: hop311 Date: Sat, 24 Feb 2024 13:17:07 +0000 Subject: Added GUIScrollbar and GFXCorneredTileSupportingTexture --- extension/deps/openvic-simulation | 2 +- .../classes/GFXButtonStateTexture.cpp | 62 +- .../classes/GFXButtonStateTexture.hpp | 35 +- .../openvic-extension/classes/GFXSpriteTexture.cpp | 26 +- .../openvic-extension/classes/GFXSpriteTexture.hpp | 8 +- .../src/openvic-extension/classes/GUINode.cpp | 14 +- .../src/openvic-extension/classes/GUINode.hpp | 6 + .../src/openvic-extension/classes/GUIScrollbar.cpp | 654 +++++++++++++++++++++ .../src/openvic-extension/classes/GUIScrollbar.hpp | 102 ++++ extension/src/openvic-extension/register_types.cpp | 8 + .../openvic-extension/singletons/GameSingleton.cpp | 10 - .../openvic-extension/singletons/GameSingleton.hpp | 2 - .../src/openvic-extension/utility/UITools.cpp | 20 +- .../src/openvic-extension/utility/Utilities.cpp | 5 +- .../src/openvic-extension/utility/Utilities.hpp | 2 +- .../NationManagementScreen/BudgetMenu.gd | 14 + game/src/Game/GameSession/ProvinceOverviewPanel.gd | 6 +- 17 files changed, 907 insertions(+), 69 deletions(-) create mode 100644 extension/src/openvic-extension/classes/GUIScrollbar.cpp create mode 100644 extension/src/openvic-extension/classes/GUIScrollbar.hpp diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index 147e0a7..164e76e 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit 147e0a772dc768bd90fdccdd2536f14c11238d57 +Subproject commit 164e76e367ff7dc5914f0d7105b5914fd3fba90a diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp index dcee965..7cbc0a6 100644 --- a/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp @@ -1,5 +1,6 @@ #include "GFXButtonStateTexture.hpp" +#include #include #include "openvic-extension/utility/ClassBindings.hpp" @@ -7,6 +8,33 @@ using namespace OpenVic; using namespace godot; +void GFXCorneredTileSupportingTexture::_bind_methods() { + OV_BIND_METHOD(GFXCorneredTileSupportingTexture::get_cornered_tile_border_size); + OV_BIND_METHOD(GFXCorneredTileSupportingTexture::is_cornered_tile_texture); + OV_BIND_METHOD(GFXCorneredTileSupportingTexture::draw_rect_cornered, { "to_canvas_item", "rect" }); +} + +GFXCorneredTileSupportingTexture::GFXCorneredTileSupportingTexture() : cornered_tile_border_size {} {} + +bool GFXCorneredTileSupportingTexture::is_cornered_tile_texture() const { + return cornered_tile_border_size != Vector2i {}; +} + +void GFXCorneredTileSupportingTexture::draw_rect_cornered(RID const& to_canvas_item, Rect2 const& rect) const { + if (is_cornered_tile_texture()) { + RenderingServer* rendering_server = RenderingServer::get_singleton(); + if (rendering_server != nullptr) { + const Size2 size = get_size(); + rendering_server->canvas_item_add_nine_patch( + to_canvas_item, rect, { {}, size }, get_rid(), + cornered_tile_border_size, size - cornered_tile_border_size + ); + } + } else { + draw_rect(to_canvas_item, rect, false); + } +} + void GFXButtonStateTexture::_bind_methods() { OV_BIND_METHOD(GFXButtonStateTexture::set_button_state, { "new_button_state" }); OV_BIND_METHOD(GFXButtonStateTexture::get_button_state); @@ -21,17 +49,19 @@ void GFXButtonStateTexture::_bind_methods() { BIND_ENUM_CONSTANT(DISABLED); } -GFXButtonStateTexture::GFXButtonStateTexture() : button_state { HOVER } {} +GFXButtonStateTexture::GFXButtonStateTexture() : button_state { HOVER }, state_image {}, state_texture {} {} Ref GFXButtonStateTexture::make_gfx_button_state_texture( - ButtonState button_state, Ref const& source_image, Rect2i const& region + ButtonState button_state, Ref const& source_image, Rect2i const& region, Vector2i const& cornered_tile_border_size ) { Ref button_state_texture; button_state_texture.instantiate(); ERR_FAIL_NULL_V(button_state_texture, nullptr); button_state_texture->set_button_state(button_state); if (source_image.is_valid()) { - ERR_FAIL_COND_V(button_state_texture->generate_state_image(source_image, region) != OK, nullptr); + ERR_FAIL_COND_V( + button_state_texture->generate_state_image(source_image, region, cornered_tile_border_size) != OK, nullptr + ); } return button_state_texture; } @@ -41,19 +71,31 @@ void GFXButtonStateTexture::set_button_state(ButtonState new_button_state) { button_state = new_button_state; } -Error GFXButtonStateTexture::generate_state_image(Ref const& source_image, Rect2i const& region) { +Error GFXButtonStateTexture::generate_state_image( + Ref const& source_image, Rect2i const& region, Vector2i const& new_cornered_tile_border_size +) { ERR_FAIL_COND_V(source_image.is_null() || source_image->is_empty(), FAILED); const Rect2i source_image_rect { {}, source_image->get_size() }; ERR_FAIL_COND_V(!region.has_area() || !source_image_rect.encloses(region), FAILED); /* Whether we've already set the ImageTexture to an image of the right dimensions and format, * and so can update it without creating and setting a new image, or not. */ - const bool can_update = state_image.is_valid() && state_image->get_size() == region.get_size() + bool can_update = state_image.is_valid() && state_image->get_size() == region.get_size() && state_image->get_format() == source_image->get_format(); if (!can_update) { state_image = Image::create(region.size.width, region.size.height, false, source_image->get_format()); ERR_FAIL_NULL_V(state_image, FAILED); } + if (state_texture.is_null()) { + can_update = false; + state_texture.instantiate(); + ERR_FAIL_NULL_V(state_texture, FAILED); + set_atlas(state_texture); + set_region({ {}, state_image->get_size() }); + } + + cornered_tile_border_size = new_cornered_tile_border_size; + static constexpr auto hover_colour = [](Color const& colour) -> Color { return { std::min(colour.r + 0.1f, 1.0f), std::min(colour.g + 0.1f, 1.0f), std::min(colour.b + 0.1f, 1.0f), colour.a }; }; @@ -74,9 +116,9 @@ Error GFXButtonStateTexture::generate_state_image(Ref const& source_image } if (can_update) { - update(state_image); + state_texture->update(state_image); } else { - set_image(state_image); + state_texture->set_image(state_image); } return OK; } @@ -109,7 +151,7 @@ void GFXButtonStateHavingTexture::_bind_methods() { void GFXButtonStateHavingTexture::_update_button_states() { for (Ref& button_state_texture : button_state_textures) { if (button_state_texture.is_valid()) { - button_state_texture->generate_state_image(button_image, get_region()); + button_state_texture->generate_state_image(button_image, get_region(), cornered_tile_border_size); } } } @@ -132,7 +174,9 @@ Ref GFXButtonStateHavingTexture::get_button_state_texture ERR_FAIL_COND_V(button_state_index >= button_state_textures.size(), nullptr); Ref& button_state_texture = button_state_textures[button_state_index]; if (button_state_texture.is_null()) { - button_state_texture = GFXButtonStateTexture::make_gfx_button_state_texture(button_state, button_image, get_region()); + button_state_texture = GFXButtonStateTexture::make_gfx_button_state_texture( + button_state, button_image, get_region(), cornered_tile_border_size + ); ERR_FAIL_NULL_V(button_state_texture, nullptr); } return button_state_texture; diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp index c98159d..b57ea46 100644 --- a/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp @@ -6,8 +6,26 @@ #include namespace OpenVic { - class GFXButtonStateTexture : public godot::ImageTexture { - GDCLASS(GFXButtonStateTexture, godot::ImageTexture) + class GFXCorneredTileSupportingTexture : public godot::AtlasTexture { + GDCLASS(GFXCorneredTileSupportingTexture, godot::AtlasTexture) + + protected: + godot::Vector2i PROPERTY_ACCESS(cornered_tile_border_size, protected); + + static void _bind_methods(); + + public: + GFXCorneredTileSupportingTexture(); + + /* Returns true if the texture has a non-zero 9 patch border. */ + bool is_cornered_tile_texture() const; + + /* Equivalent to draw_rect, but draws a 9 patch texture if this is a cornered tile texture. */ + void draw_rect_cornered(godot::RID const& to_canvas_item, godot::Rect2 const& rect) const; + }; + + class GFXButtonStateTexture : public GFXCorneredTileSupportingTexture { + GDCLASS(GFXButtonStateTexture, GFXCorneredTileSupportingTexture) public: enum ButtonState { @@ -20,6 +38,7 @@ namespace OpenVic { private: ButtonState PROPERTY(button_state); godot::Ref state_image; + godot::Ref state_texture; protected: static void _bind_methods(); @@ -29,7 +48,8 @@ namespace OpenVic { /* Create a GFXButtonStateTexture using the specified godot::Image. Returns nullptr if generate_state_image fails. */ static godot::Ref make_gfx_button_state_texture( - ButtonState button_state, godot::Ref const& source_image, godot::Rect2i const& region + ButtonState button_state, godot::Ref const& source_image, godot::Rect2i const& region, + godot::Vector2i const& cornered_tile_border_size ); /* Set the ButtonState to be generated by this class (calling this does not trigger state image generation). */ @@ -37,14 +57,17 @@ namespace OpenVic { /* Generate a modified version of the given region of source_image * and update the underlying godot::ImageTexture to use it. */ - godot::Error generate_state_image(godot::Ref const& source_image, godot::Rect2i const& region); + godot::Error generate_state_image( + godot::Ref const& source_image, godot::Rect2i const& region, + godot::Vector2i const& new_cornered_tile_border_size + ); static godot::StringName const& button_state_to_theme_name(ButtonState button_state); godot::StringName const& get_button_state_theme() const; }; - class GFXButtonStateHavingTexture : public godot::AtlasTexture { - GDCLASS(GFXButtonStateHavingTexture, godot::AtlasTexture) + class GFXButtonStateHavingTexture : public GFXCorneredTileSupportingTexture { + GDCLASS(GFXButtonStateHavingTexture, GFXCorneredTileSupportingTexture) std::array, GFXButtonStateTexture::BUTTON_STATE_COUNT> button_state_textures; diff --git a/extension/src/openvic-extension/classes/GFXSpriteTexture.cpp b/extension/src/openvic-extension/classes/GFXSpriteTexture.cpp index e002461..41bea4b 100644 --- a/extension/src/openvic-extension/classes/GFXSpriteTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXSpriteTexture.cpp @@ -24,15 +24,11 @@ void GFXSpriteTexture::_bind_methods() { OV_BIND_METHOD(GFXSpriteTexture::get_icon_index); OV_BIND_METHOD(GFXSpriteTexture::get_icon_count); - OV_BIND_METHOD(GFXSpriteTexture::is_cornered_tile_texture); - OV_BIND_METHOD(GFXSpriteTexture::draw_rect_cornered, { "to_canvas_item", "rect" }); - ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_index"), "set_icon_index", "get_icon_index"); } GFXSpriteTexture::GFXSpriteTexture() - : gfx_texture_sprite { nullptr }, icon_index { GFX::NO_FRAMES }, icon_count { GFX::NO_FRAMES }, - cornered_tile_texture { false }, cornered_tile_border_size {} {} + : gfx_texture_sprite { nullptr }, icon_index { GFX::NO_FRAMES }, icon_count { GFX::NO_FRAMES } {} Ref GFXSpriteTexture::make_gfx_sprite_texture(GFX::TextureSprite const* gfx_texture_sprite, GFX::frame_t icon) { Ref texture; @@ -47,7 +43,6 @@ void GFXSpriteTexture::clear() { _clear_button_states(); icon_index = GFX::NO_FRAMES; icon_count = GFX::NO_FRAMES; - cornered_tile_texture = false; cornered_tile_border_size = {}; } @@ -84,10 +79,8 @@ Error GFXSpriteTexture::set_gfx_texture_sprite(GFX::TextureSprite const* new_gfx GFX::CorneredTileTextureSprite const* const cornered_tile_texture_sprite = gfx_texture_sprite->cast_to(); if (cornered_tile_texture_sprite != nullptr) { - cornered_tile_texture = true; cornered_tile_border_size = Utilities::to_godot_ivec2(cornered_tile_texture_sprite->get_border_size()); } else { - cornered_tile_texture = false; cornered_tile_border_size = {}; } } @@ -140,20 +133,3 @@ Error GFXSpriteTexture::set_icon_index(int32_t new_icon_index) { _update_button_states(); return OK; } - -void GFXSpriteTexture::draw_rect_cornered(RID const& to_canvas_item, Rect2 const& rect) const { - const Ref atlas_texture = get_atlas(); - if (atlas_texture.is_valid()) { - if (cornered_tile_texture) { - RenderingServer* rendering_server = RenderingServer::get_singleton(); - if (rendering_server != nullptr) { - rendering_server->canvas_item_add_nine_patch( - to_canvas_item, rect, { {}, atlas_texture->get_size() }, atlas_texture->get_rid(), - cornered_tile_border_size, atlas_texture->get_size() - cornered_tile_border_size - ); - } - } else { - draw_rect(to_canvas_item, rect, false); - } - } -} diff --git a/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp b/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp index 783dfba..34ec405 100644 --- a/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXSpriteTexture.hpp @@ -13,13 +13,10 @@ namespace OpenVic { /* PROPERTY automatically defines getter functions: * - get_gfx_texture_sprite * - get_icon_index - * - get_icon_count - * - is_cornered_tile_texture */ + * - get_icon_count */ GFX::TextureSprite const* PROPERTY(gfx_texture_sprite); GFX::frame_t PROPERTY(icon_index); GFX::frame_t PROPERTY(icon_count); - bool PROPERTY_CUSTOM_PREFIX(cornered_tile_texture, is); - godot::Vector2i cornered_tile_border_size; protected: static void _bind_methods(); @@ -56,8 +53,5 @@ namespace OpenVic { * If zero is used but icon_count is non-zero, icon_index defaults to icon_count (the last frame, * not the first frame because it is often empty). */ godot::Error set_icon_index(GFX::frame_t new_icon_index); - - /* Equivalent to draw_rect, but draws a 9 patch texture if this is a cornered tile texture. */ - void draw_rect_cornered(godot::RID const& to_canvas_item, godot::Rect2 const& rect) const; }; } diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index a038df7..5296ad7 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -17,7 +17,8 @@ using namespace OpenVic; F(Panel, panel) \ F(TextureProgressBar, progress_bar) \ F(TextureRect, texture_rect) \ - F(GUIOverlappingElementsBox, gui_overlapping_elements_box) + F(GUIOverlappingElementsBox, gui_overlapping_elements_box) \ + F(GUIScrollbar, gui_scrollbar) #define APPLY_TO_TEXTURE_TYPES(F) \ F(GFXSpriteTexture, gfx_sprite_texture) \ @@ -44,6 +45,9 @@ void GUINode::_bind_methods() { OV_BIND_METHOD(GUINode::hide_node, { "path" }); OV_BIND_METHOD(GUINode::hide_nodes, { "paths" }); + + OV_BIND_SMETHOD(int_to_formatted_string, { "val" }); + OV_BIND_SMETHOD(float_to_formatted_string, { "val", "decimal_places" }); } GUINode::GUINode() { @@ -177,3 +181,11 @@ Error GUINode::hide_nodes(TypedArray const& paths) const { } return ret; } + +String GUINode::int_to_formatted_string(int64_t val) { + return Utilities::int_to_formatted_string(val); +} + +String GUINode::float_to_formatted_string(float val, int32_t decimal_places) { + return Utilities::float_to_formatted_string(val, decimal_places); +} diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index 42074e3..f935683 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -13,6 +13,7 @@ #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIScrollbar.hpp" namespace OpenVic { class GUINode : public godot::Control { @@ -41,6 +42,7 @@ namespace OpenVic { static godot::TextureProgressBar* get_progress_bar_from_node(godot::Node* node); static godot::TextureRect* get_texture_rect_from_node(godot::Node* node); static GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_node(godot::Node* node); + static GUIScrollbar* get_gui_scrollbar_from_node(godot::Node* node); godot::Button* get_button_from_nodepath(godot::NodePath const& path) const; godot::CheckBox* get_check_box_from_nodepath(godot::NodePath const& path) const; @@ -49,6 +51,7 @@ namespace OpenVic { godot::TextureProgressBar* get_progress_bar_from_nodepath(godot::NodePath const& path) const; godot::TextureRect* get_texture_rect_from_nodepath(godot::NodePath const& path) const; GUIOverlappingElementsBox* get_gui_overlapping_elements_box_from_nodepath(godot::NodePath const& path) const; + GUIScrollbar* get_gui_scrollbar_from_nodepath(godot::NodePath const& path) const; /* Helper functions to get textures from TextureRects and Buttons. */ static godot::Ref get_texture_from_node(godot::Node* node); @@ -63,5 +66,8 @@ namespace OpenVic { godot::Error hide_node(godot::NodePath const& path) const; godot::Error hide_nodes(godot::TypedArray const& paths) const; + + static godot::String int_to_formatted_string(int64_t val); + static godot::String float_to_formatted_string(float val, int32_t decimal_places); }; } diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp new file mode 100644 index 0000000..0f3cde1 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -0,0 +1,654 @@ +#include "GUIScrollbar.hpp" + +#include +#include +#include + +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace OpenVic; +using namespace godot; + +using OpenVic::Utilities::std_to_godot_string; +using OpenVic::Utilities::std_view_to_godot_string; + +/* StringNames cannot be constructed until Godot has called StringName::setup(), + * so we must use wrapper functions to delay their initialisation. */ +StringName const& GUIScrollbar::_signal_value_changed() { + static const StringName signal_value_changed = "value_changed"; + return signal_value_changed; +} + +void GUIScrollbar::_bind_methods() { + OV_BIND_METHOD(GUIScrollbar::emit_value_changed); + OV_BIND_METHOD(GUIScrollbar::reset); + OV_BIND_METHOD(GUIScrollbar::clear); + + OV_BIND_METHOD(GUIScrollbar::set_gui_scrollbar_name, { "gui_scene", "gui_scrollbar_name" }); + OV_BIND_METHOD(GUIScrollbar::get_gui_scrollbar_name); + + OV_BIND_METHOD(GUIScrollbar::get_orientation); + + OV_BIND_METHOD(GUIScrollbar::get_value); + OV_BIND_METHOD(GUIScrollbar::get_value_as_ratio); + OV_BIND_METHOD(GUIScrollbar::get_min_value); + OV_BIND_METHOD(GUIScrollbar::get_max_value); + OV_BIND_METHOD(GUIScrollbar::set_value, { "new_value", "signal" }, DEFVAL(true)); + OV_BIND_METHOD(GUIScrollbar::set_value_as_ratio, { "new_ratio", "signal" }, DEFVAL(true)); + + OV_BIND_METHOD(GUIScrollbar::is_range_limited); + OV_BIND_METHOD(GUIScrollbar::get_range_limit_min); + OV_BIND_METHOD(GUIScrollbar::get_range_limit_max); + OV_BIND_METHOD(GUIScrollbar::set_range_limits, { "new_range_limit_min", "new_range_limit_max", "signal" }, DEFVAL(true)); + OV_BIND_METHOD(GUIScrollbar::set_limits, { "new_min_value", "new_max_value", "signal" }, DEFVAL(true)); + + ADD_SIGNAL(MethodInfo(_signal_value_changed(), PropertyInfo(Variant::INT, "value"))); +} + +GUIScrollbar::GUIScrollbar() { + /* Anything which the constructor might not have default initialised will be set by clear(). */ + clear(); +} + +void GUIScrollbar::_start_button_change(bool shift_pressed, bool control_pressed) { + if (shift_pressed) { + if (control_pressed) { + button_change_value_base = max_value - min_value; + } else { + button_change_value_base = 10; + } + } else { + if (control_pressed) { + button_change_value_base = 100; + } else { + button_change_value_base = 1; + } + } + if (orientation == HORIZONTAL) { + button_change_value_base = -button_change_value_base; + } + button_change_value = 0; + button_change_accelerate_timer = BUTTON_CHANGE_ACCELERATE_DELAY; + button_change_timer = BUTTON_CHANGE_DELAY; + set_physics_process_internal(true); +} + +void GUIScrollbar::_stop_button_change() { + /* This ensures value always changes by at least 1 if less/more is pressed. */ + if (button_change_value == 0) { + button_change_value = button_change_value_base; + _update_button_change(); + } + set_physics_process_internal(false); +} + +bool GUIScrollbar::_update_button_change() { + if (pressed_less) { + set_value(value + button_change_value); + } else if (pressed_more) { + set_value(value - button_change_value); + } else { + return false; + } + return true; +} + +float GUIScrollbar::_value_to_ratio(int32_t val) const { + return min_value != max_value + ? static_cast(val - min_value) / (max_value - min_value) + : 0.0f; +} + +void GUIScrollbar::_calculate_rects() { + update_minimum_size(); + + const Size2 size = _get_minimum_size(); + + if (orientation == HORIZONTAL) { + slider_distance = size.width; + + if (less_texture.is_valid()) { + less_rect = { {}, less_texture->get_size() }; + + slider_distance -= less_rect.size.width; + } else { + less_rect = {}; + } + + if (more_texture.is_valid()) { + const Size2 more_size = more_texture->get_size(); + + more_rect = { { size.width - more_size.width, 0.0f }, more_size }; + + slider_distance -= more_rect.size.width; + } else { + more_rect = {}; + } + + slider_start = less_rect.size.width; + + if (track_texture.is_valid()) { + const real_t track_height = track_texture->get_height(); + + track_rect = { { slider_start, 0.0f }, { slider_distance, track_height } }; + } else { + track_rect = {}; + } + + if (slider_texture.is_valid()) { + slider_rect = { {}, slider_texture->get_size() }; + + slider_distance -= slider_rect.size.width; + } else { + slider_rect = {}; + } + } else { /* VERTICAL */ + slider_distance = size.height; + + if (less_texture.is_valid()) { + const Size2 less_size = less_texture->get_size(); + + less_rect = { { 0.0f, size.height - less_size.height }, less_size }; + + slider_distance -= less_rect.size.height; + } else { + less_rect = {}; + } + + if (more_texture.is_valid()) { + more_rect = { {}, more_texture->get_size() }; + + slider_distance -= more_rect.size.height; + } else { + more_rect = {}; + } + + slider_start = more_rect.size.height; + const real_t average_button_width = (more_rect.size.width + less_rect.size.width) / 2.0f; + + if (track_texture.is_valid()) { + const real_t track_width = track_texture->get_width(); + + /* For some reason vertical scrollbar track textures overlap with their more buttons by a single pixel. + * They have a row of transparent pixels at the top to account for this, so we must also draw them + * one pixel taller to avoid having a gap between the track and the more button. */ + track_rect = { + { (average_button_width - track_width) / 2.0f, slider_start - 1.0f }, + { track_width, slider_distance + 1.0f } + }; + } else { + track_rect = {}; + } + + if (slider_texture.is_valid()) { + const Size2 slider_size = slider_texture->get_size(); + + slider_rect = { + { (average_button_width - slider_size.width) / 2.0f, 0.0f }, + slider_size + }; + + slider_distance -= slider_rect.size.height; + } else { + slider_rect = {}; + } + } + + if (range_limit_min_texture.is_valid()) { + range_limit_min_rect = { {}, range_limit_min_texture->get_size() }; + } else { + range_limit_min_rect = {}; + } + + if (range_limit_max_texture.is_valid()) { + range_limit_max_rect = { {}, range_limit_max_texture->get_size() }; + } else { + range_limit_max_rect = {}; + } +} + +void GUIScrollbar::_constrain_value() { + /* Clamp using range limits, as even when range limiting is disabled the limits will be set to min/max values. */ + value = std::clamp(value, range_limit_min, range_limit_max); + + slider_rect.position[orientation == HORIZONTAL ? 0 : 1] = slider_start + slider_distance * get_value_as_ratio(); + + queue_redraw(); +} + +/* _constrain_value() should be called sometime after this. */ +Error GUIScrollbar::_constrain_range_limits() { + range_limit_min = std::clamp(range_limit_min, min_value, max_value); + range_limit_max = std::clamp(range_limit_max, min_value, max_value); + + Error err = OK; + if (range_limit_min > range_limit_max) { + UtilityFunctions::push_error( + "GUIScrollbar range max ", range_limit_max, " is less than range min ", range_limit_min, " - swapping values." + ); + std::swap(range_limit_min, range_limit_max); + err = FAILED; + } + + const int axis = orientation == HORIZONTAL ? 0 : 1; + range_limit_min_rect.position[axis] = slider_start + slider_distance * _value_to_ratio(range_limit_min); + range_limit_max_rect.position[axis] = slider_start + slider_distance * _value_to_ratio(range_limit_max) + + slider_rect.size[axis] / 2.0f; + + return err; +} + +/* _constrain_range_limits() should be called sometime after this. */ +Error GUIScrollbar::_constrain_limits() { + if (min_value <= max_value) { + return OK; + } else { + UtilityFunctions::push_error( + "GUIScrollbar max value ", max_value, " is less than min value ", min_value, " - swapping values." + ); + std::swap(min_value, max_value); + return FAILED; + } +} + +Vector2 GUIScrollbar::_get_minimum_size() const { + if (gui_scrollbar != nullptr) { + Size2 size = Utilities::to_godot_fvec2(gui_scrollbar->get_size()); + + const int axis = orientation == HORIZONTAL ? 1 : 0; + if (less_texture.is_valid()) { + size[axis] = std::max(size[axis], less_texture->get_size()[axis]); + } + if (more_texture.is_valid()) { + size[axis] = std::max(size[axis], more_texture->get_size()[axis]); + } + + return size; + } else { + return {}; + } +} + +void GUIScrollbar::emit_value_changed() { + emit_signal(_signal_value_changed(), value); +} + +Error GUIScrollbar::reset() { + set_physics_process_internal(false); + button_change_accelerate_timer = 0.0; + button_change_timer = 0.0; + button_change_value_base = 0; + button_change_value = 0; + + hover_slider = false; + hover_track = false; + hover_less = false; + hover_more = false; + pressed_slider = false; + pressed_track = false; + pressed_less = false; + pressed_more = false; + + value = (max_value - min_value) / 2; + range_limit_min = min_value; + range_limit_max = max_value; + + const Error err = _constrain_range_limits(); + _constrain_value(); + emit_value_changed(); + return err; +} + +void GUIScrollbar::clear() { + gui_scrollbar = nullptr; + + slider_texture.unref(); + track_texture.unref(); + less_texture.unref(); + more_texture.unref(); + + slider_rect = {}; + track_rect = {}; + less_rect = {}; + more_rect = {}; + + range_limit_min_texture.unref(); + range_limit_max_texture.unref(); + + range_limit_min_rect = {}; + range_limit_max_rect = {}; + + orientation = HORIZONTAL; + min_value = 0; + max_value = 100; + range_limited = false; + + _calculate_rects(); + + _constrain_limits(); + reset(); +} + +Error GUIScrollbar::set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar) { + if (gui_scrollbar == new_gui_scrollbar) { + return OK; + } + if (new_gui_scrollbar == nullptr) { + clear(); + return OK; + } + + bool ret = true; + + gui_scrollbar = new_gui_scrollbar; + + const String gui_scrollbar_name = std_view_to_godot_string(gui_scrollbar->get_name()); + + orientation = gui_scrollbar->is_horizontal() ? HORIZONTAL : VERTICAL; + range_limited = gui_scrollbar->is_range_limited(); + + /* _Element is either GUI::Button or GUI::Icon, both of which have their own + * separately defined get_sprite(), hence the need for a template. */ + const auto set_texture = [&gui_scrollbar_name]( + String const& target, _Element const* element, Ref& texture + ) -> bool { + ERR_FAIL_NULL_V_MSG(element, false, vformat( + "Invalid %s element for GUIScrollbar %s - null!", target, gui_scrollbar_name + )); + const String element_name = std_view_to_godot_string(element->get_name()); + + /* Get Sprite, convert to TextureSprite, use to make a GFXSpriteTexture. */ + GFX::Sprite const* sprite = element->get_sprite(); + ERR_FAIL_NULL_V_MSG(sprite, false, vformat( + "Invalid %s element %s for GUIScrollbar %s - sprite is null!", target, element_name, gui_scrollbar_name + )); + GFX::TextureSprite const* texture_sprite = sprite->cast_to(); + ERR_FAIL_NULL_V_MSG(texture_sprite, false, vformat( + "Invalid %s element %s for GUIScrollbar %s - sprite type is %s with base type %s, expected base %s!", target, + element_name, gui_scrollbar_name, std_view_to_godot_string(sprite->get_type()), + std_view_to_godot_string(sprite->get_base_type()), std_view_to_godot_string(GFX::TextureSprite::get_type_static()) + )); + texture = GFXSpriteTexture::make_gfx_sprite_texture(texture_sprite); + ERR_FAIL_NULL_V_MSG(texture, false, vformat( + "Failed to make GFXSpriteTexture from %s element %s for GUIScrollbar %s!", target, element_name, gui_scrollbar_name + )); + if constexpr (std::is_same_v<_Element, GUI::Button>) { + using enum GFXButtonStateTexture::ButtonState; + for (GFXButtonStateTexture::ButtonState state : { HOVER, PRESSED }) { + ERR_FAIL_NULL_V_MSG(texture->get_button_state_texture(state), false, vformat( + "Failed to generate %s texture for %s element %s for GUIScrollbar %s!", + GFXButtonStateTexture::button_state_to_theme_name(state), target, element_name, gui_scrollbar_name + )); + } + } + return true; + }; + + static const String SLIDER_NAME = "slider"; + static const String TRACK_NAME = "track"; + static const String LESS_NAME = "less"; + static const String MORE_NAME = "more"; + static const String RANGE_LIMIT_MIN_NAME = "range limit min"; + static const String RANGE_LIMIT_MAX_NAME = "range limit max"; + + ret &= set_texture(SLIDER_NAME, gui_scrollbar->get_slider_button(), slider_texture); + ret &= set_texture(TRACK_NAME, gui_scrollbar->get_track_button(), track_texture); + ret &= set_texture(LESS_NAME, gui_scrollbar->get_less_button(), less_texture); + ret &= set_texture(MORE_NAME, gui_scrollbar->get_more_button(), more_texture); + if (range_limited) { + ret &= set_texture(RANGE_LIMIT_MIN_NAME, gui_scrollbar->get_range_limit_min_icon(), range_limit_min_texture); + ret &= set_texture(RANGE_LIMIT_MAX_NAME, gui_scrollbar->get_range_limit_max_icon(), range_limit_max_texture); + } else { + range_limit_min_texture.unref(); + range_limit_max_texture.unref(); + } + + _calculate_rects(); + + fixed_point_t step_size = gui_scrollbar->get_step_size(); + if (step_size <= 0) { + UtilityFunctions::push_error( + "Invalid step size ", std_to_godot_string(step_size.to_string()), " for GUIScrollbar ", gui_scrollbar_name, + " - not positive! Defaulting to 1." + ); + step_size = 1; + ret = false; + } + min_value = gui_scrollbar->get_min_value() / step_size; + max_value = gui_scrollbar->get_max_value() / step_size; + + ret &= _constrain_limits() == OK; + ret &= reset() == OK; + + return ERR(ret); +} + +Error GUIScrollbar::set_gui_scrollbar_name(String const& gui_scene, String const& gui_scrollbar_name) { + if (gui_scene.is_empty() && gui_scrollbar_name.is_empty()) { + return set_gui_scrollbar(nullptr); + } + ERR_FAIL_COND_V_MSG(gui_scene.is_empty() || gui_scrollbar_name.is_empty(), FAILED, "GUI scene or scrollbar name is empty!"); + + GUI::Element const* gui_element = UITools::get_gui_element(gui_scene, gui_scrollbar_name); + ERR_FAIL_NULL_V(gui_element, FAILED); + GUI::Scrollbar const* new_gui_scrollbar = gui_element->cast_to(); + ERR_FAIL_NULL_V(new_gui_scrollbar, FAILED); + return set_gui_scrollbar(new_gui_scrollbar); +} + +String GUIScrollbar::get_gui_scrollbar_name() const { + return gui_scrollbar != nullptr ? std_view_to_godot_string(gui_scrollbar->get_name()) : String {}; +} + +void GUIScrollbar::set_value(int32_t new_value, bool signal) { + const int32_t old_value = value; + value = new_value; + _constrain_value(); + if (signal && value != old_value) { + emit_value_changed(); + } +} + +float GUIScrollbar::get_value_as_ratio() const { + return _value_to_ratio(value); +} + +void GUIScrollbar::set_value_as_ratio(float new_ratio, bool signal) { + set_value(min_value + (max_value - min_value) * new_ratio, signal); +} + +Error GUIScrollbar::set_range_limits(int32_t new_range_limit_min, int32_t new_range_limit_max, bool signal) { + ERR_FAIL_COND_V_MSG(!range_limited, FAILED, "Cannot set range limits of non-range-limited GUIScrollbar!"); + range_limit_min = new_range_limit_min; + range_limit_max = new_range_limit_max; + const Error err = _constrain_range_limits(); + set_value(value, signal); + return err; +} + +Error GUIScrollbar::set_limits(int32_t new_min_value, int32_t new_max_value, bool signal) { + min_value = new_min_value; + max_value = new_max_value; + bool ret = _constrain_limits() == OK; + ret &= _constrain_range_limits() == OK; + set_value(value, signal); + return ERR(ret); +} + +void GUIScrollbar::_gui_input(Ref const& event) { + ERR_FAIL_NULL(event); + + Ref mb = event; + + if (mb.is_valid()) { + if (mb->get_button_index() == MouseButton::MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (less_rect.has_point(mb->get_position())) { + pressed_less = true; + _start_button_change(mb->is_shift_pressed(), mb->is_ctrl_pressed()); + } else if (more_rect.has_point(mb->get_position())) { + pressed_more = true; + _start_button_change(mb->is_shift_pressed(), mb->is_ctrl_pressed()); + } else if (slider_rect.has_point(mb->get_position())) { + pressed_slider = true; + } else if (track_rect.has_point(mb->get_position())) { + pressed_track = true; + const real_t click_pos = mb->get_position()[orientation == HORIZONTAL ? 0 : 1]; + set_value_as_ratio((click_pos - slider_start) / slider_distance); + } else { + return; + } + } else { + if (pressed_less) { + _stop_button_change(); + pressed_less = false; + } else if (pressed_more) { + _stop_button_change(); + pressed_more = false; + } else if (pressed_slider) { + pressed_slider = false; + } else if (pressed_track) { + pressed_track = false; + } else { + return; + } + } + queue_redraw(); + } + return; + } + + Ref mm = event; + + if (mm.is_valid()) { + if (pressed_track) { + /* Switch to moving the slider if a track click is held and moved. */ + pressed_track = false; + pressed_slider = true; + } + if (pressed_slider) { + const real_t click_pos = mm->get_position()[orientation == HORIZONTAL ? 0 : 1]; + set_value_as_ratio((click_pos - slider_start) / slider_distance); + } + + if (hover_slider != slider_rect.has_point(mm->get_position())) { + hover_slider = !hover_slider; + queue_redraw(); + } + if (hover_track != track_rect.has_point(mm->get_position())) { + hover_track = !hover_track; + queue_redraw(); + } + if (hover_less != less_rect.has_point(mm->get_position())) { + hover_less = !hover_less; + queue_redraw(); + } + if (hover_more != more_rect.has_point(mm->get_position())) { + hover_more = !hover_more; + queue_redraw(); + } + return; + } +} + +void GUIScrollbar::_notification(int what) { + switch (what) { + case NOTIFICATION_VISIBILITY_CHANGED: + case NOTIFICATION_MOUSE_EXIT: { + hover_slider = false; + hover_track = false; + hover_less = false; + hover_more = false; + queue_redraw(); + } break; + + /* Pressing (and holding) less and more buttons. */ + case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { + const double delta = get_physics_process_delta_time(); + + button_change_accelerate_timer -= delta; + while (button_change_accelerate_timer <= 0.0) { + button_change_accelerate_timer += BUTTON_CHANGE_ACCELERATE_DELAY; + button_change_value += button_change_value_base; + } + + button_change_timer -= delta; + while (button_change_timer <= 0.0) { + button_change_timer += BUTTON_CHANGE_DELAY; + if (!_update_button_change()) { + set_physics_process_internal(false); + } + } + } break; + + case NOTIFICATION_DRAW: { + const RID ci = get_canvas_item(); + + using enum GFXButtonStateTexture::ButtonState; + + if (less_texture.is_valid()) { + Ref less_state_texture; + if (pressed_less) { + less_state_texture = less_texture->get_button_state_texture(PRESSED); + } else if (hover_less) { + less_state_texture = less_texture->get_button_state_texture(HOVER); + } + if (less_state_texture.is_null()) { + less_state_texture = less_texture; + } + less_state_texture->draw_rect(ci, less_rect, false); + } + + if (more_texture.is_valid()) { + Ref more_state_texture; + if (pressed_more) { + more_state_texture = more_texture->get_button_state_texture(PRESSED); + } else if (hover_more) { + more_state_texture = more_texture->get_button_state_texture(HOVER); + } + if (more_state_texture.is_null()) { + more_state_texture = more_texture; + } + more_state_texture->draw_rect(ci, more_rect, false); + } + + if (track_texture.is_valid()) { + Ref track_state_texture; + if (pressed_track) { + track_state_texture = track_texture->get_button_state_texture(PRESSED); + } else if (hover_track) { + track_state_texture = track_texture->get_button_state_texture(HOVER); + } + if (track_state_texture.is_null()) { + track_state_texture = track_texture; + } + track_state_texture->draw_rect_cornered(ci, track_rect); + } + + if (slider_texture.is_valid()) { + Ref slider_state_texture; + if (pressed_slider) { + slider_state_texture = slider_texture->get_button_state_texture(PRESSED); + } else if (hover_slider) { + slider_state_texture = slider_texture->get_button_state_texture(HOVER); + } + if (slider_state_texture.is_null()) { + slider_state_texture = slider_texture; + } + slider_state_texture->draw_rect(ci, slider_rect, false); + } + + if (range_limited) { + if (range_limit_min != min_value && range_limit_min_texture.is_valid()) { + range_limit_min_texture->draw_rect(ci, range_limit_min_rect, false); + } + + if (range_limit_max != max_value && range_limit_max_texture.is_valid()) { + range_limit_max_texture->draw_rect(ci, range_limit_max_rect, false); + } + } + } break; + } +} diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp new file mode 100644 index 0000000..7af7b57 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include + +#include + +#include "openvic-extension/classes/GFXSpriteTexture.hpp" + +namespace OpenVic { + class GUIScrollbar : public godot::Control { + GDCLASS(GUIScrollbar, godot::Control) + + static godot::StringName const& _signal_value_changed(); + + GUI::Scrollbar const* PROPERTY(gui_scrollbar); + + godot::Ref slider_texture; + godot::Ref track_texture; + godot::Ref less_texture; + godot::Ref more_texture; + + godot::Rect2 slider_rect; + real_t slider_start, slider_distance; + godot::Rect2 track_rect; + godot::Rect2 less_rect; + godot::Rect2 more_rect; + + godot::Ref range_limit_min_texture; + godot::Ref range_limit_max_texture; + + godot::Rect2 range_limit_min_rect; + godot::Rect2 range_limit_max_rect; + + godot::Orientation PROPERTY(orientation); + + int32_t PROPERTY(value); + int32_t PROPERTY(min_value); + int32_t PROPERTY(max_value); + + bool PROPERTY_CUSTOM_PREFIX(range_limited, is); + int32_t PROPERTY(range_limit_min); + int32_t PROPERTY(range_limit_max); + + bool hover_slider, hover_track, hover_less, hover_more; + bool pressed_slider, pressed_track, pressed_less, pressed_more; + + /* The time between value changes while the less/more button is held down (in seconds). */ + static constexpr double BUTTON_CHANGE_DELAY = 1.0 / 60.0; + /* The time between value change rate increases while the less/more button is held down (in seconds). */ + static constexpr double BUTTON_CHANGE_ACCELERATE_DELAY = 10.0 * BUTTON_CHANGE_DELAY; + + /* When the less/more button is pressed, button_change_value_base is initialised based on the shift and control keys + * and button_change_value is initialised to 0. Every BUTTON_CHANGE_ACCELERATE_DELAY seconds button_change_value + * increases by button_change_value_base, and every BUTTON_CHANGE_DELAY seconds value changes by button_change_value. + * If button_change_value is still 0 when the button is released, then value will be increased by + * button_change_value_base so that short clicks still have an effect.*/ + int32_t button_change_value_base, button_change_value; + double button_change_accelerate_timer, button_change_timer; + + void _start_button_change(bool shift_pressed, bool control_pressed); + void _stop_button_change(); + + /* Changes value by button_change_value with the direction determined by orientation and pressed_less or pressed_more. + * Returns true if a change occured, otherwise false. */ + bool _update_button_change(); + + float _value_to_ratio(int32_t val) const; + + void _calculate_rects(); + + void _constrain_value(); + godot::Error _constrain_range_limits(); + godot::Error _constrain_limits(); + + protected: + static void _bind_methods(); + void _notification(int what); + + public: + GUIScrollbar(); + + godot::Vector2 _get_minimum_size() const override; + void _gui_input(godot::Ref const& event) override; + + void emit_value_changed(); + godot::Error reset(); + void clear(); + + godot::Error set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar); + godot::Error set_gui_scrollbar_name(godot::String const& gui_scene, godot::String const& gui_scrollbar_name); + godot::String get_gui_scrollbar_name() const; + + void set_value(int32_t new_value, bool signal = true); + + float get_value_as_ratio() const; + void set_value_as_ratio(float new_ratio, bool signal = true); + + godot::Error set_range_limits(int32_t new_range_limit_min, int32_t new_range_limit_max, bool signal = true); + godot::Error set_limits(int32_t new_min_value, int32_t new_max_value, bool signal = true); + }; +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 431de02..1d503d7 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -8,6 +8,7 @@ #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIScrollbar.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -44,13 +45,20 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { Engine::get_singleton()->register_singleton("AssetManager", AssetManager::get_singleton()); ClassDB::register_class(); + ClassDB::register_abstract_class(); + + /* Depend on GFXCorneredTileSupportingTexture */ ClassDB::register_class(); ClassDB::register_abstract_class(); + + /* Depend on GFXButtonStateHavingTexture */ ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index f9a6b9d..9b8abce 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -72,8 +72,6 @@ void GameSingleton::_bind_methods() { OV_BIND_METHOD(GameSingleton::get_slave_pop_icon_index); OV_BIND_METHOD(GameSingleton::get_administrative_pop_icon_index); OV_BIND_METHOD(GameSingleton::get_rgo_owner_pop_icon_index); - OV_BIND_SMETHOD(int_to_formatted_string, { "val" }); - OV_BIND_SMETHOD(float_to_formatted_string, { "val" }); OV_BIND_METHOD(GameSingleton::set_paused, { "paused" }); OV_BIND_METHOD(GameSingleton::toggle_paused); @@ -434,14 +432,6 @@ int32_t GameSingleton::get_rgo_owner_pop_icon_index() const { return sprite; } -String GameSingleton::int_to_formatted_string(int64_t val) { - return Utilities::int_to_formatted_string(val); -} - -String GameSingleton::float_to_formatted_string(float val) { - return Utilities::float_to_formatted_string(val); -} - void GameSingleton::set_paused(bool paused) { game_manager.get_simulation_clock().set_paused(paused); } diff --git a/extension/src/openvic-extension/singletons/GameSingleton.hpp b/extension/src/openvic-extension/singletons/GameSingleton.hpp index 57a2014..35f7437 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.hpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.hpp @@ -105,8 +105,6 @@ namespace OpenVic { int32_t get_slave_pop_icon_index() const; int32_t get_administrative_pop_icon_index() const; int32_t get_rgo_owner_pop_icon_index() const; - static godot::String int_to_formatted_string(int64_t val); - static godot::String float_to_formatted_string(float val); void set_paused(bool paused); void toggle_paused(); diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index a3e70b5..9cd5db0 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -16,6 +16,7 @@ #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" +#include "openvic-extension/classes/GUIScrollbar.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -133,6 +134,8 @@ static bool generate_icon(generate_gui_args_t&& args) { 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)); + godot_texture_rect->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + GFX::IconTextureSprite const* texture_sprite = icon.get_sprite()->cast_to(); Ref texture = GFXSpriteTexture::make_gfx_sprite_texture(texture_sprite, icon.get_frame()); if (texture.is_valid()) { @@ -461,6 +464,7 @@ static bool generate_overlapping_elements(generate_gui_args_t&& args) { box, false, vformat("Failed to create GUIOverlappingElementsBox for GUI overlapping elements %s", overlapping_elements_name) ); + box->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); ret &= box->set_gui_overlapping_elements_box(&overlapping_elements) == OK; args.result = box; return ret; @@ -503,7 +507,21 @@ static bool generate_texteditbox(generate_gui_args_t&& args) { } static bool generate_scrollbar(generate_gui_args_t&& args) { - return generate_placeholder(std::move(args), { 1.0f, 0.0f, 0.0f, 0.3f }); + GUI::Scrollbar const& scrollbar = static_cast(args.element); + + const String scrollbar_name = std_view_to_godot_string(scrollbar.get_name()); + + GUIScrollbar* gui_scrollbar = nullptr; + bool ret = new_control(gui_scrollbar, scrollbar, args.name); + ERR_FAIL_NULL_V_MSG(gui_scrollbar, false, vformat("Failed to create GUIScrollbar for GUI scrollbar %s", scrollbar_name)); + + if (gui_scrollbar->set_gui_scrollbar(&scrollbar) != OK) { + UtilityFunctions::push_error("Error initialising GUIScrollbar for GUI scrollbar ", scrollbar_name); + ret = false; + } + + args.result = gui_scrollbar; + return ret; } /* Forward declaration for use in generate_window. */ diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 5957b70..7450212 100644 --- a/extension/src/openvic-extension/utility/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -36,9 +36,8 @@ String Utilities::int_to_formatted_string(int64_t val) { return (negative ? "-" : "") + String::num_int64(val); } -/* Float to formatted to 4 decimal place string. */ -String Utilities::float_to_formatted_string(float val) { - static constexpr int64_t decimal_places = 4; +/* Float to string formatted with the specified number of decimal places. */ +String Utilities::float_to_formatted_string(float val, int32_t decimal_places) { return String::num(val, decimal_places).pad_decimals(decimal_places); } diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 9b45abc..f39be3e 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -33,7 +33,7 @@ namespace OpenVic::Utilities { godot::String int_to_formatted_string(int64_t val); - godot::String float_to_formatted_string(float val); + godot::String float_to_formatted_string(float val, int32_t decimal_places); godot::String date_to_formatted_string(Date date); diff --git a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd index 7158333..0717c60 100644 --- a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd @@ -15,6 +15,20 @@ func _ready() -> void: if close_button: close_button.pressed.connect(Events.NationManagementScreens.close_nation_management_screen.bind(_screen)) + # Scrollbar test code + var test_scrollbar : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_0_slider") + var test_label : Label = get_label_from_nodepath(^"./country_budget/tax_0_inc") + test_scrollbar.value_changed.connect(func(value : int) -> void: test_label.text = str(value)) + test_scrollbar.set_range_limits(20, 80) + test_scrollbar.emit_value_changed() + + var tariff_scrollbar : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tariff_slider") + var tariff_label : Label = get_label_from_nodepath(^"./country_budget/tariffs_percent") + tariff_scrollbar.value_changed.connect(func(value : int) -> void: tariff_label.text = "%s%%" % GUINode.float_to_formatted_string(value, 1)) + tariff_scrollbar.set_limits(-100, 100) + tariff_scrollbar.set_range_limits(-45, 80) + tariff_scrollbar.emit_value_changed() + _update_info() func _notification(what : int) -> void: diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd index bf3c3dc..32f9600 100644 --- a/game/src/Game/GameSession/ProvinceOverviewPanel.gd +++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd @@ -309,14 +309,14 @@ func _update_info() -> void: if _rgo_income_label: # TODO - add £ sign and replace placeholder with actual value - _rgo_income_label.text = GameSingleton.float_to_formatted_string(12.34567) + _rgo_income_label.text = GUINode.float_to_formatted_string(12.34567, 3) if _rgo_employment_percentage_texture: pass if _rgo_employment_population_label: # TODO - replace placeholder with actual value - _rgo_employment_population_label.text = GameSingleton.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0) / 10) + _rgo_employment_population_label.text = GUINode.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0) / 10) if _rgo_employment_percentage_label: pass @@ -331,7 +331,7 @@ func _update_info() -> void: pass if _total_population_label: - _total_population_label.text = GameSingleton.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0)) + _total_population_label.text = GUINode.int_to_formatted_string(_province_info.get(_province_info_total_population_key, 0)) if _migration_label: pass -- cgit v1.2.3-56-ga3b1