Cont/kb/mouse focus priorities and behaviors reworked (#15)

* ensure focus after cont/kb input + enable mouse on SDL_QUIT + force focus to prompt while open

* only force prompt focus if mouse is not active

* default kb input, mouse click switches to kb focused input
This commit is contained in:
thecozies 2024-05-02 00:51:09 -05:00 committed by GitHub
parent 956db3366f
commit 7491eabde0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 95 additions and 30 deletions

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,6 @@
} }
.centered-page__modal { .centered-page__modal {
@extend %nav-all;
display: flex; display: flex;
position: relative; position: relative;
flex: 1 1 100%; flex: 1 1 100%;

View File

@ -5,7 +5,7 @@ $prompt-space: 24;
.prompt { .prompt {
&__overlay { &__overlay {
background-color: $color-bg-overlay; background-color: $color-bg-overlay;
pointer-events: none; pointer-events: auto;
} }
&__overlay, &__overlay,

View File

@ -78,7 +78,7 @@ namespace recomp {
void stop_scanning_input(); void stop_scanning_input();
void finish_scanning_input(InputField scanned_field); void finish_scanning_input(InputField scanned_field);
void cancel_scanning_input(); void cancel_scanning_input();
void set_cont_or_kb(bool cont_interacted); void config_menu_set_cont_or_kb(bool cont_interacted);
InputField get_scanned_input(); InputField get_scanned_input();
struct DefaultN64Mappings { struct DefaultN64Mappings {

View File

@ -116,6 +116,8 @@ namespace recomp {
PromptContext *get_prompt_context(void); PromptContext *get_prompt_context(void);
bool get_cont_active(void); bool get_cont_active(void);
void set_cont_active(bool active);
void activate_mouse();
} }
#endif #endif

View File

@ -139,6 +139,7 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
} }
recomp::open_quit_game_prompt(); recomp::open_quit_game_prompt();
recomp::activate_mouse();
break; break;
} }
case SDL_EventType::SDL_MOUSEWHEEL: case SDL_EventType::SDL_MOUSEWHEEL:

View File

@ -181,18 +181,20 @@ void recomp::cancel_scanning_input() {
controls_model_handle.DirtyVariable("active_binding_slot"); controls_model_handle.DirtyVariable("active_binding_slot");
} }
void recomp::set_cont_or_kb(bool cont_interacted) { void recomp::config_menu_set_cont_or_kb(bool cont_interacted) {
if (nav_help_model_handle && cont_active != cont_interacted) { if (cont_active != cont_interacted) {
cont_active = cont_interacted; cont_active = cont_interacted;
nav_help_model_handle.DirtyVariable("nav_help__navigate");
nav_help_model_handle.DirtyVariable("nav_help__accept");
graphics_model_handle.DirtyVariable("gfx_help__apply");
nav_help_model_handle.DirtyVariable("nav_help__exit");
}
}
bool recomp::get_cont_active() { if (nav_help_model_handle) {
return cont_active; nav_help_model_handle.DirtyVariable("nav_help__navigate");
nav_help_model_handle.DirtyVariable("nav_help__accept");
nav_help_model_handle.DirtyVariable("nav_help__exit");
}
if (graphics_model_handle) {
graphics_model_handle.DirtyVariable("gfx_help__apply");
}
}
} }
void close_config_menu_impl() { void close_config_menu_impl() {
@ -890,6 +892,8 @@ public:
} }
void make_bindings(Rml::Context* context) override { void make_bindings(Rml::Context* context) override {
// initially set cont state for ui help
recomp::config_menu_set_cont_or_kb(recomp::get_cont_active());
make_nav_help_bindings(context); make_nav_help_bindings(context);
make_general_bindings(context); make_general_bindings(context);
make_controls_bindings(context); make_controls_bindings(context);

View File

@ -786,6 +786,7 @@ struct UIContext {
public: public:
bool mouse_is_active_initialized = false; bool mouse_is_active_initialized = false;
bool mouse_is_active = false; bool mouse_is_active = false;
bool cont_is_active = false;
bool await_stick_return_x = false; bool await_stick_return_x = false;
bool await_stick_return_y = false; bool await_stick_return_y = false;
int last_active_mouse_position[2] = {0, 0}; int last_active_mouse_position[2] = {0, 0};
@ -929,11 +930,25 @@ struct UIContext {
} }
} }
void update_focus(bool mouse_moved) { void update_focus(bool mouse_moved, bool non_mouse_interacted) {
if (current_document == nullptr) { if (current_document == nullptr) {
return; return;
} }
if (cont_is_active || non_mouse_interacted) {
if (non_mouse_interacted) {
auto focusedEl = current_document->GetFocusLeafNode();
Rml::Variant* ti = focusedEl == nullptr ? nullptr : focusedEl->GetAttribute("tab-index");
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 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) {
if (mouse_is_active_changed) { if (mouse_is_active_changed) {
@ -1034,14 +1049,38 @@ struct UIContext {
} }
wasShowingPrompt = ctx->open; wasShowingPrompt = ctx->open;
if (!ctx->shouldFocus) return; if (!ctx->open) {
return;
}
Rml::Element* focused = current_document->GetFocusLeafNode(); Rml::Element* focused = current_document->GetFocusLeafNode();
if (focused) focused->Blur(); // Check if unfocused or current focus isn't either prompt button
if (mouse_is_active == false) {
if (
focused == nullptr || (
focused != current_document->GetElementById("prompt__cancel-button") &&
focused != current_document->GetElementById("prompt__confirm-button")
)
) {
ctx->shouldFocus = true;
}
}
if (!ctx->shouldFocus) {
return;
}
if (focused != nullptr) {
focused->Blur();
}
Rml::Element *targetButton = current_document->GetElementById( Rml::Element *targetButton = current_document->GetElementById(
ctx->focusOnCancel ? "prompt__cancel-button" : "prompt__confirm-button"); ctx->focusOnCancel ? "prompt__cancel-button" : "prompt__confirm-button");
if (targetButton == nullptr) {
return;
}
targetButton->Focus(true); targetButton->Focus(true);
ctx->shouldFocus = false; ctx->shouldFocus = false;
@ -1189,6 +1228,19 @@ void apply_background_input_mode() {
last_input_mode = cur_input_mode; last_input_mode = cur_input_mode;
} }
bool recomp::get_cont_active() {
return ui_context->rml.cont_is_active;
}
void recomp::set_cont_active(bool active) {
ui_context->rml.cont_is_active = active;
}
void recomp::activate_mouse() {
ui_context->rml.update_primary_input(true, false);
ui_context->rml.update_focus(true, false);
}
void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* swap_chain_framebuffer) { void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* swap_chain_framebuffer) {
std::lock_guard lock {ui_context_mutex}; std::lock_guard lock {ui_context_mutex};
@ -1231,6 +1283,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
SDL_Event cur_event{}; SDL_Event cur_event{};
bool mouse_moved = false; bool mouse_moved = false;
bool mouse_clicked = false;
bool non_mouse_interacted = false; bool non_mouse_interacted = false;
bool cont_interacted = false; bool cont_interacted = false;
bool kb_interacted = false; bool kb_interacted = false;
@ -1269,6 +1322,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
// fallthrough // fallthrough
case SDL_EventType::SDL_MOUSEBUTTONDOWN: case SDL_EventType::SDL_MOUSEBUTTONDOWN:
mouse_moved = true; mouse_moved = true;
mouse_clicked = true;
break; break;
case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: { case SDL_EventType::SDL_CONTROLLERBUTTONDOWN: {
@ -1342,9 +1396,10 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
} }
} // end dequeue event loop } // end dequeue event loop
if (cont_interacted || kb_interacted) { if (cont_interacted || kb_interacted || mouse_clicked) {
recomp::set_cont_or_kb(cont_interacted); recomp::set_cont_active(cont_interacted);
} }
recomp::config_menu_set_cont_or_kb(ui_context->rml.cont_is_active);
recomp::InputField scanned_field = recomp::get_scanned_input(); recomp::InputField scanned_field = recomp::get_scanned_input();
if (scanned_field != recomp::InputField{}) { if (scanned_field != recomp::InputField{}) {
@ -1352,7 +1407,7 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
} }
ui_context->rml.update_primary_input(mouse_moved, non_mouse_interacted); ui_context->rml.update_primary_input(mouse_moved, non_mouse_interacted);
ui_context->rml.update_focus(mouse_moved); ui_context->rml.update_focus(mouse_moved, non_mouse_interacted);
if (cur_menu != recomp::Menu::None) { if (cur_menu != recomp::Menu::None) {
int width = swap_chain_framebuffer->getWidth(); int width = swap_chain_framebuffer->getWidth();

View File

@ -5,28 +5,28 @@
//! these are hidden methods not exposed by RmlUi //! these are hidden methods not exposed by RmlUi
//! they may need to be updated eventually with RmlUi //! they may need to be updated eventually with RmlUi
enum class CanFocus { Yes, No, NoAndNoChildren }; RecompRml::CanFocus RecompRml::CanFocusElement(Rml::Element* element)
CanFocus CanFocusElement(Rml::Element* element)
{ {
if (!element->IsVisible()) if (!element->IsVisible())
return CanFocus::NoAndNoChildren; return RecompRml::CanFocus::NoAndNoChildren;
const Rml::ComputedValues& computed = element->GetComputedValues(); const Rml::ComputedValues& computed = element->GetComputedValues();
if (computed.focus() == Rml::Style::Focus::None) if (computed.focus() == Rml::Style::Focus::None)
return CanFocus::NoAndNoChildren; return RecompRml::CanFocus::NoAndNoChildren;
if (computed.tab_index() == Rml::Style::TabIndex::Auto) if (computed.tab_index() == Rml::Style::TabIndex::Auto)
return CanFocus::Yes; return RecompRml::CanFocus::Yes;
return CanFocus::No; return RecompRml::CanFocus::No;
} }
Rml::Element* SearchFocusSubtree(Rml::Element* element, bool forward) Rml::Element* SearchFocusSubtree(Rml::Element* element, bool forward)
{ {
auto can_focus = CanFocusElement(element); auto can_focus = RecompRml::CanFocusElement(element);
if (can_focus == CanFocus::Yes) if (can_focus == RecompRml::CanFocus::Yes)
return element; return element;
else if (can_focus == CanFocus::NoAndNoChildren) else if (can_focus == RecompRml::CanFocus::NoAndNoChildren)
return nullptr; return nullptr;
for (int i = 0; i < element->GetNumChildren(); i++) for (int i = 0; i < element->GetNumChildren(); i++)
@ -90,7 +90,7 @@ Rml::Element* RecompRml::FindNextTabElement(Rml::Element* current_element, bool
// We could not find anything to focus along this direction. // We could not find anything to focus along this direction.
// If we can focus the document, then focus that now. // If we can focus the document, then focus that now.
if (current_element != document && CanFocusElement(document) == CanFocus::Yes) if (current_element != document && RecompRml::CanFocusElement(document) == RecompRml::CanFocus::Yes)
return document; return document;
// Otherwise, search the entire document tree. This way we will wrap around. // Otherwise, search the entire document tree. This way we will wrap around.

View File

@ -4,6 +4,10 @@
#include "RmlUi/Core.h" #include "RmlUi/Core.h"
namespace RecompRml { namespace RecompRml {
Rml::Element* FindNextTabElement(Rml::Element* current_element, bool forward); Rml::Element* FindNextTabElement(Rml::Element* current_element, bool forward);
enum class CanFocus { Yes, No, NoAndNoChildren };
CanFocus CanFocusElement(Rml::Element* element);
} }
#endif #endif