Split config sub menu into separate context and fix configure button, prevent infinite loop when looking for autofocus element

This commit is contained in:
Mr-Wiseguy 2025-01-20 03:55:22 -05:00
parent fabd02d700
commit df976293eb
17 changed files with 196 additions and 57 deletions

View File

@ -96,7 +96,6 @@
<svg src="icons/X.svg" />
</button>
</div>
<recomp-config-sub-menu id="config_sub_menu" />
</div>
<div
class="centered-page__controls"

View File

@ -0,0 +1,70 @@
<rml>
<head>
<link type="text/rcss" href="rml.rcss"/>
<link type="text/rcss" href="recomp.rcss"/>
<title>Inventory</title>
<style>
body
{
width: 100%;
height: 100%;
}
/* Hide the window icon. */
div#title_bar div#icon
{
display: none;
}
.flex-grid {
display: flex;
}
.col {
flex: 1;
text-align: center;
}
</style>
</head>
<body class="window">
<!-- <handle move_target="#document"> -->
<div id="window" class="rmlui-window" style="display:flex; flex-flow: column; background-color:rgba(0,0,0,0)" onkeydown="config_keydown">
<div class="centered-page" onclick="close_config_menu_backdrop">
<div class="centered-page__modal">
<div class="config__icon-buttons">
<button
class="icon-button"
onclick="open_quit_game_prompt"
id="config__quit-game-button"
>
<svg src="icons/Quit.svg" />
</button>
<button
class="icon-button"
onclick="close_config_menu"
id="config__close-menu-button"
>
<svg src="icons/X.svg" />
</button>
</div>
<recomp-config-sub-menu id="config_sub_menu" />
</div>
<div
class="centered-page__controls"
data-model="nav_help_model"
>
<label>
<span>Navigate</span>
<span class="prompt-font-sm">{{nav_help__navigate}}</span>
</label>
<label>
<span>Accept</span>
<span class="prompt-font-sm">{{nav_help__accept}}</span>
</label>
<label>
<span>Exit</span>
<span class="prompt-font-sm">{{nav_help__exit}}</span>
</label>
</div>
</div>
</div>
</body>
</rml>

View File

