Reimplement prompts as a separate UI context and make it so the quit game prompt doesn't bring up the config menu

This commit is contained in:
Mr-Wiseguy 2025-04-04 04:35:06 -04:00
parent 470644e700
commit 27db2aeaa0
11 changed files with 259 additions and 84 deletions

View File

@ -172,6 +172,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_state.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_prompt.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config_sub_menu.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp

View File

@ -1,30 +0,0 @@
<template name="prompt">
<head>
</head>
<body class="prompt">
<div class="prompt__overlay" />
<div class="prompt__content-wrapper" data-if="promptOpen">
<div class="prompt__content">
<h3>{{ promptHeader }}</h3>
<p>{{ promptContent }}</p>
<div class="prompt__controls">
<button
autofocus="true"
id="prompt__confirm-button"
class="button button--success"
style="nav-left: none; nav-right: #prompt__cancel-button"
>
<div class="button__label" id="prompt__confirm-button-label">{{ promptConfirmLabel }}</div>
</button>
<button
id="prompt__cancel-button"
class="button button--error"
style="nav-right: none; nav-left: #prompt__confirm-button"
>
<div class="button__label" id="prompt__cancel-button-label">{{ promptCancelLabel }}</div>
</button>
</div>
</div>
</div>
</body>
</template>

View File

@ -29,7 +29,6 @@
<link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/mods.rml" />
<link type="text/template" href="config_menu/debug.rml" />
<link type="text/template" href="components/prompt.rml" />
</head>
<body class="window">
<!-- <handle move_target="#document"> -->
@ -117,19 +116,6 @@
<label><span style="font-family:promptfont;">&#x21A7;</span> Accept</label> -->
</div>
</div>
<!-- <div
id="prompt-root"
data-model="prompt_model"
data-if="prompt__open"
data-alias-promptOpen="prompt__open"
data-alias-promptHeader="prompt__header"
data-alias-promptContent="prompt__content"
data-alias-promptConfirmLabel="prompt__confirmLabel"
data-alias-promptCancelLabel="prompt__cancelLabel"
data-event-click="prompt__on_click"
>
<template src="prompt"/>
</div> -->
</div>
<!-- </handle> -->
<!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> -->

View File

