Skip to content

Commit

Permalink
GUIListBox rework (internal scrollbar, fixed mode, no child data vector)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hop311 committed Mar 20, 2024
1 parent 8299a89 commit 0cd9763
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 58 deletions.
169 changes: 122 additions & 47 deletions extension/src/openvic-extension/classes/GUIListBox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,57 +12,87 @@ 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;
}
}
}
}
}

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);
}
}
}

Expand All @@ -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) {
Expand All @@ -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;
}
Expand All @@ -129,37 +171,70 @@ 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);
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);
}
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;
Expand Down Expand Up @@ -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 });
Expand Down
25 changes: 14 additions & 11 deletions extension/src/openvic-extension/classes/GUIListBox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions extension/src/openvic-extension/classes/GUIScrollbar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions extension/src/openvic-extension/classes/GUIScrollbar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 0cd9763

Please sign in to comment.