@ -54,6 +54,7 @@ namespace recompui {
ContextId get_launcher_context_id();
ContextId get_config_context_id();
ContextId get_config_sub_menu_context_id();
ContextId get_close_prompt_context_id();
enum class ButtonVariant {
@ -95,6 +96,7 @@ namespace recompui {
void set_render_hooks();
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
Rml::ElementDocument* load_document(const std::filesystem::path& path);
}
#endif

View File

@ -153,13 +153,13 @@ recompui::ContextId create_context_impl(Rml::ElementDocument* document) {
return ret;
}
recompui::ContextId recompui::create_context(Rml::Context* rml_context, const std::filesystem::path& path) {
recompui::ContextId recompui::create_context(const std::filesystem::path& path) {
ContextId new_context = create_context_impl(nullptr);
auto workingdir = std::filesystem::current_path();
new_context.open();
Rml::ElementDocument* doc = rml_context->LoadDocument(path.string());
Rml::ElementDocument* doc = recompui::load_document(path.string());
opened_context->document = doc;
opened_context->root_element.base = doc;
new_context.close();

View File

@ -4,6 +4,7 @@
#include <memory>
#include <utility>
#include <filesystem>
#include <functional>
#include "RmlUi/Core.h"
@ -48,7 +49,7 @@ namespace recompui {
bool takes_input() { return true; }
};
ContextId create_context(Rml::Context*, const std::filesystem::path& path);
ContextId create_context(const std::filesystem::path& path);
ContextId create_context(Rml::ElementDocument* document);
void destroy_context(ContextId id);
ContextId get_current_context();

View File

@ -19,6 +19,7 @@ namespace recompui {
set_font_weight(700);
set_cursor(Cursor::Pointer);
set_color(Color{ 204, 204, 204, 255 });
set_tab_index(TabIndex::Auto);
hover_style.set_color(Color{ 242, 242, 242, 255 });
const uint8_t border_opacity = 204;

View File

@ -7,7 +7,7 @@
namespace recompui {
class Element : public Style, public Rml::EventListener {
friend ContextId create_context(Rml::Context* rml_context, const std::filesystem::path& path);
friend ContextId create_context(const std::filesystem::path& path);
private:
Rml::Element *base = nullptr;
Rml::ElementPtr base_owning = {};

View File

@ -85,6 +85,18 @@ namespace recompui {
}
}
static Rml::Style::TabIndex to_rml(TabIndex tab_index) {
switch (tab_index) {
case TabIndex::None:
return Rml::Style::TabIndex::None;
case TabIndex::Auto:
return Rml::Style::TabIndex::Auto;
default:
assert(false && "Unknown tab index.");
return Rml::Style::TabIndex::None;
}
}
void Style::set_property(Rml::PropertyId property_id, const Rml::Property &property, Animation) {
property_map[property_id] = property;
}
@ -459,4 +471,8 @@ namespace recompui {
set_property(Rml::PropertyId::Drag, to_rml(drag), Animation());
}
void Style::set_tab_index(TabIndex tab_index) {
set_property(Rml::PropertyId::TabIndex, to_rml(tab_index), Animation());
}
} // namespace recompui

View File

@ -83,6 +83,7 @@ namespace recompui {
void set_row_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_column_gap(float size, Unit unit = Unit::Dp, Animation animation = Animation());
void set_drag(Drag drag);
void set_tab_index(TabIndex focus);
virtual bool is_element() { return false; }
ResourceId get_resource_id() { return resource_id; }
};

View File

@ -197,6 +197,11 @@ namespace recompui {
Clone
};
enum class TabIndex {
None,
Auto
};
struct Animation {
AnimationType type = AnimationType::None;
float duration = 0.0f;

View File

@ -131,7 +131,15 @@ void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
void close_config_menu_impl() {
zelda64::save_config();
recompui::hide_context(recompui::get_config_context_id());
recompui::ContextId config_context = recompui::get_config_context_id();
recompui::ContextId sub_menu_context = recompui::get_config_sub_menu_context_id();
if (recompui::is_context_open(sub_menu_context)) {
recompui::hide_context(sub_menu_context);
}
else {
recompui::hide_context(config_context);
}
if (!ultramodern::is_game_started()) {
recompui::show_context(recompui::get_launcher_context_id(), "");
@ -434,7 +442,8 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
config_context = recompui::create_context(context, "assets/config_menu.rml");
(void)context;
config_context = recompui::create_context("assets/config_menu.rml");
Rml::ElementDocument* ret = config_context.get_document();
return ret;
}

View File

@ -1,6 +1,9 @@
#include "ui_config_sub_menu.h"
#include <cassert>
#include <string_view>
#include "recomp_ui.h"
namespace recompui {
@ -91,9 +94,12 @@ ConfigOptionRadio::ConfigOptionRadio(const std::vector<std::string> &options, El
// ConfigSubMenu
void ConfigSubMenu::back_button_pressed() {
if (quit_sub_menu_callback != nullptr) {
quit_sub_menu_callback();
}
// Hide the config sub menu and show the config menu.
ContextId config_context = recompui::get_config_context_id();
ContextId sub_menu_context = recompui::get_config_sub_menu_context_id();
recompui::hide_context(sub_menu_context);
recompui::show_context(config_context, "");
}
void ConfigSubMenu::option_hovered(ConfigOptionElement *option, bool active) {
@ -113,6 +119,8 @@ void ConfigSubMenu::option_hovered(ConfigOptionElement *option, bool active) {
}
ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
using namespace std::string_view_literals;
set_display(Display::Flex);
set_flex(1, 1, 100.0f, Unit::Percent);
set_flex_direction(FlexDirection::Column);
@ -148,10 +156,6 @@ ConfigSubMenu::~ConfigSubMenu() {
void ConfigSubMenu::enter(std::string_view title) {
title_label->set_text(title);
if (enter_sub_menu_callback != nullptr) {
enter_sub_menu_callback();
}
}
void ConfigSubMenu::clear_options() {
@ -182,18 +186,10 @@ void ConfigSubMenu::add_radio_option(std::string_view name, std::string_view des
add_option(option_radio, name, description);
}
void ConfigSubMenu::set_enter_sub_menu_callback(std::function<void()> callback) {
enter_sub_menu_callback = callback;
}
void ConfigSubMenu::set_quit_sub_menu_callback(std::function<void()> callback) {
quit_sub_menu_callback = callback;
}
// ElementConfigSubMenu
ElementConfigSubMenu::ElementConfigSubMenu(const Rml::String &tag) : Rml::Element(tag) {
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Flex);
SetProperty("width", "100%");
SetProperty("height", "100%");
@ -210,14 +206,6 @@ void ElementConfigSubMenu::set_display(bool display) {
SetProperty(Rml::PropertyId::Display, display ? Rml::Style::Display::Block : Rml::Style::Display::None);
}
void ElementConfigSubMenu::set_enter_sub_menu_callback(std::function<void()> callback) {
config_sub_menu->set_enter_sub_menu_callback(callback);
}
void ElementConfigSubMenu::set_quit_sub_menu_callback(std::function<void()> callback) {
config_sub_menu->set_quit_sub_menu_callback(callback);
}
ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const {
return config_sub_menu;
}

View File

@ -66,8 +66,6 @@ private:
Label *description_label = nullptr;
Container *config_container = nullptr;
ScrollContainer *config_scroll_container = nullptr;
std::function<void()> enter_sub_menu_callback = nullptr;
std::function<void()> quit_sub_menu_callback = nullptr;
std::vector<ConfigOptionElement *> config_option_elements;
std::unordered_set<ConfigOptionElement *> hover_option_elements;
@ -83,8 +81,6 @@ public:
void add_slider_option(std::string_view name, std::string_view description, double min, double max, double step, bool percent);
void add_text_option(std::string_view name, std::string_view description);
void add_radio_option(std::string_view name, std::string_view description, const std::vector<std::string> &options);
void set_enter_sub_menu_callback(std::function<void()> callback);
void set_quit_sub_menu_callback(std::function<void()> callback);
};
class ElementConfigSubMenu : public Rml::Element {
@ -92,8 +88,6 @@ public:
ElementConfigSubMenu(const Rml::String &tag);
virtual ~ElementConfigSubMenu();
void set_display(bool display);
void set_enter_sub_menu_callback(std::function<void()> callback);
void set_quit_sub_menu_callback(std::function<void()> callback);
ConfigSubMenu *get_config_sub_menu_element() const;
private:
ConfigSubMenu *config_sub_menu;

View File

@ -67,7 +67,8 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
launcher_context = recompui::create_context(context, "assets/launcher.rml");
(void)context;
launcher_context = recompui::create_context("assets/launcher.rml");
Rml::ElementDocument* ret = launcher_context.get_document();
return ret;
}

View File

@ -1,4 +1,5 @@
#include "ui_mod_menu.h"
#include "recomp_ui.h"
#include "librecomp/mods.hpp"
@ -80,10 +81,6 @@ void ModMenu::set_active_mod(int32_t mod_index) {
}
}
void ModMenu::set_config_sub_menu(ConfigSubMenu *config_sub_menu) {
ext_config_sub_menu = config_sub_menu;
}
void ModMenu::refresh_mods() {
recomp::mods::scan_mods();
mod_details = recomp::mods::get_mod_details(game_mod_id);
@ -96,25 +93,38 @@ void ModMenu::mod_toggled(bool enabled) {
}
}
// TODO remove this once this is migrated to the new system.
ContextId sub_menu_context;
ContextId get_config_sub_menu_context_id() {
return sub_menu_context;
}
void ModMenu::mod_configure_requested() {
if (active_mod_index >= 0) {
ext_config_sub_menu->clear_options();
// Record the context that was open when this function was called and close it.
ContextId prev_context = recompui::get_current_context();
prev_context.close();
// Open the sub menu context and set up the element.
sub_menu_context.open();
config_sub_menu->clear_options();
const recomp::mods::ConfigSchema &config_schema = recomp::mods::get_mod_config_schema(mod_details[active_mod_index].mod_id);
for (const recomp::mods::ConfigOption &option : config_schema.options) {
switch (option.type) {
case recomp::mods::ConfigOptionType::Enum: {
const recomp::mods::ConfigOptionEnum &option_enum = std::get<recomp::mods::ConfigOptionEnum>(option.variant);
ext_config_sub_menu->add_radio_option(option.name, option.description, option_enum.options);
config_sub_menu->add_radio_option(option.name, option.description, option_enum.options);
break;
}
case recomp::mods::ConfigOptionType::Number: {
const recomp::mods::ConfigOptionNumber &option_number = std::get<recomp::mods::ConfigOptionNumber>(option.variant);
ext_config_sub_menu->add_slider_option(option.name, option.description, option_number.min, option_number.max, option_number.step, option_number.percent);
config_sub_menu->add_slider_option(option.name, option.description, option_number.min, option_number.max, option_number.step, option_number.percent);
break;
}
case recomp::mods::ConfigOptionType::String: {
ext_config_sub_menu->add_text_option(option.name, option.description);
config_sub_menu->add_text_option(option.name, option.description);
break;
}
default:
@ -123,7 +133,15 @@ void ModMenu::mod_configure_requested() {
}
}
ext_config_sub_menu->enter(mod_details[active_mod_index].mod_id);
config_sub_menu->enter(mod_details[active_mod_index].mod_id);
sub_menu_context.close();
// Reopen the context that was open when this function was called.
prev_context.open();
// Hide the config menu and show the sub menu.
recompui::hide_context(recompui::get_config_context_id());
recompui::show_context(sub_menu_context, "");
}
}
@ -194,6 +212,18 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
} // this
refresh_mods();
context.close();
sub_menu_context = recompui::create_context("assets/config_sub_menu.rml");
sub_menu_context.open();
Rml::ElementDocument* sub_menu_doc = sub_menu_context.get_document();
Rml::Element* config_sub_menu_generic = sub_menu_doc->GetElementById("config_sub_menu");
ElementConfigSubMenu* config_sub_menu_element = rmlui_dynamic_cast<ElementConfigSubMenu*>(config_sub_menu_generic);
config_sub_menu = config_sub_menu_element->get_config_sub_menu_element();
sub_menu_context.close();
context.open();
}
ModMenu::~ModMenu() {
@ -214,8 +244,4 @@ ElementModMenu::~ElementModMenu() {
}
void ElementModMenu::set_config_sub_menu(ConfigSubMenu *config_sub_menu) {
mod_menu->set_config_sub_menu(config_sub_menu);
}
} // namespace recompui

View File

@ -30,7 +30,6 @@ public:
ModMenu(Element *parent);
virtual ~ModMenu();
void set_active_mod(int32_t mod_index);
void set_config_sub_menu(ConfigSubMenu *config_sub_menu);
private:
void refresh_mods();
void mod_toggled(bool enabled);
@ -43,18 +42,18 @@ private:
ModDetailsPanel *mod_details_panel = nullptr;
Container *footer_container = nullptr;
Button *refresh_button = nullptr;
ConfigSubMenu *ext_config_sub_menu = nullptr;
int32_t active_mod_index = -1;
std::vector<ModEntry *> mod_entries;
std::vector<recomp::mods::ModDetails> mod_details{};
std::string game_mod_id;
ConfigSubMenu *config_sub_menu;
};
class ElementModMenu : public Rml::Element {
public:
ElementModMenu(const Rml::String& tag);
virtual ~ElementModMenu();
void set_config_sub_menu(ConfigSubMenu *config_sub_menu);
private:
ModMenu *mod_menu;
};

View File

@ -124,12 +124,24 @@ void recompui::register_event(UiEventListenerInstancer& listener, const std::str
Rml::Element* find_autofocus_element(Rml::Element* start) {
Rml::Element* cur_element = start;
Rml::Element* first_found = nullptr;
while (cur_element) {
if (cur_element->HasAttribute("autofocus")) {
break;
}
cur_element = RecompRml::FindNextTabElement(cur_element, true);
// Track the first element that was found to know when we've wrapped around.
if (!first_found) {
first_found = cur_element;
}
// Stop searching if we found the first element again.
else {
if (cur_element == first_found) {
// Return the first tab element as there was nothing marked with autofocus.
return first_found;
}
}
}
return cur_element;
@ -151,7 +163,6 @@ public:
bool mouse_is_active_initialized = false;
bool mouse_is_active = false;
bool cont_is_active = false;
bool submenu_is_active = false;
bool await_stick_return_x = false;
bool await_stick_return_y = false;
int last_active_mouse_position[2] = {0, 0};
@ -219,7 +230,9 @@ public:
Rml::LoadFontFace(directory + face.filename, face.fallback_face);
}
}
}
void load_documents() {
launcher_menu_controller->load_document(context);
config_menu_controller->load_document(context);
}
@ -280,7 +293,7 @@ public:
}
if (cont_is_active || non_mouse_interacted) {
if (non_mouse_interacted && !submenu_is_active) {
if (non_mouse_interacted) {
auto focusedEl = current_document->GetFocusLeafNode();
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
Rml::Element* element = find_autofocus_element(current_document);
@ -336,6 +349,13 @@ public:
.takes_input = takes_input
});
// auto& on_show = context.on_show;
// if (on_show) {
// context.open();
// on_show();
// context.close();
// }
document->PullToFront();
document->Show();
}
@ -404,6 +424,7 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
std::locale::global(std::locale::classic());
#endif
ui_state = std::make_unique<UIState>(window, interface, device);
ui_state->load_documents();
}
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
@ -524,7 +545,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
bool cont_interacted = false;
bool kb_interacted = false;
bool config_was_open = recompui::is_context_open(recompui::get_config_context_id());
bool config_was_open = recompui::is_context_open(recompui::get_config_context_id()) || recompui::is_context_open(recompui::get_config_sub_menu_context_id());
while (recompui::try_deque_event(cur_event)) {
bool context_taking_input = recompui::is_context_taking_input();
@ -743,3 +764,9 @@ bool recompui::is_any_context_open() {
return ui_state->is_any_context_open();
}
Rml::ElementDocument* recompui::load_document(const std::filesystem::path& path) {
std::lock_guard lock{ui_state_mutex};
return ui_state->context->LoadDocument(path.string());
}