@ -29,7 +29,7 @@ namespace recompui {
class MenuController {
public:
virtual ~MenuController() {}
virtual Rml::ElementDocument* load_document(Rml::Context* context) = 0;
virtual void load_document() = 0;
virtual void register_events(UiEventListenerInstancer& listener) = 0;
virtual void make_bindings(Rml::Context* context) = 0;
};
@ -56,7 +56,6 @@ 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 ConfigTab {
General,
@ -79,6 +78,7 @@ namespace recompui {
NumVariants,
};
void init_prompt_context();
void open_prompt(
const std::string& headerText,
const std::string& contentText,

View File

@ -156,11 +156,6 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
return true;
}
recompui::ContextId config_context_id = recompui::get_config_context_id();
if (!recompui::is_context_shown(config_context_id)) {
recompui::show_context(config_context_id, "");
}
zelda64::open_quit_game_prompt();
recompui::activate_mouse();
break;

View File

@ -206,6 +206,7 @@ recompui::ContextId recompui::create_context() {
root->set_height(100.0f, Unit::Percent);
root->set_display(Display::Flex);
root->set_opacity(1.0f);
root->set_color(Color{ 242, 242, 242, 255 });
root->set_font_family("chiaro");
root->set_font_style(FontStyle::Normal);
root->set_font_weight(400);
@ -218,6 +219,8 @@ recompui::ContextId recompui::create_context() {
root->set_line_height(sz_add, Unit::Dp);
ret.close();
doc->Hide();
return ret;
}

View File

@ -22,6 +22,9 @@ namespace recompui {
public:
Button(Element *parent, const std::string &text, ButtonStyle style);
void add_pressed_callback(std::function<void()> callback);
Style* get_hover_style() { return &hover_style; }
Style* get_disabled_style() { return &disabled_style; }
Style* get_hover_disabled_style() { return &hover_disabled_style; }
};
} // namespace recompui

View File

@ -442,11 +442,8 @@ public:
~ConfigMenu() override {
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
(void)context;
void load_document() override {
config_context = recompui::create_context(zelda64::get_asset_path("config_menu.rml"));
Rml::ElementDocument* ret = config_context.get_document();
return ret;
}
void register_events(recompui::UiEventListenerInstancer& listener) override {
recompui::register_event(listener, "apply_options",
@ -959,27 +956,6 @@ void recompui::toggle_fullscreen() {
graphics_model_handle.DirtyVariable("wm_option");
}
void recompui::open_prompt(
const std::string& headerText,
const std::string& contentText,
const std::string& confirmLabelText,
const std::string& cancelLabelText,
std::function<void()> confirmCb,
std::function<void()> cancelCb,
ButtonVariant _confirmVariant,
ButtonVariant _cancelVariant,
bool _focusOnCancel,
const std::string& _returnElementId
) {
printf("Prompt opened\n %s (%s): %s %s\n", contentText.c_str(), headerText.c_str(), confirmLabelText.c_str(), cancelLabelText.c_str());
printf(" Autoselected %s\n", confirmLabelText.c_str());
confirmCb();
}
bool recompui::is_prompt_open() {
return false;
}
void recompui::set_config_tab(ConfigTab tab) {
ContextId config_context = recompui::get_config_context_id();

View File

@ -62,11 +62,8 @@ public:
~LauncherMenu() override {
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
(void)context;
void load_document() override {
launcher_context = recompui::create_context(zelda64::get_asset_path("launcher.rml"));
Rml::ElementDocument* ret = launcher_context.get_document();
return ret;
}
void register_events(recompui::UiEventListenerInstancer& listener) override {
recompui::register_event(listener, "select_rom",

243
src/ui/ui_prompt.cpp Normal file
View File

@ -0,0 +1,243 @@
#include <mutex>
#include "recomp_ui.h"
#include "elements/ui_element.h"
#include "elements/ui_label.h"
#include "elements/ui_button.h"
struct {
recompui::ContextId ui_context;
recompui::Label* prompt_header;
recompui::Label* prompt_label;
recompui::Button* confirm_button;
recompui::Button* cancel_button;
std::function<void()> confirm_action;
std::function<void()> cancel_action;
std::string return_element_id;
std::mutex mutex;
} prompt_state;
void run_confirm_callback() {
std::function<void()> confirm_action;
{
std::lock_guard lock{ prompt_state.mutex };
confirm_action = std::move(prompt_state.confirm_action);
}
if (confirm_action) {
confirm_action();
}
recompui::hide_context(prompt_state.ui_context);
// TODO nav: focus on return_element_id
// or just remove it as the usage of the prompt can change now
}
void run_cancel_callback() {
std::function<void()> cancel_action;
{
std::lock_guard lock{ prompt_state.mutex };
cancel_action = std::move(prompt_state.cancel_action);
}
if (cancel_action) {
cancel_action();
}
recompui::hide_context(prompt_state.ui_context);
// TODO nav: focus on return_element_id
// or just remove it as the usage of the prompt can change now
}
void recompui::init_prompt_context() {
ContextId context = create_context();
std::lock_guard lock{ prompt_state.mutex };
context.open();
prompt_state.ui_context = context;
Element* window = context.create_element<Element>(context.get_root_element());
window->set_display(Display::Flex);
window->set_flex_direction(FlexDirection::Column);
window->set_background_color({0, 0, 0, 0});
Element* prompt_overlay = context.create_element<Element>(window);
prompt_overlay->set_background_color(Color{ 190, 184, 219, 25 });
prompt_overlay->set_position(Position::Absolute);
prompt_overlay->set_top(0);
prompt_overlay->set_right(0);
prompt_overlay->set_bottom(0);
prompt_overlay->set_left(0);
Element* prompt_content_wrapper = context.create_element<Element>(window);
prompt_content_wrapper->set_display(Display::Flex);
prompt_content_wrapper->set_position(Position::Absolute);
prompt_content_wrapper->set_top(0);
prompt_content_wrapper->set_right(0);
prompt_content_wrapper->set_bottom(0);
prompt_content_wrapper->set_left(0);
prompt_content_wrapper->set_align_items(AlignItems::Center);
prompt_content_wrapper->set_justify_content(JustifyContent::Center);
Element* prompt_content = context.create_element<Element>(prompt_content_wrapper);
prompt_content->set_display(Display::Flex);
prompt_content->set_position(Position::Relative);
prompt_content->set_flex(1.0f, 1.0f);
prompt_content->set_flex_basis(100, Unit::Percent);
prompt_content->set_flex_direction(FlexDirection::Column);
prompt_content->set_width(100, Unit::Percent);
prompt_content->set_max_width(700, Unit::Dp);
prompt_content->set_height_auto();
prompt_content->set_margin_auto();
prompt_content->set_border_width(1.1, Unit::Dp);
prompt_content->set_border_radius(16, Unit::Dp);
prompt_content->set_border_color(Color{ 255, 255, 255, 51 });
prompt_content->set_background_color(Color{ 8, 7, 13, 229 });
prompt_state.prompt_header = context.create_element<Label>(prompt_content, "Graphics options have changed", LabelStyle::Large);
prompt_state.prompt_header->set_margin(24, Unit::Dp);
prompt_state.prompt_label = context.create_element<Label>(prompt_content, "Would you like to apply or discard these changes?", LabelStyle::Small);
prompt_state.prompt_label->set_margin(24, Unit::Dp);
prompt_state.prompt_label->set_margin_top(0);
Element* prompt_controls = context.create_element<Element>(prompt_content);
prompt_controls->set_display(Display::Flex);
prompt_controls->set_flex_direction(FlexDirection::Row);
prompt_controls->set_justify_content(JustifyContent::Center);
prompt_controls->set_padding_top(24, Unit::Dp);
prompt_controls->set_padding_bottom(24, Unit::Dp);
prompt_controls->set_padding_left(12, Unit::Dp);
prompt_controls->set_padding_right(12, Unit::Dp);
prompt_controls->set_border_top_width(1.1, Unit::Dp);
prompt_controls->set_border_top_color({ 255, 255, 255, 25 });
prompt_state.confirm_button = context.create_element<Button>(prompt_controls, "", ButtonStyle::Primary);
prompt_state.confirm_button->set_min_width(185.0f, Unit::Dp);
prompt_state.confirm_button->set_margin_top(0);
prompt_state.confirm_button->set_margin_bottom(0);
prompt_state.confirm_button->set_margin_left(12, Unit::Dp);
prompt_state.confirm_button->set_margin_right(12, Unit::Dp);
prompt_state.confirm_button->set_text_align(TextAlign::Center);
prompt_state.confirm_button->set_color(Color{ 204, 204, 204, 255 });
prompt_state.confirm_button->add_pressed_callback(run_confirm_callback);
// TODO nav: autofocus
// TODO nav: nav-left: none; nav-right: cancel_button
Style* confirm_hover_style = prompt_state.confirm_button->get_hover_style();
confirm_hover_style->set_border_color(Color{ 69, 208, 67, 255 });
confirm_hover_style->set_background_color(Color{ 69, 208, 67, 76 });
confirm_hover_style->set_color(Color{ 242, 242, 242, 255 });
prompt_state.cancel_button = context.create_element<Button>(prompt_controls, "", ButtonStyle::Primary);
prompt_state.cancel_button->set_min_width(185.0f, Unit::Dp);
prompt_state.cancel_button->set_margin_top(0);
prompt_state.cancel_button->set_margin_bottom(0);
prompt_state.cancel_button->set_margin_left(12, Unit::Dp);
prompt_state.cancel_button->set_margin_right(12, Unit::Dp);
prompt_state.cancel_button->set_text_align(TextAlign::Center);
prompt_state.cancel_button->set_color(Color{ 204, 204, 204, 255 });
prompt_state.cancel_button->add_pressed_callback(run_cancel_callback);
// TODO nav: nav-left: confirm_button; nav-right: none
Style* cancel_hover_style = prompt_state.cancel_button->get_hover_style();
cancel_hover_style->set_border_color(Color{ 248, 96, 57, 255 });
cancel_hover_style->set_background_color(Color{ 248, 96, 57, 76 });
cancel_hover_style->set_color(Color{ 242, 242, 242, 255 });
context.close();
}
void style_button(recompui::Button* button, recompui::ButtonVariant variant) {
recompui::Color button_color{};
switch (variant) {
case recompui::ButtonVariant::Primary:
button_color = { 185, 125, 242, 255 };
break;
case recompui::ButtonVariant::Secondary:
button_color = { 23, 214, 232, 255 };
break;
case recompui::ButtonVariant::Tertiary:
button_color = { 242, 242, 242, 255 };
break;
case recompui::ButtonVariant::Success:
button_color = { 69, 208, 67, 255 };
break;
case recompui::ButtonVariant::Error:
button_color = { 248, 96, 57, 255 };
break;
case recompui::ButtonVariant::Warning:
button_color = { 233, 205, 53, 255 };
break;
default:
assert(false);
break;
}
recompui::Color border_color = button_color;
recompui::Color background_color = button_color;
border_color.a = 0.8f * 255;
background_color.a = 0.05f * 255;
button->set_border_color(border_color);
button->set_background_color(background_color);
recompui::Color hover_border_color = button_color;
recompui::Color hover_background_color = button_color;
hover_border_color.a = 255;
hover_background_color.a = 0.3f * 255;
recompui::Style* hover_style = button->get_hover_style();
hover_style->set_border_color(hover_border_color);
hover_style->set_background_color(hover_background_color);
recompui::Color disabled_color { 255, 255, 255, 0.6f * 255 };
recompui::Style* disabled_style = button->get_disabled_style();
disabled_style->set_color(disabled_color);
}
void recompui::open_prompt(
const std::string& headerText,
const std::string& contentText,
const std::string& confirmLabelText,
const std::string& cancelLabelText,
std::function<void()> confirmCb,
std::function<void()> cancelCb,
ButtonVariant _confirmVariant,
ButtonVariant _cancelVariant,
bool _focusOnCancel,
const std::string& _returnElementId
) {
std::lock_guard lock{ prompt_state.mutex };
prompt_state.ui_context.open();
prompt_state.prompt_header->set_text(headerText);
prompt_state.prompt_label->set_text(contentText);
prompt_state.confirm_button->set_text(confirmLabelText);
prompt_state.cancel_button->set_text(cancelLabelText);
prompt_state.confirm_action = confirmCb;
prompt_state.cancel_action = cancelCb;
prompt_state.return_element_id = _returnElementId;
style_button(prompt_state.confirm_button, _confirmVariant);
style_button(prompt_state.cancel_button, _cancelVariant);
if (_focusOnCancel) {
// TODO nav: focus cancel button
}
else {
// TODO nav: focus confirm button
}
prompt_state.ui_context.close();
if (!recompui::is_context_shown(prompt_state.ui_context)) {
recompui::show_context(prompt_state.ui_context, "");
}
}
bool recompui::is_prompt_open() {
return false;
}

View File

@ -231,9 +231,10 @@ public:
}
}
void load_documents() {
launcher_menu_controller->load_document(context);
config_menu_controller->load_document(context);
void create_menus() {
launcher_menu_controller->load_document();
config_menu_controller->load_document();
recompui::init_prompt_context();
}
void unload() {
@ -443,7 +444,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();
ui_state->create_menus();
}
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};