rsx/overlays: Implement dropdown control

- The base widget and the popup are rendered separately
- The owner is responsible for placing the widget at the desired position for rendering
This commit is contained in:
kd-11 2026-03-15 20:08:53 +03:00 committed by kd-11
parent 281706fdae
commit bb652ed840
5 changed files with 277 additions and 3 deletions

View file

@ -191,6 +191,7 @@ namespace rsx
u16 margin_left = 0;
u16 margin_top = 0;
// NOTE: These two only apply for text. Containers maintain their own scroll values.
f32 horizontal_scroll_offset = 0.0f;
f32 vertical_scroll_offset = 0.0f;

View file

@ -216,11 +216,16 @@ namespace rsx
{
m_scroll_indicator_top->set_visible(!hidden);
m_scroll_indicator_bottom->set_visible(!hidden);
m_highlight_box->set_visible(!hidden);
refresh();
}
void list_view::hide_row_highliter(bool hidden)
{
m_highlight_box->set_visible(!hidden);
refresh();
}
void list_view::disable_selection_pulse(bool disabled)
{
m_highlight_box->pulse_effect_enabled = !disabled;
@ -275,6 +280,13 @@ namespace rsx
m_highlight_box->refresh();
}
void list_view::set_pos(s16 x, s16 y)
{
vertical_layout::set_pos(x, y);
update_selection();
}
compiled_resource& list_view::get_compiled()
{
if (is_compiled())

View file

@ -50,11 +50,13 @@ namespace rsx
void hide_prompt_buttons(bool hidden = true);
void hide_scroll_indicators(bool hidden = true);
void hide_row_highliter(bool hidden = false);
void disable_selection_pulse(bool disabled = true);
void set_cancel_only(bool cancel_only);
void translate(s16 _x, s16 _y) override;
void set_size(u16 w, u16 h) override;
void set_pos(s16 x, s16 y) override;
compiled_resource& get_compiled() override;
};

View file

