mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-28 07:54:51 -04:00
UI: 3-finger tap to toggle menu on mobile
This commit is contained in:
Vendored
+1
-1
Submodule extern/aurora updated: 6dd9aa2257...5973fa05f8
@@ -154,7 +154,6 @@ struct UserSettings {
|
||||
ConfigVar<bool> showPipelineCompilation;
|
||||
ConfigVar<bool> wasPresetChosen;
|
||||
ConfigVar<bool> enableCrashReporting;
|
||||
ConfigVar<bool> duskMenuOpen;
|
||||
ConfigVar<int> cardFileType;
|
||||
} backend;
|
||||
};
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
#include "fmt/format.h"
|
||||
#include "ImGuiConsole.hpp"
|
||||
#include "JSystem/JUtility/JUTGamePad.h"
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_mouse.h"
|
||||
#include "aurora/lib/window.hpp"
|
||||
#include "dusk/achievements.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/config.hpp"
|
||||
@@ -35,14 +33,6 @@ using namespace std::string_literals;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace {
|
||||
ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) {
|
||||
const AuroraWindowSize size = aurora::window::get_window_size();
|
||||
return ImVec2{
|
||||
touch.x * static_cast<float>(size.width),
|
||||
touch.y * static_cast<float>(size.height),
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) {
|
||||
while (window != nullptr) {
|
||||
const bool canScrollX = window->ScrollMax.x > 0.0f;
|
||||
@@ -241,48 +231,7 @@ namespace dusk {
|
||||
ImGuiConsole::ImGuiConsole() {}
|
||||
|
||||
void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) {
|
||||
if (!IsGameLaunched) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
if (!m_touchTapActive) {
|
||||
m_touchTapActive = true;
|
||||
m_touchTapMoved = false;
|
||||
m_touchTapFingerId = event.tfinger.fingerID;
|
||||
m_touchTapStartPos = TouchEventToScreenPos(event.tfinger);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
|
||||
const auto currentPos = TouchEventToScreenPos(event.tfinger);
|
||||
const auto delta = currentPos - m_touchTapStartPos;
|
||||
if (ImLengthSqr(delta) > 144.0f) {
|
||||
m_touchTapMoved = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) {
|
||||
const bool shouldToggle =
|
||||
!m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse);
|
||||
m_touchTapActive = false;
|
||||
m_touchTapMoved = false;
|
||||
if (shouldToggle) {
|
||||
m_isHidden = !m_isHidden;
|
||||
getSettings().backend.duskMenuOpen.setValue(!m_isHidden);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
m_touchTapActive = false;
|
||||
m_touchTapMoved = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
(void)event;
|
||||
}
|
||||
|
||||
void ImGuiConsole::UpdateSettings() {
|
||||
@@ -324,15 +273,10 @@ namespace dusk {
|
||||
// m_preLaunchWindow.draw();
|
||||
// }
|
||||
|
||||
m_isHidden = !getSettings().backend.duskMenuOpen;
|
||||
if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||
m_isHidden = !m_isHidden;
|
||||
}
|
||||
bool showMenu = !m_isHidden;
|
||||
if (getSettings().backend.duskMenuOpen != showMenu) {
|
||||
getSettings().backend.duskMenuOpen.setValue(showMenu);
|
||||
Save();
|
||||
}
|
||||
|
||||
// The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg,
|
||||
// so make the window bg fully transparent temporarily
|
||||
@@ -362,7 +306,7 @@ namespace dusk {
|
||||
|
||||
if (dusk::IsGameLaunched && !m_isLaunchInitialized) {
|
||||
AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ?
|
||||
"Tap to toggle menu"s :
|
||||
"3-finger tap to toggle menu"s :
|
||||
"Press F1 to toggle menu"s,
|
||||
4.f);
|
||||
m_isLaunchInitialized = true;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string_view>
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
|
||||
#include "ImGuiFirstRunPreset.hpp"
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
@@ -42,10 +41,6 @@ private:
|
||||
|
||||
bool m_isHidden = true;
|
||||
bool m_isLaunchInitialized = false;
|
||||
bool m_touchTapActive = false;
|
||||
bool m_touchTapMoved = false;
|
||||
SDL_FingerID m_touchTapFingerId = 0;
|
||||
ImVec2 m_touchTapStartPos = {};
|
||||
ImGuiWindow* m_dragScrollWindow = nullptr;
|
||||
ImVec2 m_dragScrollLastMousePos = {};
|
||||
std::deque<Toast> m_toasts;
|
||||
|
||||
@@ -113,7 +113,6 @@ UserSettings g_userSettings = {
|
||||
.showPipelineCompilation {"backend.showPipelineCompilation", false},
|
||||
.wasPresetChosen {"backend.wasPresetChosen", false},
|
||||
.enableCrashReporting {"backend.enableCrashReporting", true},
|
||||
.duskMenuOpen {"backend.duskMenuOpen", false},
|
||||
.cardFileType {"backend.cardFileType", static_cast<int>(CARD_GCIFOLDER)}
|
||||
}
|
||||
};
|
||||
@@ -210,7 +209,6 @@ void registerSettings() {
|
||||
Register(g_userSettings.backend.showPipelineCompilation);
|
||||
Register(g_userSettings.backend.wasPresetChosen);
|
||||
Register(g_userSettings.backend.enableCrashReporting);
|
||||
Register(g_userSettings.backend.duskMenuOpen);
|
||||
Register(g_userSettings.backend.cardFileType);
|
||||
}
|
||||
|
||||
|
||||
+163
-5
@@ -5,6 +5,7 @@
|
||||
#include <RmlUi/Core.h>
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <SDL3/SDL_touch.h>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <dolphin/pad.h>
|
||||
|
||||
@@ -22,6 +23,10 @@ constexpr double kGamepadMenuChordGraceDuration = 0.12;
|
||||
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
|
||||
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
|
||||
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
|
||||
constexpr int kMenuTapFingerCount = 3;
|
||||
constexpr float kMenuTapMoveThreshold = 12.0f;
|
||||
constexpr double kMenuTapMaxDownSpan = 0.18;
|
||||
constexpr double kMenuTapMaxDuration = 0.55;
|
||||
|
||||
struct GamepadRepeatState {
|
||||
Rml::Input::KeyIdentifier key = Rml::Input::KI_UNKNOWN;
|
||||
@@ -32,11 +37,26 @@ struct GamepadRepeatState {
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
struct TouchTapFinger {
|
||||
SDL_FingerID id = 0;
|
||||
Rml::Vector2f startPosition;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct TouchTapState {
|
||||
std::array<TouchTapFinger, kMenuTapFingerCount> fingers;
|
||||
int activeCount = 0;
|
||||
double firstDownAt = 0.0;
|
||||
bool candidate = false;
|
||||
bool failed = false;
|
||||
};
|
||||
|
||||
bool sPadInputBlocked = false;
|
||||
std::array<GamepadRepeatState, SDL_GAMEPAD_BUTTON_COUNT> sGamepadButtonRepeats;
|
||||
std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
|
||||
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
|
||||
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
|
||||
TouchTapState sTouchMenuTap;
|
||||
|
||||
double now_seconds() noexcept {
|
||||
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
|
||||
@@ -389,6 +409,133 @@ void clear_gamepad_repeats() noexcept {
|
||||
sMenuChordConsumed.fill(false);
|
||||
}
|
||||
|
||||
void reset_touch_menu_tap() noexcept {
|
||||
sTouchMenuTap = {};
|
||||
}
|
||||
|
||||
Rml::Vector2f touch_position(const SDL_TouchFingerEvent& event, Rml::Context& context) noexcept {
|
||||
const auto dimensions = context.GetDimensions();
|
||||
return {
|
||||
event.x * static_cast<float>(dimensions.x),
|
||||
event.y * static_cast<float>(dimensions.y),
|
||||
};
|
||||
}
|
||||
|
||||
TouchTapFinger* find_touch_finger(SDL_FingerID id) noexcept {
|
||||
for (auto& finger : sTouchMenuTap.fingers) {
|
||||
if (finger.active && finger.id == id) {
|
||||
return &finger;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TouchTapFinger* find_free_touch_finger() noexcept {
|
||||
for (auto& finger : sTouchMenuTap.fingers) {
|
||||
if (!finger.active) {
|
||||
return &finger;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool touch_moved_too_far(
|
||||
const TouchTapFinger& finger, Rml::Vector2f position, Rml::Context& context) noexcept {
|
||||
const Rml::Vector2f delta = position - finger.startPosition;
|
||||
const float threshold =
|
||||
kMenuTapMoveThreshold * std::max(context.GetDensityIndependentPixelRatio(), 1.0f);
|
||||
return delta.SquaredMagnitude() > threshold * threshold;
|
||||
}
|
||||
|
||||
void dispatch_menu_key(Rml::Context& context) noexcept {
|
||||
context.ProcessMouseLeave();
|
||||
context.ProcessKeyDown(Rml::Input::KI_F1, 0);
|
||||
context.ProcessKeyUp(Rml::Input::KI_F1, 0);
|
||||
}
|
||||
|
||||
bool handle_touch_menu_tap(Rml::Context& context, const SDL_Event& event) noexcept {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_FINGER_DOWN: {
|
||||
const double now = now_seconds();
|
||||
if (sTouchMenuTap.activeCount == 0) {
|
||||
reset_touch_menu_tap();
|
||||
sTouchMenuTap.firstDownAt = now;
|
||||
}
|
||||
|
||||
if (sTouchMenuTap.candidate || sTouchMenuTap.activeCount >= kMenuTapFingerCount ||
|
||||
find_touch_finger(event.tfinger.fingerID) != nullptr)
|
||||
{
|
||||
sTouchMenuTap.failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* finger = find_free_touch_finger();
|
||||
if (finger == nullptr) {
|
||||
sTouchMenuTap.failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
*finger = TouchTapFinger{
|
||||
.id = event.tfinger.fingerID,
|
||||
.startPosition = touch_position(event.tfinger, context),
|
||||
.active = true,
|
||||
};
|
||||
sTouchMenuTap.activeCount++;
|
||||
|
||||
if (now - sTouchMenuTap.firstDownAt > kMenuTapMaxDownSpan) {
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
if (sTouchMenuTap.activeCount == kMenuTapFingerCount) {
|
||||
sTouchMenuTap.candidate = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_MOTION: {
|
||||
auto* finger = find_touch_finger(event.tfinger.fingerID);
|
||||
if (finger == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (touch_moved_too_far(*finger, touch_position(event.tfinger, context), context)) {
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_UP: {
|
||||
auto* finger = find_touch_finger(event.tfinger.fingerID);
|
||||
if (finger == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const double now = now_seconds();
|
||||
if (!sTouchMenuTap.candidate ||
|
||||
touch_moved_too_far(*finger, touch_position(event.tfinger, context), context))
|
||||
{
|
||||
sTouchMenuTap.failed = true;
|
||||
}
|
||||
|
||||
*finger = {};
|
||||
sTouchMenuTap.activeCount--;
|
||||
if (sTouchMenuTap.activeCount > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool shouldDispatch = sTouchMenuTap.candidate && !sTouchMenuTap.failed &&
|
||||
now - sTouchMenuTap.firstDownAt <= kMenuTapMaxDuration;
|
||||
reset_touch_menu_tap();
|
||||
if (shouldDispatch) {
|
||||
dispatch_menu_key(context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
reset_touch_menu_tap();
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void begin_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
|
||||
if (repeat.held) {
|
||||
return;
|
||||
@@ -530,6 +677,7 @@ void release_input_block() noexcept {
|
||||
|
||||
void reset_input_state() noexcept {
|
||||
clear_gamepad_repeats();
|
||||
reset_touch_menu_tap();
|
||||
}
|
||||
|
||||
void handle_event(const SDL_Event& event) noexcept {
|
||||
@@ -541,17 +689,27 @@ void handle_event(const SDL_Event& event) noexcept {
|
||||
}
|
||||
}
|
||||
dispatch_controller_change_event(event);
|
||||
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
|
||||
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_FINGER_DOWN || event.type == SDL_EVENT_FINGER_MOTION ||
|
||||
event.type == SDL_EVENT_FINGER_UP || event.type == SDL_EVENT_FINGER_CANCELED)
|
||||
{
|
||||
if (handle_touch_menu_tap(*context, event)) {
|
||||
sync_input_block();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
|
||||
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
|
||||
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
|
||||
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
|
||||
|
||||
Reference in New Issue
Block a user