diff --git a/assets/config_menu.rml b/assets/config_menu.rml index 0f53941..0666d77 100644 --- a/assets/config_menu.rml +++ b/assets/config_menu.rml @@ -96,7 +96,6 @@ -
+ + + + Inventory + + + + +
+
+
+
+ + +
+ +
+
+ + + +
+
+
+ + diff --git a/include/recomp_ui.h b/include/recomp_ui.h index 32c07d0..5cc74be 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -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 diff --git a/src/ui/core/ui_context.cpp b/src/ui/core/ui_context.cpp index 98d0059..55a634f 100644 --- a/src/ui/core/ui_context.cpp +++ b/src/ui/core/ui_context.cpp @@ -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(); diff --git a/src/ui/core/ui_context.h b/src/ui/core/ui_context.h index 72ef338..71df0eb 100644 --- a/src/ui/core/ui_context.h +++ b/src/ui/core/ui_context.h @@ -4,6 +4,7 @@ #include #include #include +#include #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(); diff --git a/src/ui/elements/ui_button.cpp b/src/ui/elements/ui_button.cpp index 8158f45..41795f7 100644 --- a/src/ui/elements/ui_button.cpp +++ b/src/ui/elements/ui_button.cpp @@ -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; diff --git a/src/ui/elements/ui_element.h b/src/ui/elements/ui_element.h index abe38f6..2a12cf2 100644 --- a/src/ui/elements/ui_element.h +++ b/src/ui/elements/ui_element.h @@ -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 = {}; diff --git a/src/ui/elements/ui_style.cpp b/src/ui/elements/ui_style.cpp index 57e5566..bb37d78 100644 --- a/src/ui/elements/ui_style.cpp +++ b/src/ui/elements/ui_style.cpp @@ -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 \ No newline at end of file diff --git a/src/ui/elements/ui_style.h b/src/ui/elements/ui_style.h index 163a539..98558e5 100644 --- a/src/ui/elements/ui_style.h +++ b/src/ui/elements/ui_style.h @@ -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; } }; diff --git a/src/ui/elements/ui_types.h b/src/ui/elements/ui_types.h index 3ad2f54..b2c9c77 100644 --- a/src/ui/elements/ui_types.h +++ b/src/ui/elements/ui_types.h @@ -197,6 +197,11 @@ namespace recompui { Clone }; + enum class TabIndex { + None, + Auto + }; + struct Animation { AnimationType type = AnimationType::None; float duration = 0.0f; diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp index 3c028b8..4b2fcb5 100644 --- a/src/ui/ui_config.cpp +++ b/src/ui/ui_config.cpp @@ -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; } diff --git a/src/ui/ui_config_sub_menu.cpp b/src/ui/ui_config_sub_menu.cpp index 8af58d9..aff6aaf 100644 --- a/src/ui/ui_config_sub_menu.cpp +++ b/src/ui/ui_config_sub_menu.cpp @@ -1,6 +1,9 @@ #include "ui_config_sub_menu.h" #include +#include + +#include "recomp_ui.h" namespace recompui { @@ -91,9 +94,12 @@ ConfigOptionRadio::ConfigOptionRadio(const std::vector &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 callback) { - enter_sub_menu_callback = callback; -} - -void ConfigSubMenu::set_quit_sub_menu_callback(std::function 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 callback) { - config_sub_menu->set_enter_sub_menu_callback(callback); -} - -void ElementConfigSubMenu::set_quit_sub_menu_callback(std::function callback) { - config_sub_menu->set_quit_sub_menu_callback(callback); -} - ConfigSubMenu *ElementConfigSubMenu::get_config_sub_menu_element() const { return config_sub_menu; } diff --git a/src/ui/ui_config_sub_menu.h b/src/ui/ui_config_sub_menu.h index 1a2e2f9..f5799e1 100644 --- a/src/ui/ui_config_sub_menu.h +++ b/src/ui/ui_config_sub_menu.h @@ -66,8 +66,6 @@ private: Label *description_label = nullptr; Container *config_container = nullptr; ScrollContainer *config_scroll_container = nullptr; - std::function enter_sub_menu_callback = nullptr; - std::function quit_sub_menu_callback = nullptr; std::vector config_option_elements; std::unordered_set 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 &options); - void set_enter_sub_menu_callback(std::function callback); - void set_quit_sub_menu_callback(std::function 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 callback); - void set_quit_sub_menu_callback(std::function callback); ConfigSubMenu *get_config_sub_menu_element() const; private: ConfigSubMenu *config_sub_menu; diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp index 869eb63..3d550e0 100644 --- a/src/ui/ui_launcher.cpp +++ b/src/ui/ui_launcher.cpp @@ -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; } diff --git a/src/ui/ui_mod_menu.cpp b/src/ui/ui_mod_menu.cpp index a1c2cbb..2c9877e 100644 --- a/src/ui/ui_mod_menu.cpp +++ b/src/ui/ui_mod_menu.cpp @@ -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(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(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(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 diff --git a/src/ui/ui_mod_menu.h b/src/ui/ui_mod_menu.h index 865cc1b..73b8061 100644 --- a/src/ui/ui_mod_menu.h +++ b/src/ui/ui_mod_menu.h @@ -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 mod_entries; std::vector 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; }; diff --git a/src/ui/ui_state.cpp b/src/ui/ui_state.cpp index 50cc4cc..fbce11c 100644 --- a/src/ui/ui_state.cpp +++ b/src/ui/ui_state.cpp @@ -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(window, interface, device); + ui_state->load_documents(); } moodycamel::ConcurrentQueue 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()); +} +