overlays/home: Integrate the new dropdown widget

This commit is contained in:
kd-11 2026-03-15 22:41:56 +03:00 committed by kd-11
parent bb652ed840
commit 37f1533dad
4 changed files with 228 additions and 59 deletions

View file

@ -2,6 +2,7 @@
#include "Emu/RSX/Overlays/overlays.h"
#include "Emu/RSX/Overlays/overlay_checkbox.h"
#include "Emu/RSX/Overlays/overlay_select.h"
#include "Emu/RSX/Overlays/overlay_slider.h"
#include "Emu/System.h"
@ -121,19 +122,147 @@ namespace rsx
public:
home_menu_dropdown(cfg::_enum<T>* setting, const std::string& text)
: home_menu_setting<T, cfg::_enum<T>>(setting, text)
{}
{
for (size_t index = 0; index < setting->size(); index++)
{
auto translated = Emu.GetCallbacks().get_localized_setting(home_menu_setting<T, cfg::_enum<T>>::m_setting, static_cast<u32>(index));
m_options.emplace_back(std::move(translated));
}
}
void sync_selection()
{
auto setting = home_menu_setting<T, cfg::_enum<T>>::m_setting;
if (!setting || !m_dropdown)
{
return;
}
const auto current = fmt::format("%s", setting->get());
const auto list = setting->to_list();
for (s32 index = 0; index <= list.size(); ++index)
{
if (list[index] != current)
{
continue;
}
if (index != m_dropdown->get_selected_index())
{
m_dropdown->select_item(index);
}
break;
}
}
void set_size(u16 w, u16 h = element_height) override
{
home_menu_setting<T, cfg::_enum<T>>::set_reserved_width(w / 2 + menu_entry_margin);
auto dropdown = std::make_unique<overlays::select>(w / 2, element_height, m_options);
dropdown->auto_resize();
home_menu_setting<T, cfg::_enum<T>>::set_reserved_width(dropdown->w + menu_entry_margin);
home_menu_setting<T, cfg::_enum<T>>::set_size(w, h);
auto dropdown = std::make_unique<overlays::label>();
// Center horizontally
dropdown->set_pos(0, this->compute_vertically_centered(dropdown.get()));
m_dropdown = horizontal_layout::add_element(dropdown);
m_dropdown->set_size(w / 2, element_height);
m_dropdown->set_font("Arial", 14);
m_dropdown->align_text(home_menu_dropdown<T>::text_align::center);
m_dropdown->back_color = { 0.3f, 0.3f, 0.3f, 1.0f };
// Select the correct item
sync_selection();
}
bool is_popup_visible() const
{
return m_popup_visible;
}
s32 get_selected_index() const
{
return m_dropdown->get_selected_index();
}
void open_popup(const overlay_element* parent = nullptr)
{
m_previous_selection = m_dropdown->get_selected_index();
m_popup_visible = true;
if (!parent)
{
m_dropdown->get_popup()->set_pos(m_dropdown->x, m_dropdown->y + m_dropdown->h);
return;
}
u16 dropdown_y = m_dropdown->y;
// Check if the parent is a layout that can scroll
if (auto container = dynamic_cast<const vertical_layout*>(parent))
{
// Apply the scroll (convert coordinate to view space from container space)
dropdown_y = std::max<u16>(dropdown_y, container->scroll_offset_value) - container->scroll_offset_value;
}
const int space_above = static_cast<int>(dropdown_y) - std::min<int>(parent->y, dropdown_y);
const int space_below = std::max<int>(parent->y + parent->h, dropdown_y) - dropdown_y;
const int popup_height = m_dropdown->get_popup()->h;
u16 popup_x = m_dropdown->x, popup_y = parent->y;
if (space_below >= popup_height)
{
popup_y = dropdown_y + m_dropdown->h + 4;
}
else if (space_above >= popup_height)
{
popup_y = dropdown_y - popup_height - 4;
}
m_dropdown->get_popup()->set_pos(popup_x, popup_y);
}
void close_popup()
{
m_popup_visible = false;
}
page_navigation handle_input(pad_button button)
{
switch (button)
{
case pad_button::circle:
m_dropdown->select_item(m_previous_selection);
close_popup();
return page_navigation::exit;
case pad_button::cross:
m_dropdown->select_item(m_dropdown->get_selected_index());
close_popup();
return page_navigation::exit;
case pad_button::dpad_up:
case pad_button::ls_up:
m_dropdown->select_previous();
break;
case pad_button::dpad_down:
case pad_button::ls_down:
m_dropdown->select_next();
break;
default:
break;
}
return page_navigation::stay;
}
compiled_resource& render_popup()
{
if (this->m_popup_visible)
{
return m_dropdown->get_popup()->get_compiled();
}
m_popup_compiled_resources.clear();
return m_popup_compiled_resources;
}
compiled_resource& get_compiled() override
@ -142,11 +271,8 @@ namespace rsx
if (!this->is_compiled())
{
const std::string value_text = Emu.GetCallbacks().get_localized_setting(home_menu_setting<T, cfg::_enum<T>>::m_setting, static_cast<u32>(this->m_last_value));
m_dropdown->set_text(value_text);
m_dropdown->set_pos(m_dropdown->x, this->y + (this->h - m_dropdown->h) / 2);
this->compiled_resources = horizontal_layout::get_compiled();
sync_selection();
horizontal_layout::get_compiled();
this->compiled_resources.add(m_dropdown->get_compiled());
}
@ -154,7 +280,12 @@ namespace rsx
}
private:
label* m_dropdown;
std::vector<std::string> m_options;
select* m_dropdown = nullptr;
int m_previous_selection = -1;
bool m_popup_visible = false;
compiled_resource m_popup_compiled_resources;
};
template <typename T, typename C>

