fix and improve kb/cont config tab > panel navigation

prompt component - wip
This commit is contained in:
thecozies 2024-04-18 09:42:39 -05:00
parent 2b96bb8be9
commit 95ab0074ea
11 changed files with 260 additions and 8 deletions

View File

@ -143,6 +143,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_launcher.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_config.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp ${CMAKE_SOURCE_DIR}/src/ui/ui_color_hack.cpp
${CMAKE_SOURCE_DIR}/src/ui/ui_rml_hacks.cpp
${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontEngineInterfaceScaled.cpp
${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFace.cpp ${CMAKE_SOURCE_DIR}/src/ui/FontEngineScaled/FontFace.cpp

View File

@ -0,0 +1,30 @@
<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-button-left"
class="button button--success"
style="nav-left: none; nav-right: #prompt-button-right"
>
<div class="button__label">{{ promptConfirmLabel }}</div>
</button>
<button
id="prompt-button-right"
class="button button--error"
style="nav-right: none; nav-left: #prompt-button-left"
>
<div class="button__label">{{ promptCancelLabel }}</div>
</button>
</div>
</div>
</div>
</body>
</template>

View File

@ -28,6 +28,7 @@
<link type="text/template" href="config_menu/graphics.rml" /> <link type="text/template" href="config_menu/graphics.rml" />
<link type="text/template" href="config_menu/sound.rml" /> <link type="text/template" href="config_menu/sound.rml" />
<link type="text/template" href="config_menu/debug.rml" /> <link type="text/template" href="config_menu/debug.rml" />
<link type="text/template" href="components/prompt.rml" />
</head> </head>
<body class="window"> <body class="window">
<!-- <handle move_target="#document"> --> <!-- <handle move_target="#document"> -->
@ -42,7 +43,7 @@
<panel class="config" data-model="general_model"> <panel class="config" data-model="general_model">
<template src="config-menu__general" /> <template src="config-menu__general" />
</panel> </panel>
<tab class="tab"> <tab class="tab" id="tab_controls">
<div>Controls</div> <div>Controls</div>
<div class="tab__indicator"></div> <div class="tab__indicator"></div>
</tab> </tab>
@ -63,7 +64,7 @@
<panel class="config" data-model="sound_options_model"> <panel class="config" data-model="sound_options_model">
<template src="config-menu__sound" /> <template src="config-menu__sound" />
</panel> </panel>
<tab class="tab" data-model="debug_model" data-if="debug_enabled"> <tab class="tab" data-model="debug_model" data-if="debug_enabled" id="tab_debug">
<div>Debug</div> <div>Debug</div>
<div class="tab__indicator"></div> <div class="tab__indicator"></div>
</tab> </tab>
@ -100,6 +101,15 @@
<label><span style="font-family:promptfont;">&#x21A7;</span> Accept</label> --> <label><span style="font-family:promptfont;">&#x21A7;</span> Accept</label> -->
</div> </div>
</div> </div>
<div
data-alias-promptOpen="1"
data-alias-promptHeader="'test header'"
data-alias-promptContent="'This allows templates to be used as reusable components within data models. By wrapping the inline template in an element that defines variable name aliases, the template can refer to any outside variable by a fixed name.'"
data-alias-promptConfirmLabel="'Confirm'"
data-alias-promptCancelLabel="'Cancel'"
>
<template src="prompt"/>
</div>
</div> </div>
<!-- </handle> --> <!-- </handle> -->
<!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> --> <!-- <handle size_target="#document" style="position: absolute; width: 16dp; height: 16dp; bottom: 0px; right: 0px; cursor: resize;"></handle> -->

View File

@ -10,7 +10,7 @@
id="cont_kb_toggle" id="cont_kb_toggle"
data-class-toggle--checked="input_device_is_keyboard" data-class-toggle--checked="input_device_is_keyboard"
onclick="toggle_input_device" onclick="toggle_input_device"
style="nav-down:#input_row_button_0_0" style="nav-down: #input_row_button_0_0; nav-up: #tab_controls"
> >
<div class="toggle__border" /> <div class="toggle__border" />
<div class="toggle__floater" /> <div class="toggle__floater" />

View File

@ -16,7 +16,7 @@
<div class="config-debug__option-controls"> <div class="config-debug__option-controls">
<div class="config-debug__select-wrapper"> <div class="config-debug__select-wrapper">
<div class="config-debug__select-label"><div>Region</div></div> <div class="config-debug__select-label"><div>Region</div></div>
<select data-value="area_index" onchange="area_index_changed"> <select data-value="area_index" onchange="area_index_changed" id="area_index_select" style="nav-up: #tab_debug">
<option data-for="area, i : area_names" data-attr-value="i">{{area}}</option> <option data-for="area, i : area_names" data-attr-value="i">{{area}}</option>
</select> </select>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,57 @@
.prompt {
&__overlay {
pointer-events: none;
}
&__overlay,
&__content-wrapper {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&__content-wrapper {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
&__content {
display: flex;
flex-direction: column;
position: relative;
margin: auto;
flex: 1 1 100%;
max-width: space(820);
width: 100%;
height: auto;
background: $color-modal-overlay;
border-radius: $border-radius-modal;
border-color: $color-border;
border-width: $border-width-thickness;
h3, p {
margin: space(16);
}
}
&__controls {
display: flex;
padding: space(16);
border-top-color: $color-border-soft;
border-top-width: $border-width-thickness;
.button {
nav-up: none;
nav-down: none;
}
}
}

View File

@ -9,4 +9,5 @@
@import "./MenuListItem"; @import "./MenuListItem";
@import "./SubtitleTitle"; @import "./SubtitleTitle";
@import "./Toggle"; @import "./Toggle";
@import "./BottomLeft" @import "./BottomLeft";
@import "./Prompt";

View File

@ -8,6 +8,7 @@
#include "recomp_ui.h" #include "recomp_ui.h"
#include "recomp_input.h" #include "recomp_input.h"
#include "recomp_game.h" #include "recomp_game.h"
#include "ui_rml_hacks.hpp"
#include "concurrentqueue.h" #include "concurrentqueue.h"
@ -956,6 +957,39 @@ struct UIContext {
void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) { void add_menu(recomp::Menu menu, std::unique_ptr<recomp::MenuController>&& controller) {
menus.emplace(menu, std::move(controller)); menus.emplace(menu, std::move(controller));
} }
void update_config_menu_loop(bool menu_changed) {
static int prevTab = -1;
if (menu_changed) prevTab = -1;
Rml::ElementTabSet *tabset = (Rml::ElementTabSet *)current_document->GetElementById("config_tabset");
if (tabset == nullptr) return;
int curTab = tabset->GetActiveTab();
if (curTab == prevTab) return;
prevTab = curTab;
Rml::ElementList panels;
current_document->GetElementsByTagName(panels, "panel");
Rml::Element *firstFocus = nullptr;
for (const auto& panel : panels) {
if (panel->IsVisible()) {
firstFocus = RecompRml::FindNextTabElement(panel, true);
break;
}
}
if (!firstFocus) return;
Rml::String id = firstFocus->GetId();
if (id.empty()) return;
Rml::ElementList tabs;
current_document->GetElementsByTagName(tabs, "tab");
for (const auto& tab : tabs) {
tab->SetProperty("nav-down", "#" + id);
}
}
} rml; } rml;
}; };
@ -1079,7 +1113,6 @@ int cont_axis_to_key(SDL_ControllerAxisEvent& axis, float value) {
return 0; return 0;
} }
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};
@ -1104,7 +1137,8 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
prev_menu = recomp::Menu::None; prev_menu = recomp::Menu::None;
} }
if (cur_menu != prev_menu) { bool menu_changed = cur_menu != prev_menu;
if (menu_changed) {
ui_context->rml.swap_document(cur_menu); ui_context->rml.swap_document(cur_menu);
} }
@ -1123,6 +1157,10 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
bool cont_interacted = false; bool cont_interacted = false;
bool kb_interacted = false; bool kb_interacted = false;
if (cur_menu == recomp::Menu::Config) {
ui_context->rml.update_config_menu_loop(menu_changed);
}
while (recomp::try_deque_event(cur_event)) { while (recomp::try_deque_event(cur_event)) {
bool menu_is_open = cur_menu != recomp::Menu::None; bool menu_is_open = cur_menu != recomp::Menu::None;

106
src/ui/ui_rml_hacks.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "recomp_ui.h"
#include "RmlUi/Core.h"
#include "ui_rml_hacks.hpp"
//! these are hidden methods not exposed by RmlUi
//! they may need to be updated eventually with RmlUi
enum class CanFocus { Yes, No, NoAndNoChildren };
CanFocus CanFocusElement(Rml::Element* element)
{
if (!element->IsVisible())
return CanFocus::NoAndNoChildren;
const Rml::ComputedValues& computed = element->GetComputedValues();
if (computed.focus() == Rml::Style::Focus::None)
return CanFocus::NoAndNoChildren;
if (computed.tab_index() == Rml::Style::TabIndex::Auto)
return CanFocus::Yes;
return CanFocus::No;
}
Rml::Element* SearchFocusSubtree(Rml::Element* element, bool forward)
{
auto can_focus = CanFocusElement(element);
if (can_focus == CanFocus::Yes)
return element;
else if (can_focus == CanFocus::NoAndNoChildren)
return nullptr;
for (int i = 0; i < element->GetNumChildren(); i++)
{
int child_index = i;
if (!forward)
child_index = element->GetNumChildren() - i - 1;
if (Rml::Element* result = SearchFocusSubtree(element->GetChild(child_index), forward))
return result;
}
return nullptr;
}
Rml::Element* RecompRml::FindNextTabElement(Rml::Element* current_element, bool forward)
{
// This algorithm is quite sneaky, I originally thought a depth first search would work, but it appears not. What is
// required is to cut the tree in half along the nodes from current_element up the root and then either traverse the
// tree in a clockwise or anticlock wise direction depending if you're searching forward or backward respectively.
// If we're searching forward, check the immediate children of this node first off.
if (forward)
{
for (int i = 0; i < current_element->GetNumChildren(); i++)
if (Rml::Element* result = SearchFocusSubtree(current_element->GetChild(i), forward))
return result;
}
// Now walk up the tree, testing either the bottom or top
// of the tree, depending on whether we're going forward
// or backward respectively.
bool search_enabled = false;
Rml::Element* document = current_element->GetOwnerDocument();
Rml::Element* child = current_element;
Rml::Element* parent = current_element->GetParentNode();
while (child != document)
{
const int num_children = parent->GetNumChildren();
for (int i = 0; i < num_children; i++)
{
// Calculate index into children
const int child_index = forward ? i : (num_children - i - 1);
Rml::Element* search_child = parent->GetChild(child_index);
// Do a search if its enabled
if (search_enabled)
if (Rml::Element* result = SearchFocusSubtree(search_child, forward))
return result;
// Enable searching when we reach the child.
if (search_child == child)
search_enabled = true;
}
// Advance up the tree
child = parent;
parent = parent->GetParentNode();
search_enabled = false;
}
// We could not find anything to focus along this direction.
// If we can focus the document, then focus that now.
if (current_element != document && CanFocusElement(document) == CanFocus::Yes)
return document;
// Otherwise, search the entire document tree. This way we will wrap around.
const int num_children = document->GetNumChildren();
for (int i = 0; i < num_children; i++)
{
const int child_index = forward ? i : (num_children - i - 1);
if (Rml::Element* result = SearchFocusSubtree(document->GetChild(child_index), forward))
return result;
}
return nullptr;
}

9
src/ui/ui_rml_hacks.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef UI_RML_HACKS_H
#define UI_RML_HACKS_H
#include "RmlUi/Core.h"
namespace RecompRml {
Rml::Element* FindNextTabElement(Rml::Element* current_element, bool forward);
}
#endif