mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2025-03-12 14:56:37 +01:00
Refactor Rml document handling to use new ContextId system (prompts currently unimplemented)
This commit is contained in:
parent
6ca41f37c9
commit
1bd61451fa
@ -154,6 +154,7 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/game/rom_decompression.cpp
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src/ui/ui_renderer.cpp
|
||||
${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_config_sub_menu.cpp
|
||||
|
@ -33,7 +33,7 @@
|
||||
</head>
|
||||
<body class="window">
|
||||
<!-- <handle move_target="#document"> -->
|
||||
<div id="window" class="rmlui-window rmlui-window--hidden" style="display:flex; flex-flow: column; background-color:rgba(0,0,0,0)" onkeydown="config_keydown">
|
||||
<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">
|
||||
<tabset class="tabs" id="config_tabset">
|
||||
@ -118,7 +118,7 @@
|
||||
<label><span style="font-family:promptfont;">↧</span> Accept</label> -->
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
id="prompt-root"
|
||||
data-model="prompt_model"
|
||||
data-if="prompt__open"
|
||||
@ -130,7 +130,7 @@
|
||||
data-event-click="prompt__on_click"
|
||||
>
|
||||
<template src="prompt"/>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- </handle> -->
|
||||
<!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> -->
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
// TODO move this file into src/ui
|
||||
|
||||
#include "SDL.h"
|
||||
#include "RmlUi/Core.h"
|
||||
@ -10,124 +13,88 @@
|
||||
#include "../src/ui/util/hsv.h"
|
||||
#include "../src/ui/util/bem.h"
|
||||
|
||||
#include "../src/ui/core/ui_context.h"
|
||||
|
||||
namespace Rml {
|
||||
class ElementDocument;
|
||||
class EventListenerInstancer;
|
||||
class Context;
|
||||
class Event;
|
||||
class ElementDocument;
|
||||
class EventListenerInstancer;
|
||||
class Context;
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
class UiEventListenerInstancer;
|
||||
class UiEventListenerInstancer;
|
||||
|
||||
class MenuController {
|
||||
public:
|
||||
virtual ~MenuController() {}
|
||||
virtual Rml::ElementDocument* load_document(Rml::Context* context) = 0;
|
||||
virtual void register_events(UiEventListenerInstancer& listener) = 0;
|
||||
virtual void make_bindings(Rml::Context* context) = 0;
|
||||
};
|
||||
// TODO remove this once the UI has been ported over to the new system.
|
||||
class MenuController {
|
||||
public:
|
||||
virtual ~MenuController() {}
|
||||
virtual Rml::ElementDocument* load_document(Rml::Context* context) = 0;
|
||||
virtual void register_events(UiEventListenerInstancer& listener) = 0;
|
||||
virtual void make_bindings(Rml::Context* context) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<MenuController> create_launcher_menu();
|
||||
std::unique_ptr<MenuController> create_config_menu();
|
||||
std::unique_ptr<MenuController> create_launcher_menu();
|
||||
std::unique_ptr<MenuController> create_config_menu();
|
||||
|
||||
using event_handler_t = void(const std::string& param, Rml::Event&);
|
||||
using event_handler_t = void(const std::string& param, Rml::Event&);
|
||||
|
||||
void queue_event(const SDL_Event& event);
|
||||
bool try_deque_event(SDL_Event& out);
|
||||
void queue_event(const SDL_Event& event);
|
||||
bool try_deque_event(SDL_Event& out);
|
||||
|
||||
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
||||
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
||||
std::unique_ptr<UiEventListenerInstancer> make_event_listener_instancer();
|
||||
void register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler);
|
||||
|
||||
enum class Menu {
|
||||
Launcher,
|
||||
Config,
|
||||
None
|
||||
};
|
||||
void show_context(ContextId context, std::string_view param);
|
||||
void hide_context(ContextId context);
|
||||
void hide_all_contexts();
|
||||
bool is_context_open(ContextId context);
|
||||
bool is_context_taking_input();
|
||||
bool is_any_context_open();
|
||||
|
||||
void set_current_menu(Menu menu);
|
||||
Menu get_current_menu();
|
||||
ContextId get_launcher_context_id();
|
||||
ContextId get_config_context_id();
|
||||
ContextId get_close_prompt_context_id();
|
||||
|
||||
enum class ConfigSubmenu {
|
||||
General,
|
||||
Controls,
|
||||
Graphics,
|
||||
Audio,
|
||||
Mods,
|
||||
Debug,
|
||||
Count
|
||||
};
|
||||
enum class ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Success,
|
||||
Error,
|
||||
Warning,
|
||||
NumVariants,
|
||||
};
|
||||
|
||||
enum class ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary,
|
||||
Success,
|
||||
Error,
|
||||
Warning,
|
||||
NumVariants,
|
||||
};
|
||||
void 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::Success,
|
||||
ButtonVariant _cancelVariant = ButtonVariant::Error,
|
||||
bool _focusOnCancel = true,
|
||||
const std::string& _returnElementId = ""
|
||||
);
|
||||
bool is_prompt_open();
|
||||
|
||||
void set_config_submenu(ConfigSubmenu submenu);
|
||||
void apply_color_hack();
|
||||
void get_window_size(int& width, int& height);
|
||||
void set_cursor_visible(bool visible);
|
||||
void update_supported_options();
|
||||
void toggle_fullscreen();
|
||||
|
||||
void destroy_ui();
|
||||
void apply_color_hack();
|
||||
void get_window_size(int& width, int& height);
|
||||
void set_cursor_visible(bool visible);
|
||||
void update_supported_options();
|
||||
void toggle_fullscreen();
|
||||
void update_rml_display_refresh_rate();
|
||||
bool get_cont_active(void);
|
||||
void set_cont_active(bool active);
|
||||
void activate_mouse();
|
||||
|
||||
extern const std::unordered_map<ButtonVariant, std::string> button_variants;
|
||||
void message_box(const char* msg);
|
||||
|
||||
struct PromptContext {
|
||||
Rml::DataModelHandle model_handle;
|
||||
std::string header = "";
|
||||
std::string content = "";
|
||||
std::string confirmLabel = "Confirm";
|
||||
std::string cancelLabel = "Cancel";
|
||||
ButtonVariant confirmVariant = ButtonVariant::Success;
|
||||
ButtonVariant cancelVariant = ButtonVariant::Error;
|
||||
std::function<void()> onConfirm;
|
||||
std::function<void()> onCancel;
|
||||
void set_render_hooks();
|
||||
|
||||
std::string returnElementId = "";
|
||||
|
||||
bool open = false;
|
||||
bool shouldFocus = false;
|
||||
bool focusOnCancel = true;
|
||||
|
||||
PromptContext() = default;
|
||||
|
||||
void close_prompt();
|
||||
void 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::Success,
|
||||
ButtonVariant _cancelVariant = ButtonVariant::Error,
|
||||
bool _focusOnCancel = true,
|
||||
const std::string& _returnElementId = ""
|
||||
);
|
||||
void on_confirm(void);
|
||||
void on_cancel(void);
|
||||
void on_click(Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs);
|
||||
};
|
||||
|
||||
PromptContext *get_prompt_context(void);
|
||||
|
||||
bool get_cont_active(void);
|
||||
void set_cont_active(bool active);
|
||||
void activate_mouse();
|
||||
|
||||
void message_box(const char* msg);
|
||||
|
||||
void set_render_hooks();
|
||||
|
||||
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
|
||||
Rml::ElementPtr create_custom_element(Rml::Element* parent, std::string tag);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -103,7 +103,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
SDL_KeyboardEvent* keyevent = &event->key;
|
||||
|
||||
// Skip repeated events when not in the menu
|
||||
if (recompui::get_current_menu() == recompui::Menu::None &&
|
||||
if (!recompui::is_context_taking_input() &&
|
||||
event->key.repeat) {
|
||||
break;
|
||||
}
|
||||
@ -156,8 +156,9 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recompui::get_current_menu() != recompui::Menu::Config) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::ContextId config_context_id = recompui::get_config_context_id();
|
||||
if (!recompui::is_context_open(config_context_id)) {
|
||||
recompui::show_context(config_context_id, "");
|
||||
}
|
||||
|
||||
zelda64::open_quit_game_prompt();
|
||||
@ -711,8 +712,8 @@ void recomp::set_right_analog_suppressed(bool suppressed) {
|
||||
}
|
||||
|
||||
bool recomp::game_input_disabled() {
|
||||
// Disable input if any menu is open.
|
||||
return recompui::get_current_menu() != recompui::Menu::None;
|
||||
// Disable input if any menu that blocks input is open.
|
||||
return recompui::is_any_context_open();
|
||||
}
|
||||
|
||||
bool recomp::all_input_disabled() {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "slot_map.h"
|
||||
|
||||
#include "ultramodern/error_handling.hpp"
|
||||
#include "recomp_ui.h"
|
||||
#include "ui_context.h"
|
||||
#include "../elements/ui_element.h"
|
||||
|
||||
@ -62,8 +63,7 @@ enum class ContextErrorType {
|
||||
DestroyResourceWithoutOpen,
|
||||
DestroyResourceInWrongContext,
|
||||
DestroyResourceNotFound,
|
||||
GetDocumentWithoutOpen,
|
||||
GetDocumentInWrongContext,
|
||||
GetDocumentInvalidContext,
|
||||
};
|
||||
|
||||
enum class SlotTag : uint8_t {
|
||||
@ -116,11 +116,8 @@ void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||
case ContextErrorType::DestroyResourceNotFound:
|
||||
error_message = "Attempted to destroy a UI resource that doesn't exist in the current context";
|
||||
break;
|
||||
case ContextErrorType::GetDocumentWithoutOpen:
|
||||
error_message = "Attempted to get the current UI context's document with no open UI context";
|
||||
break;
|
||||
case ContextErrorType::GetDocumentInWrongContext:
|
||||
error_message = "Attempted to get the document of a UI context that's not open";
|
||||
case ContextErrorType::GetDocumentInvalidContext:
|
||||
error_message = "Attempted to get the document of an invalid UI context";
|
||||
break;
|
||||
default:
|
||||
error_message = "Unknown UI context error";
|
||||
@ -129,7 +126,7 @@ void context_error(recompui::ContextId id, ContextErrorType type) {
|
||||
|
||||
// This assumes the error is coming from a mod, as it's unlikely that an end user will see a UI context error
|
||||
// in the base recomp.
|
||||
ultramodern::error_handling::message_box((std::string{"Fatal error in mod - "} + error_message + ".").c_str());
|
||||
recompui::message_box((std::string{"Fatal error in mod - "} + error_message + ".").c_str());
|
||||
assert(false);
|
||||
ultramodern::error_handling::quick_exit(__FILE__, __LINE__, __FUNCTION__);
|
||||
}
|
||||
@ -159,6 +156,8 @@ recompui::ContextId create_context_impl(Rml::ElementDocument* document) {
|
||||
recompui::ContextId recompui::create_context(Rml::Context* rml_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());
|
||||
opened_context->document = doc;
|
||||
@ -376,31 +375,25 @@ void recompui::ContextId::clear_children() {
|
||||
}
|
||||
|
||||
Rml::ElementDocument* recompui::ContextId::get_document() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::GetDocumentWithoutOpen);
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInWrongContext);
|
||||
}
|
||||
|
||||
return opened_context->document;
|
||||
return ctx->document;
|
||||
}
|
||||
|
||||
recompui::Element* recompui::ContextId::get_root_element() {
|
||||
// Ensure a context is currently opened by this thread.
|
||||
if (opened_context_id == ContextId::null()) {
|
||||
context_error(*this, ContextErrorType::GetDocumentWithoutOpen);
|
||||
std::lock_guard lock{ context_state.all_contexts_lock };
|
||||
|
||||
Context* ctx = context_state.all_contexts.get(context_slotmap::key{ slot_id });
|
||||
if (ctx == nullptr) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInvalidContext);
|
||||
}
|
||||
|
||||
// Check that the context that was specified is the same one that's currently open.
|
||||
if (*this != opened_context_id) {
|
||||
context_error(*this, ContextErrorType::GetDocumentInWrongContext);
|
||||
}
|
||||
|
||||
return &opened_context->root_element;
|
||||
return &ctx->root_element;
|
||||
}
|
||||
|
||||
recompui::ContextId recompui::get_current_context() {
|
||||
|
@ -43,6 +43,9 @@ namespace recompui {
|
||||
void close();
|
||||
|
||||
static constexpr ContextId null() { return ContextId{ .slot_id = uint32_t(-1) }; }
|
||||
|
||||
// TODO
|
||||
bool takes_input() { return true; }
|
||||
};
|
||||
|
||||
ContextId create_context(Rml::Context*, const std::filesystem::path& path);
|
||||
|
@ -18,87 +18,6 @@ Rml::DataModelHandle controls_model_handle;
|
||||
Rml::DataModelHandle graphics_model_handle;
|
||||
Rml::DataModelHandle sound_options_model_handle;
|
||||
|
||||
recompui::PromptContext prompt_context;
|
||||
|
||||
namespace recompui {
|
||||
const std::unordered_map<ButtonVariant, std::string> button_variants {
|
||||
{ButtonVariant::Primary, "primary"},
|
||||
{ButtonVariant::Secondary, "secondary"},
|
||||
{ButtonVariant::Tertiary, "tertiary"},
|
||||
{ButtonVariant::Success, "success"},
|
||||
{ButtonVariant::Error, "error"},
|
||||
{ButtonVariant::Warning, "warning"}
|
||||
};
|
||||
|
||||
void PromptContext::close_prompt() {
|
||||
open = false;
|
||||
model_handle.DirtyVariable("prompt__open");
|
||||
}
|
||||
|
||||
void PromptContext::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
|
||||
) {
|
||||
open = true;
|
||||
header = headerText;
|
||||
content = contentText;
|
||||
confirmLabel = confirmLabelText;
|
||||
cancelLabel = cancelLabelText;
|
||||
onConfirm = confirmCb;
|
||||
onCancel = cancelCb;
|
||||
confirmVariant = _confirmVariant;
|
||||
cancelVariant = _cancelVariant;
|
||||
focusOnCancel = _focusOnCancel;
|
||||
returnElementId = _returnElementId;
|
||||
|
||||
model_handle.DirtyVariable("prompt__open");
|
||||
model_handle.DirtyVariable("prompt__header");
|
||||
model_handle.DirtyVariable("prompt__content");
|
||||
model_handle.DirtyVariable("prompt__confirmLabel");
|
||||
model_handle.DirtyVariable("prompt__cancelLabel");
|
||||
shouldFocus = true;
|
||||
}
|
||||
|
||||
void PromptContext::on_confirm(void) {
|
||||
onConfirm();
|
||||
open = false;
|
||||
model_handle.DirtyVariable("prompt__open");
|
||||
}
|
||||
|
||||
void PromptContext::on_cancel(void) {
|
||||
onCancel();
|
||||
open = false;
|
||||
model_handle.DirtyVariable("prompt__open");
|
||||
}
|
||||
|
||||
void PromptContext::on_click(Rml::DataModelHandle model_handle, Rml::Event& event, const Rml::VariantList& inputs) {
|
||||
Rml::Element *target = event.GetTargetElement();
|
||||
auto id = target->GetId();
|
||||
if (id == "prompt__confirm-button" || id == "prompt__confirm-button-label") {
|
||||
on_confirm();
|
||||
event.StopPropagation();
|
||||
} else if (id == "prompt__cancel-button" || id == "prompt__cancel-button-label") {
|
||||
on_cancel();
|
||||
event.StopPropagation();
|
||||
}
|
||||
if (event.GetCurrentElement()->GetId() == "prompt-root") {
|
||||
event.StopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
PromptContext *get_prompt_context() {
|
||||
return &prompt_context;
|
||||
}
|
||||
};
|
||||
|
||||
// True if controller config menu is open, false if keyboard config menu is open, undefined otherwise
|
||||
bool configuring_controller = false;
|
||||
|
||||
@ -212,11 +131,10 @@ void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
|
||||
void close_config_menu_impl() {
|
||||
zelda64::save_config();
|
||||
|
||||
if (ultramodern::is_game_started()) {
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
}
|
||||
else {
|
||||
recompui::set_current_menu(recompui::Menu::Launcher);
|
||||
recompui::hide_context(recompui::get_config_context_id());
|
||||
|
||||
if (!ultramodern::is_game_started()) {
|
||||
recompui::show_context(recompui::get_launcher_context_id(), "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +156,7 @@ void apply_graphics_config(void) {
|
||||
|
||||
void close_config_menu() {
|
||||
if (ultramodern::renderer::get_graphics_config() != new_options) {
|
||||
prompt_context.open_prompt(
|
||||
recompui::open_prompt(
|
||||
"Graphics options have changed",
|
||||
"Would you like to apply or discard the changes?",
|
||||
"Apply",
|
||||
@ -265,7 +183,7 @@ void close_config_menu() {
|
||||
}
|
||||
|
||||
void zelda64::open_quit_game_prompt() {
|
||||
prompt_context.open_prompt(
|
||||
recompui::open_prompt(
|
||||
"Are you sure you want to quit?",
|
||||
"Any progress since your last save will be lost.",
|
||||
"Quit",
|
||||
@ -499,22 +417,15 @@ struct DebugContext {
|
||||
}
|
||||
};
|
||||
|
||||
void recompui::update_rml_display_refresh_rate() {
|
||||
static uint32_t lastRate = 0;
|
||||
if (!graphics_model_handle) return;
|
||||
|
||||
uint32_t curRate = ultramodern::get_display_refresh_rate();
|
||||
if (curRate != lastRate) {
|
||||
graphics_model_handle.DirtyVariable("display_refresh_rate");
|
||||
}
|
||||
lastRate = curRate;
|
||||
}
|
||||
|
||||
DebugContext debug_context;
|
||||
|
||||
recompui::ContextId config_context;
|
||||
|
||||
recompui::ContextId recompui::get_config_context_id() {
|
||||
return config_context;
|
||||
}
|
||||
|
||||
class ConfigMenu : public recompui::MenuController {
|
||||
private:
|
||||
recompui::ContextId config_context;
|
||||
public:
|
||||
ConfigMenu() {
|
||||
|
||||
@ -524,9 +435,7 @@ public:
|
||||
}
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
config_context = recompui::create_context(context, "assets/config_menu.rml");
|
||||
config_context.open();
|
||||
Rml::ElementDocument* ret = config_context.get_document();
|
||||
config_context.close();
|
||||
return ret;
|
||||
}
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
@ -537,7 +446,7 @@ public:
|
||||
});
|
||||
recompui::register_event(listener, "config_keydown",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
if (!prompt_context.open && event.GetId() == Rml::EventId::Keydown) {
|
||||
if (!recompui::is_prompt_open() && event.GetId() == Rml::EventId::Keydown) {
|
||||
auto key = event.GetParameter<Rml::Input::KeyIdentifier>("key_identifier", Rml::Input::KeyIdentifier::KI_UNKNOWN);
|
||||
switch (key) {
|
||||
case Rml::Input::KeyIdentifier::KI_ESCAPE:
|
||||
@ -996,34 +905,15 @@ public:
|
||||
debug_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_prompt_bindings(Rml::Context* context) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("prompt_model");
|
||||
if (!constructor) {
|
||||
throw std::runtime_error("Failed to make RmlUi data model for the prompt");
|
||||
}
|
||||
|
||||
// Bind the debug mode enabled flag.
|
||||
constructor.Bind("prompt__open", &prompt_context.open);
|
||||
constructor.Bind("prompt__header", &prompt_context.header);
|
||||
constructor.Bind("prompt__content", &prompt_context.content);
|
||||
constructor.Bind("prompt__confirmLabel", &prompt_context.confirmLabel);
|
||||
constructor.Bind("prompt__cancelLabel", &prompt_context.cancelLabel);
|
||||
|
||||
constructor.BindEventCallback("prompt__on_click", &recompui::PromptContext::on_click, &prompt_context);
|
||||
|
||||
prompt_context.model_handle = constructor.GetModelHandle();
|
||||
}
|
||||
|
||||
void make_bindings(Rml::Context* context) override {
|
||||
// initially set cont state for ui help
|
||||
recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
//recomp::config_menu_set_cont_or_kb(recompui::get_cont_active());
|
||||
make_nav_help_bindings(context);
|
||||
make_general_bindings(context);
|
||||
make_controls_bindings(context);
|
||||
make_graphics_bindings(context);
|
||||
make_sound_options_bindings(context);
|
||||
make_debug_bindings(context);
|
||||
make_prompt_bindings(context);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1058,3 +948,24 @@ void recompui::toggle_fullscreen() {
|
||||
apply_graphics_config();
|
||||
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;
|
||||
}
|
||||
|
@ -52,6 +52,12 @@ void select_rom() {
|
||||
}
|
||||
}
|
||||
|
||||
recompui::ContextId launcher_context;
|
||||
|
||||
recompui::ContextId recompui::get_launcher_context_id() {
|
||||
return launcher_context;
|
||||
}
|
||||
|
||||
class LauncherMenu : public recompui::MenuController {
|
||||
public:
|
||||
LauncherMenu() {
|
||||
@ -61,7 +67,9 @@ public:
|
||||
|
||||
}
|
||||
Rml::ElementDocument* load_document(Rml::Context* context) override {
|
||||
return context->LoadDocument("assets/launcher.rml");
|
||||
launcher_context = recompui::create_context(context, "assets/launcher.rml");
|
||||
Rml::ElementDocument* ret = launcher_context.get_document();
|
||||
return ret;
|
||||
}
|
||||
void register_events(recompui::UiEventListenerInstancer& listener) override {
|
||||
recompui::register_event(listener, "select_rom",
|
||||
@ -78,25 +86,25 @@ public:
|
||||
recompui::register_event(listener, "start_game",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recomp::start_game(supported_games[0].game_id);
|
||||
recompui::set_current_menu(recompui::Menu::None);
|
||||
recompui::hide_all_contexts();
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "open_controls",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Controls);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "controls");
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "open_settings",
|
||||
[](const std::string& param, Rml::Event& event) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::General);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "general");
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "open_mods",
|
||||
[](const std::string ¶m, Rml::Event &event) {
|
||||
recompui::set_current_menu(recompui::Menu::Config);
|
||||
recompui::set_config_submenu(recompui::ConfigSubmenu::Mods);
|
||||
recompui::hide_all_contexts();
|
||||
recompui::show_context(recompui::get_config_context_id(), "mods");
|
||||
}
|
||||
);
|
||||
recompui::register_event(listener, "exit_game",
|
||||
|
File diff suppressed because it is too large
Load Diff
35
src/ui/ui_renderer.h
Normal file
35
src/ui/ui_renderer.h
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef __UI_RENDERER_H__
|
||||
#define __UI_RENDERER_H__
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace RT64 {
|
||||
class RenderInterface;
|
||||
class RenderDevice;
|
||||
class RenderCommandList;
|
||||
class RenderFramebuffer;
|
||||
};
|
||||
|
||||
namespace Rml {
|
||||
class RenderInterface;
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
class RmlRenderInterface_RT64_impl;
|
||||
|
||||
class RmlRenderInterface_RT64 {
|
||||
private:
|
||||
std::unique_ptr<RmlRenderInterface_RT64_impl> impl;
|
||||
public:
|
||||
RmlRenderInterface_RT64();
|
||||
~RmlRenderInterface_RT64();
|
||||
void reset();
|
||||
void init(RT64::RenderInterface* interface, RT64::RenderDevice* device);
|
||||
Rml::RenderInterface* get_rml_interface();
|
||||
|
||||
void start(RT64::RenderCommandList* list, int image_width, int image_height);
|
||||
void end(RT64::RenderCommandList* list, RT64::RenderFramebuffer* framebuffer);
|
||||
};
|
||||
} // namespace recompui
|
||||
|
||||
#endif
|
746
src/ui/ui_state.cpp
Normal file
746
src/ui/ui_state.cpp
Normal file
@ -0,0 +1,746 @@
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <SDL_video.h>
|
||||
#else
|
||||
#include <SDL2/SDL_video.h>
|
||||
#endif
|
||||
|
||||
#include "rt64_render_hooks.h"
|
||||
|
||||
#include "concurrentqueue.h"
|
||||
|
||||
#include "RmlUi/Core.h"
|
||||
#include "RmlUi/Debugger.h"
|
||||
#include "RmlUi/Core/RenderInterfaceCompatibility.h"
|
||||
#include "RmlUi/../../Source/Core/Elements/ElementLabel.h"
|
||||
#include "RmlUi_Platform_SDL.h"
|
||||
|
||||
#include "recomp_ui.h"
|
||||
#include "recomp_input.h"
|
||||
#include "librecomp/game.hpp"
|
||||
#include "zelda_config.h"
|
||||
#include "ui_rml_hacks.hpp"
|
||||
#include "ui_elements.h"
|
||||
#include "ui_mod_menu.h"
|
||||
#include "ui_renderer.h"
|
||||
#include "librecomp/config.hpp"
|
||||
|
||||
bool can_focus(Rml::Element* element) {
|
||||
return element->GetOwnerDocument() != nullptr && element->GetProperty(Rml::PropertyId::TabIndex)->Get<Rml::Style::TabIndex>() != Rml::Style::TabIndex::None;
|
||||
}
|
||||
|
||||
//! Copied from lib\RmlUi\Source\Core\Elements\ElementLabel.cpp
|
||||
// Get the first descending element whose tag name matches one of tags.
|
||||
static Rml::Element* TagMatchRecursive(const Rml::StringList& tags, Rml::Element* element)
|
||||
{
|
||||
const int num_children = element->GetNumChildren();
|
||||
|
||||
for (int i = 0; i < num_children; i++)
|
||||
{
|
||||
Rml::Element* child = element->GetChild(i);
|
||||
|
||||
for (const Rml::String& tag : tags)
|
||||
{
|
||||
if (child->GetTagName() == tag)
|
||||
return child;
|
||||
}
|
||||
|
||||
Rml::Element* matching_element = TagMatchRecursive(tags, child);
|
||||
if (matching_element)
|
||||
return matching_element;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rml::Element* get_target(Rml::ElementDocument* document, Rml::Element* element) {
|
||||
// Labels can have targets, so check if this element is a label.
|
||||
if (element->GetTagName() == "label") {
|
||||
Rml::ElementLabel* labelElement = (Rml::ElementLabel*)element;
|
||||
const Rml::String target_id = labelElement->GetAttribute<Rml::String>("for", "");
|
||||
|
||||
if (target_id.empty())
|
||||
{
|
||||
const Rml::StringList matching_tags = {"button", "input", "textarea", "progress", "progressbar", "select"};
|
||||
|
||||
return TagMatchRecursive(matching_tags, element);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rml::Element* target = labelElement->GetElementById(target_id);
|
||||
if (target != element)
|
||||
return target;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
// Return the element directly if no target exists.
|
||||
return element;
|
||||
}
|
||||
|
||||
namespace recompui {
|
||||
class UiEventListener : public Rml::EventListener {
|
||||
event_handler_t* handler_;
|
||||
Rml::String param_;
|
||||
public:
|
||||
UiEventListener(event_handler_t* handler, Rml::String&& param) : handler_(handler), param_(std::move(param)) {}
|
||||
void ProcessEvent(Rml::Event& event) override {
|
||||
handler_(param_, event);
|
||||
}
|
||||
};
|
||||
|
||||
class UiEventListenerInstancer : public Rml::EventListenerInstancer {
|
||||
std::unordered_map<Rml::String, event_handler_t*> handler_map_;
|
||||
std::unordered_map<Rml::String, UiEventListener> listener_map_;
|
||||
public:
|
||||
Rml::EventListener* InstanceEventListener(const Rml::String& value, Rml::Element* element) override {
|
||||
// Check if a listener has already been made for the full event string and return it if so.
|
||||
auto find_listener_it = listener_map_.find(value);
|
||||
if (find_listener_it != listener_map_.end()) {
|
||||
return &find_listener_it->second;
|
||||
}
|
||||
|
||||
// No existing listener, so check if a handler has been registered for this event type and create a listener for it if so.
|
||||
size_t delimiter_pos = value.find(':');
|
||||
Rml::String event_type = value.substr(0, delimiter_pos);
|
||||
auto find_handler_it = handler_map_.find(event_type);
|
||||
if (find_handler_it != handler_map_.end()) {
|
||||
// A handler was found, create a listener and return it.
|
||||
Rml::String event_param = value.substr(std::min(delimiter_pos, value.size()));
|
||||
return &listener_map_.emplace(value, UiEventListener{ find_handler_it->second, std::move(event_param) }).first->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void register_event(const Rml::String& value, event_handler_t* handler) {
|
||||
handler_map_.emplace(value, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void recompui::register_event(UiEventListenerInstancer& listener, const std::string& name, event_handler_t* handler) {
|
||||
listener.register_event(name, handler);
|
||||
}
|
||||
|
||||
Rml::Element* find_autofocus_element(Rml::Element* start) {
|
||||
Rml::Element* cur_element = start;
|
||||
|
||||
while (cur_element) {
|
||||
if (cur_element->HasAttribute("autofocus")) {
|
||||
break;
|
||||
}
|
||||
cur_element = RecompRml::FindNextTabElement(cur_element, true);
|
||||
}
|
||||
|
||||
return cur_element;
|
||||
}
|
||||
|
||||
struct ContextDetails {
|
||||
recompui::ContextId context;
|
||||
Rml::ElementDocument* document;
|
||||
bool takes_input;
|
||||
};
|
||||
|
||||
class UIState {
|
||||
Rml::Element* prev_focused = nullptr;
|
||||
bool mouse_is_active_changed = false;
|
||||
std::unique_ptr<recompui::MenuController> launcher_menu_controller{};
|
||||
std::unique_ptr<recompui::MenuController> config_menu_controller{};
|
||||
std::vector<ContextDetails> opened_contexts{};
|
||||
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};
|
||||
std::unique_ptr<recompui::MenuController> config_controller;
|
||||
std::unique_ptr<recompui::MenuController> launcher_controller;
|
||||
std::unique_ptr<SystemInterface_SDL> system_interface;
|
||||
recompui::RmlRenderInterface_RT64 render_interface;
|
||||
Rml::Context* context;
|
||||
recompui::UiEventListenerInstancer event_listener_instancer;
|
||||
|
||||
UIState(const UIState& rhs) = delete;
|
||||
UIState& operator=(const UIState& rhs) = delete;
|
||||
UIState(UIState&& rhs) = delete;
|
||||
UIState& operator=(UIState&& rhs) = delete;
|
||||
|
||||
UIState(SDL_Window* window, RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
launcher_menu_controller = recompui::create_launcher_menu();
|
||||
config_menu_controller = recompui::create_config_menu();
|
||||
|
||||
system_interface = std::make_unique<SystemInterface_SDL>();
|
||||
system_interface->SetWindow(window);
|
||||
render_interface.init(interface, device);
|
||||
|
||||
launcher_menu_controller->register_events(event_listener_instancer);
|
||||
config_menu_controller->register_events(event_listener_instancer);
|
||||
|
||||
Rml::SetSystemInterface(system_interface.get());
|
||||
Rml::SetRenderInterface(render_interface.get_rml_interface());
|
||||
Rml::Factory::RegisterEventListenerInstancer(&event_listener_instancer);
|
||||
|
||||
recompui::register_custom_elements();
|
||||
|
||||
Rml::Initialise();
|
||||
|
||||
// Apply the hack to replace RmlUi's default color parser with one that conforms to HTML5 alpha parsing for SASS compatibility
|
||||
recompui::apply_color_hack();
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
|
||||
context = Rml::CreateContext("main", Rml::Vector2i(width, height));
|
||||
launcher_menu_controller->make_bindings(context);
|
||||
config_menu_controller->make_bindings(context);
|
||||
|
||||
Rml::Debugger::Initialise(context);
|
||||
{
|
||||
const Rml::String directory = "assets/";
|
||||
|
||||
struct FontFace {
|
||||
const char* filename;
|
||||
bool fallback_face;
|
||||
};
|
||||
FontFace font_faces[] = {
|
||||
{"LatoLatin-Regular.ttf", false},
|
||||
{"ChiaroNormal.otf", false},
|
||||
{"ChiaroBold.otf", false},
|
||||
{"LatoLatin-Italic.ttf", false},
|
||||
{"LatoLatin-Bold.ttf", false},
|
||||
{"LatoLatin-BoldItalic.ttf", false},
|
||||
{"NotoEmoji-Regular.ttf", true},
|
||||
{"promptfont/promptfont.ttf", false},
|
||||
};
|
||||
|
||||
for (const FontFace& face : font_faces) {
|
||||
Rml::LoadFontFace(directory + face.filename, face.fallback_face);
|
||||
}
|
||||
}
|
||||
|
||||
launcher_menu_controller->load_document(context);
|
||||
config_menu_controller->load_document(context);
|
||||
}
|
||||
|
||||
void unload() {
|
||||
render_interface.reset();
|
||||
}
|
||||
|
||||
void update_primary_input(bool mouse_moved, bool non_mouse_interacted) {
|
||||
mouse_is_active_changed = false;
|
||||
if (non_mouse_interacted) {
|
||||
// controller newly interacted with
|
||||
if (mouse_is_active) {
|
||||
mouse_is_active = false;
|
||||
mouse_is_active_changed = true;
|
||||
}
|
||||
}
|
||||
else if (mouse_moved) {
|
||||
// mouse newly interacted with
|
||||
if (!mouse_is_active) {
|
||||
mouse_is_active = true;
|
||||
mouse_is_active_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_moved || non_mouse_interacted) {
|
||||
mouse_is_active_initialized = true;
|
||||
}
|
||||
|
||||
if (mouse_is_active_initialized) {
|
||||
recompui::set_cursor_visible(mouse_is_active);
|
||||
}
|
||||
|
||||
Rml::ElementDocument* current_document = top_input_document();
|
||||
if (current_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO is this needed?
|
||||
Rml::Element* window_el = current_document->GetElementById("window");
|
||||
if (window_el != nullptr) {
|
||||
if (mouse_is_active) {
|
||||
if (!window_el->HasAttribute("mouse-active")) {
|
||||
window_el->SetAttribute("mouse-active", true);
|
||||
}
|
||||
}
|
||||
else if (window_el->HasAttribute("mouse-active")) {
|
||||
window_el->RemoveAttribute("mouse-active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_focus(bool mouse_moved, bool non_mouse_interacted) {
|
||||
Rml::ElementDocument* current_document = top_input_document();
|
||||
|
||||
if (current_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cont_is_active || non_mouse_interacted) {
|
||||
if (non_mouse_interacted && !submenu_is_active) {
|
||||
auto focusedEl = current_document->GetFocusLeafNode();
|
||||
if (focusedEl == nullptr || RecompRml::CanFocusElement(focusedEl) != RecompRml::CanFocus::Yes) {
|
||||
Rml::Element* element = find_autofocus_element(current_document);
|
||||
if (element != nullptr) {
|
||||
element->Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was mouse motion, get the current hovered element (or its target if it points to one) and focus that if applicable.
|
||||
if (mouse_is_active) {
|
||||
if (mouse_is_active_changed) {
|
||||
Rml::Element* focused = current_document->GetFocusLeafNode();
|
||||
if (focused) focused->Blur();
|
||||
} else if (mouse_moved) {
|
||||
Rml::Element* hovered = context->GetHoverElement();
|
||||
if (hovered) {
|
||||
Rml::Element* hover_target = get_target(current_document, hovered);
|
||||
if (hover_target && can_focus(hover_target)) {
|
||||
prev_focused = hover_target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mouse_is_active) {
|
||||
if (!prev_focused || !can_focus(prev_focused)) {
|
||||
// Find the autofocus element in the tab chain
|
||||
Rml::Element* element = find_autofocus_element(current_document);
|
||||
if (element && can_focus(element)) {
|
||||
prev_focused = element;
|
||||
}
|
||||
}
|
||||
|
||||
if (mouse_is_active_changed && prev_focused && can_focus(prev_focused)) {
|
||||
prev_focused->Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void show_context(recompui::ContextId context) {
|
||||
if (std::find_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c){ return c.context == context; }) != opened_contexts.end()) {
|
||||
recompui::message_box("Attemped to show the same context twice");
|
||||
assert(false);
|
||||
}
|
||||
bool takes_input = context.takes_input();
|
||||
Rml::ElementDocument* document = context.get_document();
|
||||
opened_contexts.push_back(ContextDetails{
|
||||
.context = context,
|
||||
.document = document,
|
||||
.takes_input = takes_input
|
||||
});
|
||||
|
||||
document->PullToFront();
|
||||
document->Show();
|
||||
}
|
||||
|
||||
void hide_context(recompui::ContextId context) {
|
||||
auto remove_it = std::remove_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c) { return c.context == context; });
|
||||
if (remove_it == opened_contexts.end()) {
|
||||
recompui::message_box("Attemped to hide a context that isn't shown");
|
||||
assert(false);
|
||||
}
|
||||
opened_contexts.erase(remove_it, opened_contexts.end());
|
||||
|
||||
context.get_document()->Hide();
|
||||
}
|
||||
|
||||
void hide_all_contexts() {
|
||||
for (auto& context : opened_contexts) {
|
||||
context.document->Hide();
|
||||
}
|
||||
|
||||
opened_contexts.clear();
|
||||
}
|
||||
|
||||
bool is_context_open(recompui::ContextId context) {
|
||||
return std::find_if(opened_contexts.begin(), opened_contexts.end(), [context](auto& c){ return c.context == context; }) != opened_contexts.end();
|
||||
}
|
||||
|
||||
bool is_context_taking_input() {
|
||||
return std::find_if(opened_contexts.begin(), opened_contexts.end(), [](auto& c){ return c.takes_input; }) != opened_contexts.end();
|
||||
}
|
||||
|
||||
bool is_any_context_open() {
|
||||
return !opened_contexts.empty();
|
||||
}
|
||||
|
||||
Rml::ElementDocument* top_input_document() {
|
||||
// Iterate backwards and stop at the first context that takes input.
|
||||
for (auto it = opened_contexts.rbegin(); it != opened_contexts.rend(); it++) {
|
||||
if (it->takes_input) {
|
||||
return it->document;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<UIState> ui_state;
|
||||
std::recursive_mutex ui_state_mutex{};
|
||||
|
||||
// TODO make this not be global
|
||||
extern SDL_Window* window;
|
||||
|
||||
void recompui::get_window_size(int& width, int& height) {
|
||||
SDL_GetWindowSizeInPixels(window, &width, &height);
|
||||
}
|
||||
|
||||
inline const std::string read_file_to_string(std::filesystem::path path) {
|
||||
std::ifstream stream = std::ifstream{path};
|
||||
std::ostringstream ss;
|
||||
ss << stream.rdbuf();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
|
||||
#if defined(__linux__)
|
||||
std::locale::global(std::locale::classic());
|
||||
#endif
|
||||
ui_state = std::make_unique<UIState>(window, interface, device);
|
||||
}
|
||||
|
||||
moodycamel::ConcurrentQueue<SDL_Event> ui_event_queue{};
|
||||
|
||||
void recompui::queue_event(const SDL_Event& event) {
|
||||
ui_event_queue.enqueue(event);
|
||||
}
|
||||
|
||||
bool recompui::try_deque_event(SDL_Event& out) {
|
||||
return ui_event_queue.try_dequeue(out);
|
||||
}
|
||||
|
||||
int cont_button_to_key(SDL_ControllerButtonEvent& button) {
|
||||
// Configurable accept button in menu
|
||||
auto menuAcceptBinding0 = recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuAcceptBinding1 = recomp::get_input_binding(recomp::GameInput::ACCEPT_MENU, 1, recomp::InputDevice::Controller);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuAcceptBinding0.input_type != 0 && button.button == menuAcceptBinding0.input_id) ||
|
||||
(menuAcceptBinding1.input_type != 0 && button.button == menuAcceptBinding1.input_id)) {
|
||||
return SDLK_RETURN;
|
||||
}
|
||||
|
||||
// Configurable apply button in menu
|
||||
auto menuApplyBinding0 = recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuApplyBinding1 = recomp::get_input_binding(recomp::GameInput::APPLY_MENU, 1, recomp::InputDevice::Controller);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuApplyBinding0.input_type != 0 && button.button == menuApplyBinding0.input_id) ||
|
||||
(menuApplyBinding1.input_type != 0 && button.button == menuApplyBinding1.input_id)) {
|
||||
return SDLK_f;
|
||||
}
|
||||
|
||||
// Allows closing the menu
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuToggleBinding0.input_type != 0 && button.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != 0 && button.button == menuToggleBinding1.input_id)) {
|
||||
return SDLK_ESCAPE;
|
||||
}
|
||||
|
||||
switch (button.button) {
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||
return SDLK_UP;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||
return SDLK_DOWN;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
||||
return SDLK_LEFT;
|
||||
case SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||
return SDLK_RIGHT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int cont_axis_to_key(SDL_ControllerAxisEvent& axis, float value) {
|
||||
switch (axis.axis) {
|
||||
case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY:
|
||||
if (value < 0) return SDLK_UP;
|
||||
return SDLK_DOWN;
|
||||
case SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX:
|
||||
if (value >= 0) return SDLK_RIGHT;
|
||||
return SDLK_LEFT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void apply_background_input_mode() {
|
||||
static recomp::BackgroundInputMode last_input_mode = recomp::BackgroundInputMode::OptionCount;
|
||||
|
||||
recomp::BackgroundInputMode cur_input_mode = recomp::get_background_input_mode();
|
||||
|
||||
if (last_input_mode != cur_input_mode) {
|
||||
SDL_SetHint(
|
||||
SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
|
||||
cur_input_mode == recomp::BackgroundInputMode::On
|
||||
? "1"
|
||||
: "0"
|
||||
);
|
||||
}
|
||||
last_input_mode = cur_input_mode;
|
||||
}
|
||||
|
||||
bool recompui::get_cont_active() {
|
||||
return ui_state->cont_is_active;
|
||||
}
|
||||
|
||||
void recompui::set_cont_active(bool active) {
|
||||
ui_state->cont_is_active = active;
|
||||
}
|
||||
|
||||
void recompui::activate_mouse() {
|
||||
ui_state->update_primary_input(true, false);
|
||||
ui_state->update_focus(true, false);
|
||||
}
|
||||
|
||||
void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* swap_chain_framebuffer) {
|
||||
|
||||
apply_background_input_mode();
|
||||
|
||||
// Return early if the ui context has been destroyed already.
|
||||
if (!ui_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return to the launcher if no menu is open and the game isn't started.
|
||||
if (!recompui::is_any_context_open() && !ultramodern::is_game_started()) {
|
||||
recompui::show_context(recompui::get_launcher_context_id(), "");
|
||||
}
|
||||
|
||||
std::lock_guard lock{ ui_state_mutex };
|
||||
|
||||
SDL_Event cur_event{};
|
||||
|
||||
bool mouse_moved = false;
|
||||
bool mouse_clicked = false;
|
||||
bool non_mouse_interacted = false;
|
||||
bool cont_interacted = false;
|
||||
bool kb_interacted = false;
|
||||
|
||||
bool config_was_open = recompui::is_context_open(recompui::get_config_context_id());
|
||||
|
||||
while (recompui::try_deque_event(cur_event)) {
|
||||
bool context_taking_input = recompui::is_context_taking_input();
|
||||
if (!recomp::all_input_disabled()) {
|
||||
// Implement some additional behavior for specific events on top of what RmlUi normally does with them.
|
||||
switch (cur_event.type) {
|
||||
case SDL_EventType::SDL_MOUSEMOTION: {
|
||||
int *last_mouse_pos = ui_state->last_active_mouse_position;
|
||||
|
||||
if (!ui_state->mouse_is_active) {
|
||||
float xD = cur_event.motion.x - last_mouse_pos[0];
|
||||
float yD = cur_event.motion.y - last_mouse_pos[1];
|
||||
if (sqrt(xD * xD + yD * yD) < 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
last_mouse_pos[0] = cur_event.motion.x;
|
||||
last_mouse_pos[1] = cur_event.motion.y;
|
||||
|
||||
// if controller is the primary input, don't use mouse movement to allow cursor to reactivate
|
||||
if (recompui::get_cont_active()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case SDL_EventType::SDL_MOUSEBUTTONDOWN:
|
||||
mouse_moved = true;
|
||||
mouse_clicked = true;
|
||||
break;
|
||||
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: {
|
||||
int rml_key = cont_button_to_key(cur_event.cbutton);
|
||||
if (context_taking_input && rml_key) {
|
||||
ui_state->context->ProcessKeyDown(RmlSDL::ConvertKey(rml_key), 0);
|
||||
}
|
||||
non_mouse_interacted = true;
|
||||
cont_interacted = true;
|
||||
break;
|
||||
}
|
||||
case SDL_EventType::SDL_KEYDOWN:
|
||||
non_mouse_interacted = true;
|
||||
kb_interacted = true;
|
||||
break;
|
||||
case SDL_EventType::SDL_USEREVENT:
|
||||
if (cur_event.user.code == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY) {
|
||||
ui_state->await_stick_return_y = true;
|
||||
} else if (cur_event.user.code == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX) {
|
||||
ui_state->await_stick_return_x = true;
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERAXISMOTION:
|
||||
SDL_ControllerAxisEvent* axis_event = &cur_event.caxis;
|
||||
if (axis_event->axis != SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY && axis_event->axis != SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTX) {
|
||||
break;
|
||||
}
|
||||
|
||||
float axis_value = axis_event->value * (1 / 32768.0f);
|
||||
bool* await_stick_return = axis_event->axis == SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_LEFTY
|
||||
? &ui_state->await_stick_return_y
|
||||
: &ui_state->await_stick_return_x;
|
||||
if (fabsf(axis_value) > 0.5f) {
|
||||
if (!*await_stick_return) {
|
||||
*await_stick_return = true;
|
||||
non_mouse_interacted = true;
|
||||
int rml_key = cont_axis_to_key(cur_event.caxis, axis_value);
|
||||
if (context_taking_input && rml_key) {
|
||||
ui_state->context->ProcessKeyDown(RmlSDL::ConvertKey(rml_key), 0);
|
||||
}
|
||||
}
|
||||
non_mouse_interacted = true;
|
||||
cont_interacted = true;
|
||||
}
|
||||
else if (*await_stick_return && fabsf(axis_value) < 0.15f) {
|
||||
*await_stick_return = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (context_taking_input) {
|
||||
RmlSDL::InputEventHandler(ui_state->context, cur_event);
|
||||
}
|
||||
}
|
||||
|
||||
// If the config menu isn't open and the game has been started and either the escape key or select button are pressed, open the config menu.
|
||||
if (!config_was_open && ultramodern::is_game_started()) {
|
||||
bool open_config = false;
|
||||
|
||||
switch (cur_event.type) {
|
||||
case SDL_EventType::SDL_KEYDOWN:
|
||||
if (cur_event.key.keysym.scancode == SDL_Scancode::SDL_SCANCODE_ESCAPE) {
|
||||
open_config = true;
|
||||
}
|
||||
break;
|
||||
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN:
|
||||
auto menuToggleBinding0 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 0, recomp::InputDevice::Controller);
|
||||
auto menuToggleBinding1 = recomp::get_input_binding(recomp::GameInput::TOGGLE_MENU, 1, recomp::InputDevice::Controller);
|
||||
// note - magic number: 0 is InputType::None
|
||||
if ((menuToggleBinding0.input_type != 0 && cur_event.cbutton.button == menuToggleBinding0.input_id) ||
|
||||
(menuToggleBinding1.input_type != 0 && cur_event.cbutton.button == menuToggleBinding1.input_id)) {
|
||||
open_config = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
recompui::hide_all_contexts();
|
||||
if (open_config) {
|
||||
recompui::show_context(recompui::get_config_context_id(), "");
|
||||
}
|
||||
}
|
||||
} // end dequeue event loop
|
||||
|
||||
if (cont_interacted || kb_interacted || mouse_clicked) {
|
||||
recompui::set_cont_active(cont_interacted);
|
||||
}
|
||||
recomp::config_menu_set_cont_or_kb(ui_state->cont_is_active);
|
||||
|
||||
recomp::InputField scanned_field = recomp::get_scanned_input();
|
||||
if (scanned_field != recomp::InputField{}) {
|
||||
recomp::finish_scanning_input(scanned_field);
|
||||
}
|
||||
|
||||
ui_state->update_primary_input(mouse_moved, non_mouse_interacted);
|
||||
ui_state->update_focus(mouse_moved, non_mouse_interacted);
|
||||
|
||||
if (recompui::is_any_context_open()) {
|
||||
int width = swap_chain_framebuffer->getWidth();
|
||||
int height = swap_chain_framebuffer->getHeight();
|
||||
|
||||
// Scale the UI based on the window size with 1080 vertical resolution as the reference point.
|
||||
ui_state->context->SetDensityIndependentPixelRatio((height) / 1080.0f);
|
||||
|
||||
ui_state->render_interface.start(command_list, width, height);
|
||||
|
||||
static int prev_width = 0;
|
||||
static int prev_height = 0;
|
||||
|
||||
if (prev_width != width || prev_height != height) {
|
||||
ui_state->context->SetDimensions({ width, height });
|
||||
}
|
||||
prev_width = width;
|
||||
prev_height = height;
|
||||
|
||||
ui_state->context->Update();
|
||||
ui_state->context->Render();
|
||||
ui_state->render_interface.end(command_list, swap_chain_framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void deinit_hook() {
|
||||
recompui::destroy_all_contexts();
|
||||
|
||||
std::lock_guard lock {ui_state_mutex};
|
||||
Rml::Debugger::Shutdown();
|
||||
Rml::Shutdown();
|
||||
ui_state->unload();
|
||||
ui_state.reset();
|
||||
}
|
||||
|
||||
void recompui::set_render_hooks() {
|
||||
RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook);
|
||||
}
|
||||
|
||||
void recompui::message_box(const char* msg) {
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
|
||||
printf("[ERROR] %s\n", msg);
|
||||
}
|
||||
|
||||
void recompui::show_context(ContextId context, std::string_view param) {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
// TODO call the context's on_show callback with the param.
|
||||
ui_state->show_context(context);
|
||||
}
|
||||
|
||||
void recompui::hide_context(ContextId context) {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
ui_state->hide_context(context);
|
||||
}
|
||||
|
||||
void recompui::hide_all_contexts() {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (ui_state) {
|
||||
ui_state->hide_all_contexts();
|
||||
}
|
||||
}
|
||||
|
||||
bool recompui::is_context_open(ContextId context) {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (!ui_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ui_state->is_context_open(context);
|
||||
}
|
||||
|
||||
bool recompui::is_context_taking_input() {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (!ui_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ui_state->is_context_taking_input();
|
||||
}
|
||||
|
||||
bool recompui::is_any_context_open() {
|
||||
std::lock_guard lock{ui_state_mutex};
|
||||
|
||||
if (!ui_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ui_state->is_any_context_open();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user