View file

@ -49,11 +49,13 @@ namespace rsx
void home_menu_page::on_activate()
{
hide_scroll_indicators(false);
hide_row_highliter(false);
}
void home_menu_page::on_deactivate()
{
hide_scroll_indicators(true);
hide_row_highliter(true);
}
void home_menu_page::set_current_page(home_menu_page* page)
@ -186,6 +188,16 @@ namespace rsx
return page->handle_button_press(button_press, is_auto_repeat, auto_repeat_interval_ms);
}
if (m_popup && m_popup.input_hook)
{
auto popup_action = m_popup.input_hook(button_press);
if (popup_action == page_navigation::exit)
{
m_popup.dismiss();
}
return page_navigation::stay;
}
switch (button_press)
{
case pad_button::dpad_left:
@ -232,6 +244,7 @@ namespace rsx
g_cfg.from_string(g_backup_cfg.to_string());
Emu.GetCallbacks().update_emu_settings();
*m_config_changed = false;
refresh();
}
});
}
@ -249,6 +262,7 @@ namespace rsx
if (m_config_changed)
{
*m_config_changed = false;
refresh();
}
});
}
@ -330,6 +344,11 @@ namespace rsx
compiled_resource& home_menu_page::get_compiled()
{
if (m_popup)
{
m_is_compiled = false;
}
if (m_message_box && !m_message_box->is_compiled())
{
m_is_compiled = false;
@ -364,6 +383,11 @@ namespace rsx
}
}
if (m_popup)
{
compiled_resources.add(m_popup.get_compiled());
}
m_is_compiled = true;
return compiled_resources;
}

View file

@ -9,6 +9,20 @@ namespace rsx
{
namespace overlays
{
struct home_menu_popup
{
std::function<compiled_resource&()> get_compiled;
std::function<page_navigation(pad_button)> input_hook;
void dismiss()
{
get_compiled = {};
input_hook = {};
}
operator bool() const { return !!get_compiled; }
};
struct home_menu_page : public list_view
{
public:
@ -35,6 +49,8 @@ namespace rsx
std::shared_ptr<home_menu_message_box> m_message_box;
std::shared_ptr<bool> m_config_changed;
home_menu_popup m_popup;
protected:
virtual void add_page(home_menu::fa_icon icon, std::shared_ptr<home_menu_page> page);
virtual void add_item(home_menu::fa_icon icon, std::string_view, std::function<page_navigation(pad_button)> callback);

View file

@ -79,66 +79,64 @@ namespace rsx
std::unique_ptr<overlay_element> elem = std::make_unique<home_menu_dropdown<T>>(setting, localized_text);
elem->set_size(this->w, menu_entry_height);
add_item(elem, [this, setting](pad_button btn) -> page_navigation
add_item(elem, [this, setting, elem = elem.get()](pad_button btn) -> page_navigation
{
if (setting)
if (btn != pad_button::cross)
{
bool set_default = false;
switch (btn)
return page_navigation::stay;
}
if (!setting)
{
return page_navigation::stay;
}
// If we're receiving this, we need to open the popup and install the input hook
auto dropdown = ensure(dynamic_cast<home_menu_dropdown<T>*>(elem));
dropdown->open_popup(static_cast<const list_view*>(this));
auto render_fn = [dropdown]() -> compiled_resource&
{
return dropdown->render_popup();
};
auto input_fn = [this, dropdown, setting](pad_button button) -> page_navigation
{
const auto result = dropdown->handle_input(button);
if (!setting || result != page_navigation::exit)
{
case pad_button::cross:
break;
case pad_button::select:
set_default = true;
break;
default:
return page_navigation::stay;
return result;
}
T value = setting->get();
if (set_default)
{
if (value == setting->def)
{
return page_navigation::stay;
}
value = setting->def;
}
usz new_index = 0;
const std::string val = fmt::format("%s", value);
const auto previous = fmt::format("%s", setting->get());
const std::vector<std::string> list = setting->to_list();
const int selected_idx = dropdown->get_selected_index();
for (usz i = 0; i < list.size(); i++)
{
const std::string& entry = list[i];
if (entry == val)
{
if (set_default)
{
new_index = i;
break;
}
new_index = (i + 1) % list.size();
break;
}
}
if (const std::string& next_value = ::at32(list, new_index); setting->from_string(next_value))
if (const std::string& next_value = ::at32(list, selected_idx); setting->from_string(next_value))
{
rsx_log.notice("User toggled dropdown in '%s'. Setting '%s' to %s", title, setting->get_name(), next_value);
if (next_value != previous)
{
Emu.GetCallbacks().update_emu_settings();
if (m_config_changed)
{
*m_config_changed = true;
}
refresh();
}
}
else
{
rsx_log.error("Can't toggle dropdown in '%s'. Setting '%s' to '%s' failed", title, setting->get_name(), next_value);
}
Emu.GetCallbacks().update_emu_settings();
if (m_config_changed) *m_config_changed = true;
refresh();
}
return result;
};
m_popup.get_compiled = render_fn;
m_popup.input_hook = input_fn;
return page_navigation::stay;
});