From f8af17618e9f24dd6833ecaab64bf968cca2ec15 Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 20 Mar 2024 00:11:22 +0000 Subject: Label padding, progressbar range and piechart slice order tweaks --- .../classes/GFXPieChartTexture.cpp | 40 +++++++++++++++++----- .../classes/GFXPieChartTexture.hpp | 10 +++--- .../src/openvic-extension/utility/UITools.cpp | 14 ++++++-- 3 files changed, 49 insertions(+), 15 deletions(-) (limited to 'extension/src') diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp index f4c7851..10a2cb5 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp @@ -23,7 +23,7 @@ StringName const& GFXPieChartTexture::_slice_weight_key() { return slice_weight_key; } -static constexpr float PI = std::numbers::pi_v; +static constexpr float TWO_PI = 2.0f * std::numbers::pi_v; Error GFXPieChartTexture::_generate_pie_chart_image() { ERR_FAIL_NULL_V(gfx_pie_chart, FAILED); @@ -31,40 +31,57 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { gfx_pie_chart->get_size() <= 0, FAILED, vformat("Invalid GFX::PieChart size for GFXPieChartTexture - %d", gfx_pie_chart->get_size()) ); + const int32_t pie_chart_size = 2 * gfx_pie_chart->get_size(); + /* Whether we've already set the ImageTexture to an image of the right dimensions, * and so can update it without creating and setting a new image, or not. */ const bool can_update = pie_chart_image.is_valid() && pie_chart_image->get_width() == pie_chart_size && pie_chart_image->get_height() == pie_chart_size; + if (!can_update) { pie_chart_image = Image::create(pie_chart_size, pie_chart_size, false, Image::FORMAT_RGBA8); ERR_FAIL_NULL_V(pie_chart_image, FAILED); } static const Color background_colour { 0.0f, 0.0f, 0.0f, 0.0f }; + if (!slices.empty()) { const float pie_chart_radius = gfx_pie_chart->get_size(); + const Vector2 centre_translation = Vector2 { 0.5f, 0.5f } - static_cast(pie_chart_image->get_size()) * 0.5f; + for (Vector2i point { 0, 0 }; point.y < pie_chart_image->get_height(); ++point.y) { + for (point.x = 0; point.x < pie_chart_image->get_width(); ++point.x) { + const Vector2 offset = centre_translation + point; + if (offset.length() <= pie_chart_radius) { - float theta = 0.5f * PI + atan2(offset.y, offset.x); + + /* Calculate the anti-clockwise angle between the point and the centre of the image. + * The y coordinate is negated as the image coordinate system's y increases downwards. */ + float theta = atan2(-offset.y, offset.x); if (theta < 0.0f) { - theta += 2.0f * PI; + theta += TWO_PI; } + /* Rescale angle so that total_weight is a full rotation. */ - theta *= total_weight / (2.0f * PI); + theta *= total_weight / TWO_PI; + + /* Default to the first colour in case theta never reaches 0 due to floating point inaccuracy. */ Color colour = slices.front().first; + /* Find the slice theta lies in. */ for (slice_t const& slice : slices) { - if (theta <= slice.second) { + theta -= slice.second; + + if (theta <= 0.0f) { colour = slice.first; break; - } else { - theta -= slice.second; } } + pie_chart_image->set_pixelv(point, colour); } else { pie_chart_image->set_pixelv(point, background_colour); @@ -72,7 +89,9 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { } } } else { + pie_chart_image->fill(background_colour); + } if (can_update) { @@ -80,10 +99,11 @@ Error GFXPieChartTexture::_generate_pie_chart_image() { } else { set_image(pie_chart_image); } + return OK; } -Error GFXPieChartTexture::set_slices_array(TypedArray const& new_slices) { +Error GFXPieChartTexture::set_slices_array(godot_pie_chart_data_t const& new_slices) { slices.clear(); total_weight = 0.0f; for (int32_t i = 0; i < new_slices.size(); ++i) { @@ -92,7 +112,9 @@ Error GFXPieChartTexture::set_slices_array(TypedArray const& new_sli !slice_dict.has(_slice_colour_key()) || !slice_dict.has(_slice_weight_key()), vformat("Invalid slice keys at index %d", i) ); slice_t slice = std::make_pair(slice_dict[_slice_colour_key()], slice_dict[_slice_weight_key()]); - ERR_CONTINUE_MSG(slice.second <= 0.0f, vformat("Invalid slice values at index %d", i)); + ERR_CONTINUE_MSG( + slice.second <= 0.0f, vformat("Invalid slice values at index %d \"%s\"", i, slice_dict[_slice_identifier_key()]) + ); total_weight += slice.second; slices.emplace_back(std::move(slice)); } diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp index f8279e4..abeca1e 100644 --- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp @@ -29,15 +29,17 @@ namespace OpenVic { public: GFXPieChartTexture(); + using godot_pie_chart_data_t = godot::TypedArray; + /* Set slices given an Array of Dictionaries, each with the following key-value entries: * - colour: Color * - weight: float */ - godot::Error set_slices_array(godot::TypedArray const& new_slices); + godot::Error set_slices_array(godot_pie_chart_data_t const& new_slices); /* Generate slice data from a distribution of HasIdentifierAndColour derived objects, sorted by their weight. * The resulting Array of Dictionaries can be used as an argument for set_slices_array. */ template T> - static godot::TypedArray distribution_to_slices_array(fixed_point_map_t const& dist) { + static godot_pie_chart_data_t distribution_to_slices_array(fixed_point_map_t const& dist) { using namespace godot; using entry_t = std::pair; std::vector sorted_dist; @@ -49,9 +51,9 @@ namespace OpenVic { sorted_dist.push_back(entry); } std::sort(sorted_dist.begin(), sorted_dist.end(), [](entry_t const& lhs, entry_t const& rhs) -> bool { - return lhs.second < rhs.second; + return lhs.first < rhs.first; }); - TypedArray array; + godot_pie_chart_data_t array; for (auto const& [key, val] : sorted_dist) { Dictionary sub_dict; sub_dict[_slice_identifier_key()] = Utilities::std_view_to_godot_string(key->get_identifier()); diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index c00b64d..23dcced 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -177,8 +177,13 @@ static bool generate_icon(generate_gui_args_t&& args) { godot_progress_bar, false, vformat("Failed to create TextureProgressBar for GUI icon %s", icon_name) ); + static constexpr double MIN_VALUE = 0.0, MAX_VALUE = 1.0; + static constexpr uint32_t STEPS = 100; + godot_progress_bar->set_nine_patch_stretch(true); - godot_progress_bar->set_max(1.0); + godot_progress_bar->set_step((MAX_VALUE - MIN_VALUE) / STEPS); + godot_progress_bar->set_min(MIN_VALUE); + godot_progress_bar->set_max(MAX_VALUE); GFX::ProgressBar const* progress_bar = icon.get_sprite()->cast_to(); @@ -435,7 +440,12 @@ static bool generate_text(generate_gui_args_t&& args) { ERR_FAIL_NULL_V_MSG(godot_label, false, vformat("Failed to create Label for GUI text %s", text_name)); godot_label->set_text(std_view_to_godot_string(text.get_text())); - godot_label->set_custom_minimum_size(Utilities::to_godot_fvec2(text.get_max_size())); + + static const Vector2 default_padding { 1.0f, 0.0f }; + const Vector2 border_size = Utilities::to_godot_fvec2(text.get_border_size()) + default_padding; + const Vector2 max_size = Utilities::to_godot_fvec2(text.get_max_size()); + godot_label->set_position(godot_label->get_position() + border_size); + godot_label->set_custom_minimum_size(max_size - 2 * border_size); using enum GUI::AlignedElement::format_t; static const ordered_map format_map { -- cgit v1.2.3-56-ga3b1 From 8299a896d7658ffe86272ea0b21b8e5f7c600510 Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 20 Mar 2024 22:48:47 +0000 Subject: Add selected button state --- .../classes/GFXButtonStateTexture.cpp | 44 ++++++++++++++-------- .../classes/GFXButtonStateTexture.hpp | 5 ++- .../src/openvic-extension/classes/GUIScrollbar.cpp | 2 +- .../src/openvic-extension/utility/UITools.cpp | 4 +- 4 files changed, 35 insertions(+), 20 deletions(-) (limited to 'extension/src') diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp index 7cbc0a6..f6f3c22 100644 --- a/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.cpp @@ -39,14 +39,15 @@ void GFXButtonStateTexture::_bind_methods() { OV_BIND_METHOD(GFXButtonStateTexture::set_button_state, { "new_button_state" }); OV_BIND_METHOD(GFXButtonStateTexture::get_button_state); - OV_BIND_SMETHOD(button_state_to_theme_name, { "button_state" }); - OV_BIND_METHOD(GFXButtonStateTexture::get_button_state_theme); + OV_BIND_SMETHOD(button_state_to_name, { "button_state" }); + OV_BIND_METHOD(GFXButtonStateTexture::get_button_state_name); OV_BIND_METHOD(GFXButtonStateTexture::generate_state_image, { "source_image" }); BIND_ENUM_CONSTANT(HOVER); BIND_ENUM_CONSTANT(PRESSED); BIND_ENUM_CONSTANT(DISABLED); + BIND_ENUM_CONSTANT(SELECTED); } GFXButtonStateTexture::GFXButtonStateTexture() : button_state { HOVER }, state_image {}, state_texture {} {} @@ -67,7 +68,10 @@ Ref GFXButtonStateTexture::make_gfx_button_state_texture( } void GFXButtonStateTexture::set_button_state(ButtonState new_button_state) { - ERR_FAIL_COND(new_button_state != HOVER && new_button_state != PRESSED && new_button_state != DISABLED); + ERR_FAIL_COND( + new_button_state != HOVER && new_button_state != PRESSED && + new_button_state != DISABLED && new_button_state != SELECTED + ); button_state = new_button_state; } @@ -106,8 +110,15 @@ Error GFXButtonStateTexture::generate_state_image( const float luma = colour.get_luminance(); return { luma, luma, luma, colour.a }; }; + static constexpr auto selected_colour = [](Color const& colour) -> Color { + return { std::max(colour.r - 0.3f, 0.0f), std::max(colour.g - 0.3f, 0.0f), std::max(colour.b - 0.3f, 0.0f), colour.a }; + }; - const auto colour_func = button_state == HOVER ? hover_colour : button_state == PRESSED ? pressed_colour : disabled_colour; + const auto colour_func = + button_state == HOVER ? hover_colour : + button_state == PRESSED ? pressed_colour : + button_state == DISABLED ? disabled_colour : + selected_colour; for (Vector2i point { 0, 0 }; point.y < state_image->get_height(); ++point.y) { for (point.x = 0; point.x < state_image->get_width(); ++point.x) { @@ -123,25 +134,28 @@ Error GFXButtonStateTexture::generate_state_image( return OK; } -StringName const& GFXButtonStateTexture::button_state_to_theme_name(ButtonState button_state) { - static const StringName theme_name_hover = "hover"; - static const StringName theme_name_pressed = "pressed"; - static const StringName theme_name_disabled = "disabled"; - static const StringName theme_name_error = "INVALID BUTTON STATE"; +StringName const& GFXButtonStateTexture::button_state_to_name(ButtonState button_state) { + static const StringName name_hover = "hover"; + static const StringName name_pressed = "pressed"; + static const StringName name_disabled = "disabled"; + static const StringName name_selected = "selected"; + static const StringName name_error = "INVALID BUTTON STATE"; switch (button_state) { case HOVER: - return theme_name_hover; + return name_hover; case PRESSED: - return theme_name_pressed; + return name_pressed; case DISABLED: - return theme_name_disabled; + return name_disabled; + case SELECTED: + return name_selected; default: - return theme_name_error; + return name_error; } } -StringName const& GFXButtonStateTexture::get_button_state_theme() const { - return button_state_to_theme_name(button_state); +StringName const& GFXButtonStateTexture::get_button_state_name() const { + return button_state_to_name(button_state); } void GFXButtonStateHavingTexture::_bind_methods() { diff --git a/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp index b57ea46..d97ca2b 100644 --- a/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp +++ b/extension/src/openvic-extension/classes/GFXButtonStateTexture.hpp @@ -32,6 +32,7 @@ namespace OpenVic { HOVER, PRESSED, DISABLED, + SELECTED, BUTTON_STATE_COUNT }; @@ -62,8 +63,8 @@ namespace OpenVic { 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; + static godot::StringName const& button_state_to_name(ButtonState button_state); + godot::StringName const& get_button_state_name() const; }; class GFXButtonStateHavingTexture : public GFXCorneredTileSupportingTexture { diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp index 93eb00b..cdfa2fd 100644 --- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp +++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp @@ -394,7 +394,7 @@ Error GUIScrollbar::set_gui_scrollbar(GUI::Scrollbar const* new_gui_scrollbar) { 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 + GFXButtonStateTexture::button_state_to_name(state), target, element_name, gui_scrollbar_name )); } } diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index 23dcced..4af2b74 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -347,11 +347,11 @@ static bool generate_button(generate_gui_args_t&& args) { Ref button_state_texture = texture->get_button_state_texture(button_state); if (button_state_texture.is_valid()) { ret &= add_theme_stylebox( - godot_button, button_state_texture->get_button_state_theme(), button_state_texture + godot_button, button_state_texture->get_button_state_name(), button_state_texture ); } else { UtilityFunctions::push_error( - "Failed to make ", GFXButtonStateTexture::button_state_to_theme_name(button_state), + "Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state), " GFXButtonStateTexture for GUI button ", button_name ); ret = false; -- cgit v1.2.3-56-ga3b1 From 0cd976341792ea30ca41e09d9c238e4e342402cd Mon Sep 17 00:00:00 2001 From: hop311 Date: Wed, 20 Mar 2024 22:49:37 +0000 Subject: GUIListBox rework (internal scrollbar, fixed mode, no child data vector) --- .../src/openvic-extension/classes/GUIListBox.cpp | 169 +++++++++++++++------ .../src/openvic-extension/classes/GUIListBox.hpp | 25 +-- .../src/openvic-extension/classes/GUIScrollbar.cpp | 10 ++ .../src/openvic-extension/classes/GUIScrollbar.hpp | 2 + 4 files changed, 148 insertions(+), 58 deletions(-) (limited to 'extension/src') 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(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(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(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 const& event) { ERR_FAIL_NULL(event); + if (scrollbar == nullptr) { + return; + } + Ref 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 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(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 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); -- cgit v1.2.3-56-ga3b1