diff options
author | hop311 <hop3114@gmail.com> | 2024-03-03 14:40:20 +0100 |
---|---|---|
committer | hop311 <hop3114@gmail.com> | 2024-03-03 14:40:20 +0100 |
commit | 9ee1940ac3d15aa4c0a87b84d1c4ab8958184f63 (patch) | |
tree | 8c319b540020aaf382592c9b513a27e9fcaaa603 /extension/src | |
parent | d45d6270c8924f571b53d71ac8eb9ce5a7788255 (diff) |
Add GUIListBox + GUIScrollbar tweaksgui-listbox
Diffstat (limited to 'extension/src')
8 files changed, 364 insertions, 27 deletions
diff --git a/extension/src/openvic-extension/classes/GUIListBox.cpp b/extension/src/openvic-extension/classes/GUIListBox.cpp new file mode 100644 index 0000000..f7b8d88 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIListBox.cpp @@ -0,0 +1,226 @@ +#include "GUIListBox.hpp" + +#include <godot_cpp/classes/input_event_mouse_button.hpp> +#include <godot_cpp/variant/utility_functions.hpp> + +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace OpenVic; +using namespace godot; + +using OpenVic::Utilities::std_view_to_godot_string; + +Error GUIListBox::_calculate_child_arrangement() { + ERR_FAIL_NULL_V(gui_listbox, FAILED); + + const int32_t child_count = get_child_count(); + const real_t max_height = get_size().height; + + real_t height = 0.0f, height_under_max_scroll_index = 0.0f; + + children_data.clear(); + max_scroll_index = 0; + + for (int32_t index = 0; index < child_count; ++index) { + Control* child = Object::cast_to<Control>(get_child(index)); + if (child != nullptr && child != scrollbar && child->is_visible()) { + const real_t child_height = child->get_size().height; /* Spacing is ignored */ + children_data.push_back({ child, height, child_height }); + + height += child_height; + height_under_max_scroll_index += child_height; + + while (height_under_max_scroll_index > max_height && max_scroll_index + 1 < children_data.size()) { + height_under_max_scroll_index -= children_data[max_scroll_index++].height; + } + } + } + + ERR_FAIL_NULL_V(scrollbar, FAILED); + + scrollbar->set_limits(0, max_scroll_index, false); + scrollbar->emit_value_changed(); + scrollbar->set_visible(max_scroll_index > 0); + + return OK; +} + +Error GUIListBox::_update_child_positions() { + ERR_FAIL_NULL_V(gui_listbox, FAILED); + + if (children_data.empty()) { + return OK; + } + + const real_t max_height = get_size().height; + const real_t scroll_pos = children_data[scroll_index].start_pos; + + for (int32_t index = 0; index < children_data.size(); ++index) { + child_data_t const& data = children_data[index]; + if (index < scroll_index || data.start_pos + data.height > scroll_pos + max_height) { + data.child->set_position({ 0.0f, max_height + 10.0f }); + } else { + data.child->set_position({ 0.0f, data.start_pos - scroll_pos }); + } + } + + return OK; +} + +void GUIListBox::_bind_methods() { + OV_BIND_METHOD(GUIListBox::clear); + OV_BIND_METHOD(GUIListBox::clear_children); + + OV_BIND_METHOD(GUIListBox::get_scroll_index); + OV_BIND_METHOD(GUIListBox::set_scroll_index, { "new_scroll_index" }); + OV_BIND_METHOD(GUIListBox::get_max_scroll_index); + + OV_BIND_METHOD(GUIListBox::get_gui_listbox_name); + OV_BIND_METHOD(GUIListBox::get_scrollbar); +} + +void GUIListBox::_notification(int what) { + switch (what) { + case NOTIFICATION_SORT_CHILDREN: { + _calculate_child_arrangement(); + } break; + } +} + +GUIListBox::GUIListBox() + : gui_listbox { nullptr }, scrollbar { nullptr }, children_data {}, scroll_index { 0 }, max_scroll_index { 0 } { + set_clip_contents(true); +} + +Vector2 GUIListBox::_get_minimum_size() const { + if (gui_listbox != nullptr) { + Size2 size = Utilities::to_godot_fvec2(gui_listbox->get_size()); + + if (scrollbar != nullptr) { + size.width += scrollbar->get_minimum_size().width; + } + + return size; + } else { + return {}; + } +} + +void GUIListBox::_gui_input(godot::Ref<godot::InputEvent> const& event) { + ERR_FAIL_NULL(event); + + Ref<InputEventMouseButton> mb = event; + + if (mb.is_valid()) { + if (mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::MOUSE_BUTTON_WHEEL_UP) { + set_scroll_index(scroll_index - 1); + } else if (mb->get_button_index() == MouseButton::MOUSE_BUTTON_WHEEL_DOWN) { + set_scroll_index(scroll_index + 1); + } else { + return; + } + accept_event(); + } + } +} + +void GUIListBox::clear() { + gui_listbox = nullptr; + children_data.clear(); + scroll_index = 0; + max_scroll_index = 0; + clear_children(); + if (scrollbar != nullptr) { + remove_child(scrollbar); + scrollbar = nullptr; + } +} + +void GUIListBox::clear_children() { + int32_t child_count = get_child_count(); + while (child_count > 0) { + Node* child = get_child(--child_count); + if (child != scrollbar) { + remove_child(child); + } + } + if (scrollbar != nullptr) { + scrollbar->set_value(0); + } +} + +void GUIListBox::set_scroll_index(int32_t new_scroll_index) { + scroll_index = std::clamp(new_scroll_index, 0, max_scroll_index); + if (scrollbar != nullptr && scrollbar->get_value() != scroll_index) { + scrollbar->set_value(scroll_index, false); + } + _update_child_positions(); +} + +Error GUIListBox::set_gui_listbox(GUI::ListBox const* new_gui_listbox) { + if (gui_listbox == new_gui_listbox) { + return OK; + } + + if (new_gui_listbox == nullptr) { + clear(); + return OK; + } + + gui_listbox = new_gui_listbox; + + const String scrollbar_name = std_view_to_godot_string(gui_listbox->get_scrollbar_name()); + + Error err = OK; + + if (scrollbar_name.is_empty()) { + UtilityFunctions::push_error("GUIListBox ", get_name(), " has no scrollbar name!"); + err = FAILED; + scrollbar = nullptr; + } else { + static const String scrollbar_scene = "core"; + + Control* scrollbar_control = nullptr; + if (!UITools::generate_gui_element(scrollbar_scene, scrollbar_name, "", scrollbar_control)) { + UtilityFunctions::push_error("Error generating scrollbar ", scrollbar_name, " for GUIListBox ", get_name()); + err = FAILED; + } + + if (scrollbar_control != nullptr) { + scrollbar = Object::cast_to<GUIScrollbar>(scrollbar_control); + if (scrollbar != nullptr) { + add_child(scrollbar); + + const Size2 size = Utilities::to_godot_fvec2(gui_listbox->get_size()); + scrollbar->set_position({ size.width, 0.0f }); + scrollbar->set_length_override(size.height); + + static const StringName set_scroll_index_func_name = "set_scroll_index"; + + scrollbar->connect( + GUIScrollbar::signal_value_changed(), Callable { this, set_scroll_index_func_name }, CONNECT_PERSIST + ); + } else { + UtilityFunctions::push_error( + "Element ", scrollbar_name, " for GUIListBox ", get_name(), " is not a GUIScrollbar" + ); + err = FAILED; + } + } else { + scrollbar = nullptr; + } + } + + return err; +} + +String GUIListBox::get_gui_listbox_name() const { + return gui_listbox != nullptr ? std_view_to_godot_string(gui_listbox->get_name()) : String {}; +} + +GUIScrollbar* GUIListBox::get_scrollbar() const { + return scrollbar; +} diff --git a/extension/src/openvic-extension/classes/GUIListBox.hpp b/extension/src/openvic-extension/classes/GUIListBox.hpp new file mode 100644 index 0000000..9d49840 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUIListBox.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include <godot_cpp/classes/container.hpp> + +#include <openvic-simulation/interface/GUI.hpp> + +#include "openvic-extension/classes/GUIScrollbar.hpp" + +namespace OpenVic { + class GUIListBox : public godot::Container { + GDCLASS(GUIListBox, godot::Container) + + GUI::ListBox const* PROPERTY(gui_listbox); + + GUIScrollbar* scrollbar; + + struct child_data_t { + Control* child; + real_t start_pos, height; + }; + + std::vector<child_data_t> children_data; + + /* The children_data index of the topmost visible child element. */ + int32_t PROPERTY(scroll_index); + int32_t PROPERTY(max_scroll_index); + + godot::Error _calculate_child_arrangement(); + godot::Error _update_child_positions(); + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUIListBox(); + + godot::Vector2 _get_minimum_size() const override; + void _gui_input(godot::Ref<godot::InputEvent> const& event) override; + + /* Reset gui_listbox to nullptr, and remove all child elements. */ + void clear(); + + /* Remove all child elements except for the scrollbar. */ + void clear_children(); + + void set_scroll_index(int32_t new_scroll_index); + + /* Set the GUI::ListBox. This does not affect any existing child elements. */ + godot::Error set_gui_listbox(GUI::ListBox const* new_gui_listbox); + + /* Return the name of the GUI::ListBox, or an empty String if it's null. */ + godot::String get_gui_listbox_name() const; + + GUIScrollbar* get_scrollbar() const; + }; +} diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index ceff66e..73ebb0c 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -18,7 +18,8 @@ using namespace OpenVic; F(TextureProgressBar, progress_bar) \ F(TextureRect, texture_rect) \ F(GUIOverlappingElementsBox, gui_overlapping_elements_box) \ - F(GUIScrollbar, gui_scrollbar) + F(GUIScrollbar, gui_scrollbar) \ + F(GUIListBox, gui_listbox) #define APPLY_TO_TEXTURE_TYPES(F) \ F(GFXSpriteTexture, gfx_sprite_texture) \ diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index dc7f665..3dbe403 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -12,6 +12,7 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" @@ -43,6 +44,7 @@ namespace OpenVic { 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); + static GUIListBox* get_gui_listbox_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; @@ -52,6 +54,7 @@ namespace OpenVic { 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; + GUIListBox* get_gui_listbox_from_nodepath(godot::NodePath const& path) const; /* Helper functions to get textures from TextureRects and Buttons. */ static godot::Ref<godot::Texture2D> get_texture_from_node(godot::Node* node); diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp index 0f3cde1..93eb00b 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -16,7 +16,7 @@ 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() { +StringName const& GUIScrollbar::signal_value_changed() { static const StringName signal_value_changed = "value_changed"; return signal_value_changed; } @@ -44,7 +44,10 @@ void GUIScrollbar::_bind_methods() { 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"))); + OV_BIND_METHOD(GUIScrollbar::get_length_override); + OV_BIND_METHOD(GUIScrollbar::set_length_override, { "new_length_override" }); + + ADD_SIGNAL(MethodInfo(signal_value_changed(), PropertyInfo(Variant::INT, "value"))); } GUIScrollbar::GUIScrollbar() { @@ -220,24 +223,30 @@ void GUIScrollbar::_constrain_value() { /* _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; - } + if (range_limited) { + 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; + 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; + return err; + } else { + range_limit_min = min_value; + range_limit_max = max_value; + return OK; + } } /* _constrain_range_limits() should be called sometime after this. */ @@ -265,6 +274,10 @@ Vector2 GUIScrollbar::_get_minimum_size() const { size[axis] = std::max(size[axis], more_texture->get_size()[axis]); } + if (length_override > 0.0f) { + size[1 - axis] = length_override; + } + return size; } else { return {}; @@ -272,7 +285,7 @@ Vector2 GUIScrollbar::_get_minimum_size() const { } void GUIScrollbar::emit_value_changed() { - emit_signal(_signal_value_changed(), value); + emit_signal(signal_value_changed(), value); } Error GUIScrollbar::reset() { @@ -291,7 +304,7 @@ Error GUIScrollbar::reset() { pressed_less = false; pressed_more = false; - value = (max_value - min_value) / 2; + value = min_value; range_limit_min = min_value; range_limit_max = max_value; @@ -321,6 +334,7 @@ void GUIScrollbar::clear() { range_limit_max_rect = {}; orientation = HORIZONTAL; + length_override = 0.0f; min_value = 0; max_value = 100; range_limited = false; @@ -347,6 +361,7 @@ Error GUIScrollbar::set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar) { const String gui_scrollbar_name = std_view_to_godot_string(gui_scrollbar->get_name()); orientation = gui_scrollbar->is_horizontal() ? HORIZONTAL : VERTICAL; + length_override = 0.0f; range_limited = gui_scrollbar->is_range_limited(); /* _Element is either GUI::Button or GUI::Icon, both of which have their own @@ -477,6 +492,19 @@ Error GUIScrollbar::set_limits(int32_t new_min_value, int32_t new_max_value, boo return ERR(ret); } +void GUIScrollbar::set_length_override(real_t new_length_override) { + ERR_FAIL_COND_MSG( + length_override < 0, vformat("Invalid GUIScrollbar length override: %f - cannot be negative!", length_override) + ); + + length_override = new_length_override; + + _calculate_rects(); + _constrain_limits(); + _constrain_range_limits(); + _constrain_value(); +} + void GUIScrollbar::_gui_input(Ref<InputEvent> const& event) { ERR_FAIL_NULL(event); diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp index 7af7b57..c5b476a 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.hpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp @@ -11,8 +11,6 @@ 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<GFXSpriteTexture> slider_texture; @@ -33,6 +31,7 @@ namespace OpenVic { godot::Rect2 range_limit_max_rect; godot::Orientation PROPERTY(orientation); + real_t PROPERTY(length_override); int32_t PROPERTY(value); int32_t PROPERTY(min_value); @@ -78,6 +77,8 @@ namespace OpenVic { void _notification(int what); public: + static godot::StringName const& signal_value_changed(); + GUIScrollbar(); godot::Vector2 _get_minimum_size() const override; @@ -98,5 +99,8 @@ namespace OpenVic { 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); + + /* Override the main dimension of gui_scollbar's size with the specified length. */ + void set_length_override(real_t new_length_override); }; } diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 1d503d7..750cc53 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -6,6 +6,7 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" @@ -56,6 +57,7 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class<GFXMaskedFlagTexture>(); ClassDB::register_class<GFXPieChartTexture>(); + ClassDB::register_class<GUIListBox>(); ClassDB::register_class<GUINode>(); ClassDB::register_class<GUIOverlappingElementsBox>(); ClassDB::register_class<GUIScrollbar>(); diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index 3cc0d53..c00b64d 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -15,6 +15,7 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" #include "openvic-extension/singletons/AssetManager.hpp" @@ -485,6 +486,24 @@ static bool generate_overlapping_elements(generate_gui_args_t&& args) { return ret; } +static bool generate_listbox(generate_gui_args_t&& args) { + GUI::ListBox const& listbox = static_cast<GUI::ListBox const&>(args.element); + + const String listbox_name = std_view_to_godot_string(listbox.get_name()); + + GUIListBox* gui_listbox = nullptr; + bool ret = new_control(gui_listbox, listbox, args.name); + ERR_FAIL_NULL_V_MSG(gui_listbox, false, vformat("Failed to create GUIListBox for GUI listbox %s", listbox_name)); + + if (gui_listbox->set_gui_listbox(&listbox) != OK) { + UtilityFunctions::push_error("Error initialising GUIListBox for GUI listbox ", listbox_name); + ret = false; + } + + args.result = gui_listbox; + return ret; +} + template<std::derived_from<GUI::Element> T> requires requires(T const& element) { { element.get_size() } -> std::same_as<fvec2_t>; @@ -513,10 +532,6 @@ static bool generate_placeholder(generate_gui_args_t&& args, Color colour) { return ret; } -static bool generate_listbox(generate_gui_args_t&& args) { - return generate_placeholder<GUI::ListBox>(std::move(args), { 0.0f, 0.0f, 1.0f, 0.3f }); -} - static bool generate_texteditbox(generate_gui_args_t&& args) { return generate_placeholder<GUI::TextEditBox>(std::move(args), { 0.0f, 1.0f, 0.0f, 0.3f }); } |