aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author hop311 <hop3114@gmail.com>2024-03-20 23:49:37 +0100
committer hop311 <hop3114@gmail.com>2024-03-20 23:49:37 +0100
commit0cd976341792ea30ca41e09d9c238e4e342402cd (patch)
treebb1e7fbe1e9f2c082ca82318911a09191455fd04
parent8299a896d7658ffe86272ea0b21b8e5f7c600510 (diff)
GUIListBox rework (internal scrollbar, fixed mode, no child data vector)pop-menu
-rw-r--r--extension/src/openvic-extension/classes/GUIListBox.cpp169
-rw-r--r--extension/src/openvic-extension/classes/GUIListBox.hpp25
-rw-r--r--extension/src/openvic-extension/classes/GUIScrollbar.cpp10
-rw-r--r--extension/src/openvic-extension/classes/GUIScrollbar.hpp2
4 files changed, 148 insertions, 58 deletions
diff --git a/extension/src/openvic-extension/classes/GUIListBox.cpp b/extension/src/openvic-extension/classes/GUIListBox.cpp
index f7b8d88..d04ab59 100644
--- a/extension/src/openvic-extension/classes/GUIListBox.cpp
+++ b/extension/src/openvic-extension/classes/GUIListBox.cpp
@@ -12,28 +12,52 @@ using namespace godot;
using OpenVic::Utilities::std_view_to_godot_string;
-Error GUIListBox::_calculate_child_arrangement() {
- ERR_FAIL_NULL_V(gui_listbox, FAILED);
+/* StringNames cannot be constructed until Godot has called StringName::setup(),
+ * so we must use wrapper functions to delay their initialisation. */
+StringName const& GUIListBox::_signal_scroll_index_changed() {
+ static const StringName signal_scroll_index_changed = "scroll_index_changed";
+ return signal_scroll_index_changed;
+}
- const int32_t child_count = get_child_count();
- const real_t max_height = get_size().height;
+Error GUIListBox::_calculate_max_scroll_index(bool signal) {
+ if (fixed) {
+ if (fixed_item_count <= 0) {
+ max_scroll_index = 0;
+ fixed_visible_items = 0;
+ } else if (fixed_item_height <= 0.0f) {
+ max_scroll_index = fixed_item_count - 1;
+ fixed_visible_items = max_scroll_index;
+ } else {
+ const real_t max_height = get_size().height;
- real_t height = 0.0f, height_under_max_scroll_index = 0.0f;
+ fixed_visible_items = max_height / fixed_item_height;
+ max_scroll_index = fixed_item_count - std::max(fixed_visible_items, 1);
+ }
+ } else {
+ const int32_t child_count = get_child_count();
- children_data.clear();
- max_scroll_index = 0;
+ if (child_count <= 0) {
+ max_scroll_index = 0;
+ } else {
+ const real_t max_height = get_size().height;
- 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 });
+ real_t height_under_max_scroll_index = 0.0f;
- height += child_height;
- height_under_max_scroll_index += child_height;
+ max_scroll_index = child_count;
- 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;
+ while (max_scroll_index > 0) {
+ max_scroll_index--;
+ Control* child = Object::cast_to<Control>(get_child(max_scroll_index));
+ if (child != nullptr) {
+ height_under_max_scroll_index += child->get_size().height; /* Spacing is ignored */
+
+ if (height_under_max_scroll_index > max_height) {
+ if (max_scroll_index + 1 < child_count) {
+ max_scroll_index++;
+ }
+ break;
+ }
+ }
}
}
}
@@ -41,28 +65,34 @@ Error GUIListBox::_calculate_child_arrangement() {
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);
+ set_scroll_index(scrollbar->get_value(), signal);
+
return OK;
}
Error GUIListBox::_update_child_positions() {
- ERR_FAIL_NULL_V(gui_listbox, FAILED);
+ const int32_t child_count = get_child_count();
+ const real_t max_height = get_size().height;
- if (children_data.empty()) {
- return OK;
- }
+ real_t height = 0.0f;
- const real_t max_height = get_size().height;
- const real_t scroll_pos = children_data[scroll_index].start_pos;
+ const int32_t child_scroll_index = fixed ? 0 : scroll_index;
- 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 });
+ for (int32_t index = 0; index < child_count; ++index) {
+ Control* child = Object::cast_to<Control>(get_child(index));
+
+ if (child != nullptr) {
+ if (index < child_scroll_index) {
+ child->hide();
+ } else {
+ child->set_position({ 0.0f, height });
+
+ height += child->get_size().height; /* Spacing is ignored */
+
+ child->set_visible(height <= max_height);
+ }
}
}
@@ -71,28 +101,36 @@ Error GUIListBox::_update_child_positions() {
void GUIListBox::_bind_methods() {
OV_BIND_METHOD(GUIListBox::clear);
- OV_BIND_METHOD(GUIListBox::clear_children);
+ OV_BIND_METHOD(GUIListBox::clear_children, { "remaining_child_count" }, DEFVAL(0));
OV_BIND_METHOD(GUIListBox::get_scroll_index);
- OV_BIND_METHOD(GUIListBox::set_scroll_index, { "new_scroll_index" });
+ OV_BIND_METHOD(GUIListBox::set_scroll_index, { "new_scroll_index", "signal" }, DEFVAL(true));
OV_BIND_METHOD(GUIListBox::get_max_scroll_index);
+ OV_BIND_METHOD(GUIListBox::is_fixed);
+ OV_BIND_METHOD(GUIListBox::get_fixed_item_count);
+ OV_BIND_METHOD(GUIListBox::get_fixed_visible_items);
+ OV_BIND_METHOD(GUIListBox::get_fixed_item_height);
+ OV_BIND_METHOD(GUIListBox::set_fixed, { "item_count", "item_height", "signal" }, DEFVAL(true));
+ OV_BIND_METHOD(GUIListBox::unset_fixed, { "signal" }, DEFVAL(true));
+
OV_BIND_METHOD(GUIListBox::get_gui_listbox_name);
OV_BIND_METHOD(GUIListBox::get_scrollbar);
+
+ ADD_SIGNAL(MethodInfo(_signal_scroll_index_changed(), PropertyInfo(Variant::INT, "value")));
}
void GUIListBox::_notification(int what) {
switch (what) {
case NOTIFICATION_SORT_CHILDREN: {
- _calculate_child_arrangement();
+ _calculate_max_scroll_index(!fixed);
} break;
}
}
GUIListBox::GUIListBox()
- : gui_listbox { nullptr }, scrollbar { nullptr }, children_data {}, scroll_index { 0 }, max_scroll_index { 0 } {
- set_clip_contents(true);
-}
+ : gui_listbox { nullptr }, scrollbar { nullptr }, scroll_index { 0 }, max_scroll_index { 0 },
+ fixed { false }, fixed_item_count { 0 }, fixed_visible_items { 0 }, fixed_item_height { 0.0f } {}
Vector2 GUIListBox::_get_minimum_size() const {
if (gui_listbox != nullptr) {
@@ -111,14 +149,18 @@ Vector2 GUIListBox::_get_minimum_size() const {
void GUIListBox::_gui_input(godot::Ref<godot::InputEvent> const& event) {
ERR_FAIL_NULL(event);
+ if (scrollbar == nullptr) {
+ return;
+ }
+
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);
+ scrollbar->decrement_value();
} else if (mb->get_button_index() == MouseButton::MOUSE_BUTTON_WHEEL_DOWN) {
- set_scroll_index(scroll_index + 1);
+ scrollbar->increment_value();
} else {
return;
}
@@ -129,9 +171,14 @@ void GUIListBox::_gui_input(godot::Ref<godot::InputEvent> const& event) {
void GUIListBox::clear() {
gui_listbox = nullptr;
- children_data.clear();
scroll_index = 0;
max_scroll_index = 0;
+
+ fixed = false;
+ fixed_item_count = 0;
+ fixed_visible_items = 0;
+ fixed_item_height = 0.0f;
+
clear_children();
if (scrollbar != nullptr) {
remove_child(scrollbar);
@@ -139,27 +186,55 @@ void GUIListBox::clear() {
}
}
-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);
- }
+void GUIListBox::clear_children(int32_t remaining_child_count) {
+ ERR_FAIL_COND(remaining_child_count < 0);
+
+ int32_t child_index = get_child_count();
+
+ while (child_index > remaining_child_count) {
+ remove_child(get_child(--child_index));
}
+
if (scrollbar != nullptr) {
scrollbar->set_value(0);
}
}
-void GUIListBox::set_scroll_index(int32_t new_scroll_index) {
+void GUIListBox::set_scroll_index(int32_t new_scroll_index, bool signal) {
+ const int32_t old_scroll_index = 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);
}
+
+ if (signal && scroll_index != old_scroll_index) {
+ emit_signal(_signal_scroll_index_changed(), scroll_index);
+ }
+
_update_child_positions();
}
+Error GUIListBox::set_fixed(int32_t item_count, real_t item_height, bool signal) {
+ fixed = true;
+
+ fixed_item_count = item_count;
+ fixed_item_height = item_height;
+
+ return _calculate_max_scroll_index(signal);
+}
+
+Error GUIListBox::unset_fixed(bool signal) {
+ ERR_FAIL_COND_V(!fixed, FAILED);
+
+ fixed = false;
+ fixed_item_count = 0;
+ fixed_item_height = 0.0f;
+
+ return _calculate_max_scroll_index(signal);
+}
+
Error GUIListBox::set_gui_listbox(GUI::ListBox const* new_gui_listbox) {
if (gui_listbox == new_gui_listbox) {
return OK;
@@ -192,7 +267,7 @@ Error GUIListBox::set_gui_listbox(GUI::ListBox const* new_gui_listbox) {
if (scrollbar_control != nullptr) {
scrollbar = Object::cast_to<GUIScrollbar>(scrollbar_control);
if (scrollbar != nullptr) {
- add_child(scrollbar);
+ add_child(scrollbar, false, INTERNAL_MODE_FRONT);
const Size2 size = Utilities::to_godot_fvec2(gui_listbox->get_size());
scrollbar->set_position({ size.width, 0.0f });
diff --git a/extension/src/openvic-extension/classes/GUIListBox.hpp b/extension/src/openvic-extension/classes/GUIListBox.hpp
index 9d49840..775feae 100644
--- a/extension/src/openvic-extension/classes/GUIListBox.hpp
+++ b/extension/src/openvic-extension/classes/GUIListBox.hpp
@@ -14,18 +14,18 @@ namespace OpenVic {
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();
+ bool PROPERTY_CUSTOM_PREFIX(fixed, is);
+ int32_t PROPERTY(fixed_item_count);
+ int32_t PROPERTY(fixed_visible_items);
+ real_t PROPERTY(fixed_item_height);
+
+ static godot::StringName const& _signal_scroll_index_changed();
+
+ godot::Error _calculate_max_scroll_index(bool signal);
godot::Error _update_child_positions();
protected:
@@ -42,10 +42,13 @@ namespace OpenVic {
/* Reset gui_listbox to nullptr, and remove all child elements. */
void clear();
- /* Remove all child elements except for the scrollbar. */
- void clear_children();
+ /* Remove child elements until there are remaining_child_count left excluding the scrollbar. */
+ void clear_children(int32_t remaining_child_count = 0);
+
+ void set_scroll_index(int32_t new_scroll_index, bool signal = true);
- void set_scroll_index(int32_t new_scroll_index);
+ godot::Error set_fixed(int32_t item_count, real_t item_height, bool signal = true);
+ godot::Error unset_fixed(bool signal = true);
/* Set the GUI::ListBox. This does not affect any existing child elements. */
godot::Error set_gui_listbox(GUI::ListBox const* new_gui_listbox);
diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp
index cdfa2fd..dab74d8 100644
--- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp
+++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp
@@ -36,6 +36,8 @@ void GUIScrollbar::_bind_methods() {
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::increment_value, { "signal" }, DEFVAL(true));
+ OV_BIND_METHOD(GUIScrollbar::decrement_value, { "signal" }, DEFVAL(true));
OV_BIND_METHOD(GUIScrollbar::set_value_as_ratio, { "new_ratio", "signal" }, DEFVAL(true));
OV_BIND_METHOD(GUIScrollbar::is_range_limited);
@@ -466,6 +468,14 @@ void GUIScrollbar::set_value(int32_t new_value, bool signal) {
}
}
+void GUIScrollbar::increment_value(bool signal) {
+ set_value(value + 1, signal);
+}
+
+void GUIScrollbar::decrement_value(bool signal) {
+ set_value(value - 1, signal);
+}
+
float GUIScrollbar::get_value_as_ratio() const {
return _value_to_ratio(value);
}
diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp
index c5b476a..16b2e00 100644
--- a/extension/src/openvic-extension/classes/GUIScrollbar.hpp
+++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp
@@ -93,6 +93,8 @@ namespace OpenVic {
godot::String get_gui_scrollbar_name() const;
void set_value(int32_t new_value, bool signal = true);
+ void increment_value(bool signal = true);
+ void decrement_value(bool signal = true);
float get_value_as_ratio() const;
void set_value_as_ratio(float new_ratio, bool signal = true);