@ -1 +1,225 @@
#include "stdafx.h"
#include "overlay_select.h"
namespace rsx::overlays
{
constexpr u16 max_dropdown_arrow_dimension = 12;
constexpr u16 dropdown_arrow_spacing = 4;
constexpr u16 dropdown_pack_padding = 6;
select_popup::select_popup(u16 w, u16 h)
: list_view(w, h, false)
{
hide_prompt_buttons();
hide_scroll_indicators();
pack_padding = dropdown_pack_padding;
}
select::select(u16 w, u16 h, const std::vector<std::string>& options)
{
std::vector<std::unique_ptr<overlay_element>> labels;
for (const auto& option : options)
{
std::unique_ptr<overlay_element> element = std::make_unique<overlays::label>();
labels.push_back(std::move(element));
auto label = dynamic_cast<overlays::label*>(labels.back().get());
label->set_padding(8, 0, 0, 0);
label->set_text(option);
label->set_font("Arial", 14);
label->set_padding(4);
label->back_color.a = 0.f;
label->auto_resize();
}
create_popup(labels);
set_padding(8, 10, 6, 12);
set_size(w, h);
select_item(0);
}
select::select(u16 w, u16 h, const std::vector<std::u32string>& options)
{
std::vector<std::unique_ptr<overlay_element>> labels;
for (const auto& option : options)
{
std::unique_ptr<overlay_element> element = std::make_unique<overlays::label>();
labels.push_back(std::move(element));
auto label = dynamic_cast<overlays::label*>(labels.back().get());
label->set_padding(8, 0, 0, 0);
label->set_unicode_text(option);
label->set_padding(4);
label->set_font("Arial", 14);
label->back_color.a = 0.f;
label->auto_resize();
}
create_popup(labels);
set_padding(8, 10, 6, 12);
set_size(w, h);
select_item(0);
}
select::select(u16 w, u16 h, std::vector<std::unique_ptr<overlay_element>>& options)
{
create_popup(options);
set_padding(8, 10, 6, 12);
set_size(w, h);
select_item(0);
}
void select::create_popup(std::vector<std::unique_ptr<overlay_element>>& options)
{
u16 max_w = 0;
u16 max_h = 0;
for (const auto& option : options)
{
max_w = std::max(max_w, option->w);
max_h += option->h + dropdown_pack_padding;
}
m_popup = std::make_unique<select_popup>(max_w, max_h + dropdown_pack_padding + 4);
m_popup->back_color = color4f(0.3f, 0.3f, 0.3f, 1.0f);
for (auto& option : options)
{
m_popup->add_entry(option);
}
}
void select::auto_resize()
{
u16 _h = max_dropdown_arrow_dimension; // Minimum arrow dimension
if (m_label)
{
_h = std::max(_h, m_label->h);
}
// Compute length of longest entry
u16 text_w = 0, text_h = 0;
for (auto& option : m_popup->m_items)
{
if (option->text.empty())
{
continue;
}
u16 tw = 0, th = 0;
option->measure_text(tw, th, true);
text_w = std::max<u16>(text_w, tw);
text_h = std::max<u16>(text_h, th);
}
_h = std::max<u16>(_h, text_h);
_h += padding_top + padding_bottom;
if (text_w == 0)
{
// Avoid using auto_resize with manually generated options list
text_w = m_popup->w;
}
u16 _w = text_w + dropdown_arrow_spacing + std::max<u16>(_h / 2, max_dropdown_arrow_dimension); // Enough to fit longest string + space for the arrow and padding
_w += padding_left + padding_right;
set_size(_w, _h);
}
void select::set_size(u16 w, u16 h)
{
clear_items();
if (const u16 min_x = (padding_left + padding_right + max_dropdown_arrow_dimension + (2 * dropdown_arrow_spacing)); w < min_x)
{
w = min_x;
}
if (const u16 min_y = (padding_top + padding_bottom + 16); h < min_y)
{
h = min_y;
}
box_layout::set_size(w, h);
auto background = std::make_unique<rounded_rect>();
background->set_size(w, h);
background->radius = std::min(h / 4, 5);
background->back_color = color4f(0.3f, 0.3f, 0.3f, 1.0f);
const u16 arrow_size = std::min<u16>(h / 2, max_dropdown_arrow_dimension);
auto arrow = std::make_unique<arrow_down>();
arrow->set_size(arrow_size, arrow_size);
arrow->back_color = color4f(0.8f, 0.8f, 0.8f, 1.f);
auto textfield = std::make_unique<label>();
textfield->set_font("Arial", 14);
textfield->align_text(text_align::left);
textfield->back_color.a = 0.f;
// Stretch the textfield
textfield->set_size(w - arrow->w - padding_left - padding_right - dropdown_arrow_spacing, h);
// Center align the arrow vertically to align with the text
if (textfield->h > arrow->h)
{
arrow->set_pos(0, textfield->compute_vertically_centered(arrow.get()));
arrow->y = std::max<u16>(arrow->y, padding_top) - padding_top;
}
// Align items using a container
auto packer = std::make_unique<horizontal_layout>();
packer->set_pos(padding_left, padding_top);
m_label = packer->add_element(textfield);
packer->add_spacer(dropdown_arrow_spacing);
packer->add_element(arrow);
// Pack
add_element(background);
add_element(packer);
// Extend the popup's width
m_popup->set_size(w, m_popup->h);
update_text();
}
void select::select_item(int selection)
{
m_popup->select_entry(selection);
update_text();
}
void select::update_text()
{
if (m_popup->get_selected_index() < 0)
{
return;
}
auto element = m_popup->get_selected_entry();
m_label->set_unicode_text(element->text);
m_label->auto_resize();
m_is_compiled = false;
}
compiled_resource& select::get_compiled()
{
if (is_compiled())
{
return compiled_resources;
}
compiled_resources.clear();
if (!is_visible())
{
m_is_compiled = true;
return compiled_resources;
}
box_layout::get_compiled();
m_is_compiled = true;
return compiled_resources;
}
}

View file

@ -1,10 +1,45 @@
#pragma once
#include "overlay_controls.h"
#include "overlay_arrow.h"
#include "overlay_list_view.hpp"
namespace rsx::overlays
{
struct select : public overlay_element
struct select_popup : public list_view
{
select_popup(u16 w, u16 h);
};
struct select : public box_layout
{
select(u16 w, u16 h, const std::vector<std::string>& options);
select(u16 w, u16 h, const std::vector<std::u32string>& options);
select(u16 w, u16 h, std::vector<std::unique_ptr<overlay_element>>& options);
void set_size(u16 w, u16 h) override;
void auto_resize();
void select_item(int selection);
void select_previous() { m_popup->select_previous(); }
void select_next() { m_popup->select_next(); }
int get_selected_index() const { return m_popup ? m_popup->get_selected_index() : -1; }
// Returns the drawable popup for selection.
select_popup* get_popup() { return m_popup.get(); }
compiled_resource& get_compiled() override;
protected:
std::unique_ptr<select_popup> m_popup;
label* m_label = nullptr;
void update_text();
void create_popup(std::vector<std::unique_ptr<overlay_element>>& options);
};
}