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 9be4427..39543c8 100644
--- a/src/ui/ui_config.cpp
+++ b/src/ui/ui_config.cpp
@@ -132,7 +132,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(), "");
@@ -435,7 +443,8 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
- config_context = recompui::create_context(context, zelda64::get_asset_path("config_menu.rml"));
+ (void)context;
+ config_context = recompui::create_context(zelda64::get_asset_path("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 8167a53..253942d 100644
--- a/src/ui/ui_launcher.cpp
+++ b/src/ui/ui_launcher.cpp
@@ -63,7 +63,8 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
- launcher_context = recompui::create_context(context, zelda64::get_asset_path("launcher.rml"));
+ (void)context;
+ launcher_context = recompui::create_context(zelda64::get_asset_path("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 39a6ccf..9b04b45 100644
--- a/src/ui/ui_state.cpp
+++ b/src/ui/ui_state.cpp
@@ -19,6 +19,7 @@
#include "recomp_input.h"
#include "librecomp/game.hpp"
#include "zelda_config.h"
+#include "zelda_support.h"
#include "ui_rml_hacks.hpp"
#include "ui_elements.h"
#include "ui_mod_menu.h"
@@ -124,12 +125,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 +164,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};
@@ -218,7 +230,9 @@ public:
Rml::LoadFontFace(font.string(), face.fallback_face);
}
}
+ }
+ void load_documents() {
launcher_menu_controller->load_document(context);
config_menu_controller->load_document(context);
}
@@ -279,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);
@@ -335,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();
}
@@ -403,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{};
@@ -523,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();
@@ -742,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());
+}
+