From f81d25b4258d04919610a9b562d5c8215a1e6731 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 27 Jun 2026 21:20:14 -0600 Subject: [PATCH] Add "Touch Targeting" option --- extern/aurora | 2 +- include/dusk/settings.h | 13 +++++ src/dusk/config.cpp | 1 + src/dusk/settings.cpp | 2 + src/dusk/ui/settings.cpp | 59 ++++++++++++++++++++++ src/dusk/ui/touch_controls.cpp | 91 ++++++++++++++++++++++++---------- 6 files changed, 141 insertions(+), 27 deletions(-) diff --git a/extern/aurora b/extern/aurora index e145b9ec20..9087a409da 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit e145b9ec206c70c3b67c4041e544b456f7037bbb +Subproject commit 9087a409da35b17446af12d7456ec6563cf2dd43 diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 0718704afa..fdff069226 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -46,6 +46,12 @@ enum class FrameInterpMode : u8 { Unlimited = 2, }; +enum class TouchTargeting : u8 { + Hybrid = 0, + Hold = 1, + Switch = 2, +}; + enum class MenuScaling : u8 { GameCube = 0, Wii = 1, @@ -97,6 +103,12 @@ struct ConfigEnumRange { static constexpr auto max = FrameInterpMode::Unlimited; }; +template <> +struct ConfigEnumRange { + static constexpr auto min = TouchTargeting::Hybrid; + static constexpr auto max = TouchTargeting::Switch; +}; + template <> struct ConfigEnumRange { static constexpr auto min = MenuScaling::GameCube; @@ -216,6 +228,7 @@ struct UserSettings { ConfigVar invertMouseY; ConfigVar freeCamera; ConfigVar enableTouchControls; + ConfigVar touchTargeting; ConfigVar enableMenuPointer; ConfigVar touchControlsLayout; ConfigVar invertCameraXAxis; diff --git a/src/dusk/config.cpp b/src/dusk/config.cpp index fa331eb518..43d3f19dad 100644 --- a/src/dusk/config.cpp +++ b/src/dusk/config.cpp @@ -378,6 +378,7 @@ nlohmann::json ConfigImpl::dumpToJson(const ConfigVar; +template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; template class ConfigImpl; diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 39783d2d8f..7831c5c41f 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -96,6 +96,7 @@ UserSettings g_userSettings = { .invertMouseY {"game.invertMouseY", false}, .freeCamera {"game.freeCamera", false}, .enableTouchControls {"game.enableTouchControls", false}, + .touchTargeting {"game.touchTargeting", TouchTargeting::Hybrid}, .enableMenuPointer {"game.enableMenuPointer", true}, .touchControlsLayout {"game.touchControlsLayout", ui::ControlLayout{}}, .invertCameraXAxis {"game.invertCameraXAxis", false}, @@ -329,6 +330,7 @@ void registerSettings() { Register(g_userSettings.game.invertMouseY); Register(g_userSettings.game.freeCamera); Register(g_userSettings.game.enableTouchControls); + Register(g_userSettings.game.touchTargeting); Register(g_userSettings.game.enableMenuPointer); Register(g_userSettings.game.touchControlsLayout); Register(g_userSettings.game.debugFlyCam); diff --git a/src/dusk/ui/settings.cpp b/src/dusk/ui/settings.cpp index b255e101c1..3c89f34136 100644 --- a/src/dusk/ui/settings.cpp +++ b/src/dusk/ui/settings.cpp @@ -77,6 +77,18 @@ constexpr std::array kInterpolationModes = { "Unlimited", }; +constexpr std::array kTouchTargetingLabels = { + "Hybrid", + "Hold", + "Switch", +}; + +constexpr std::array kTouchTargetingDescriptions = { + "Tap once to lock on when a target is found. Double-tap when none is found to hold L.", + "L stays held only while your finger is on the button.", + "Tap L to keep it held. Tap again to release it.", +}; + constexpr std::array kGyroInputModeLabels = { "Sensor", "Mouse", @@ -407,6 +419,14 @@ bool gyro_enabled() { return getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal; } +Rml::String touch_targeting_label(TouchTargeting targeting) { + const auto index = static_cast(targeting); + if (index >= kTouchTargetingLabels.size()) { + return "Unknown"; + } + return kTouchTargetingLabels[index]; +} + struct ConfigBoolProps { Rml::String key; Rml::String icon; @@ -1003,6 +1023,45 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) { pane.clear(); pane.add_text("Open the touch controls layout editor."); }); + leftPane.register_control(leftPane.add_select_button({ + .key = "Touch Targeting", + .getValue = + [] { + return touch_targeting_label( + getSettings().game.touchTargeting.getValue()); + }, + .isDisabled = + [] { return !getSettings().game.enableTouchControls; }, + .isModified = + [] { + const auto& targeting = + getSettings().game.touchTargeting; + return targeting.getValue() != + targeting.getDefaultValue(); + }, + }), + rightPane, [](Pane& pane) { + pane.clear(); + for (int i = 0; i < static_cast(kTouchTargetingLabels.size()); ++i) { + pane.add_button({ + .text = kTouchTargetingLabels[i], + .isSelected = + [i] { + return getSettings().game.touchTargeting.getValue() == + static_cast(i); + }, + }) + .on_pressed([i] { + mDoAud_seStartMenu(kSoundItemChange); + getSettings().game.touchTargeting.setValue( + static_cast(i)); + config::Save(); + }); + } + pane.add_rml(fmt::format("
Hybrid: {}
Hold: {}
Switch: {}", + kTouchTargetingDescriptions[0], kTouchTargetingDescriptions[1], + kTouchTargetingDescriptions[2])); + }); config_percent_select(leftPane, rightPane, getSettings().game.touchCameraXSensitivity, "Touch Camera X Sensitivity", "Adjusts touch camera horizontal sensitivity.

Applies to touch input only.", diff --git a/src/dusk/ui/touch_controls.cpp b/src/dusk/ui/touch_controls.cpp index 6056f0d11d..f6b0019a98 100644 --- a/src/dusk/ui/touch_controls.cpp +++ b/src/dusk/ui/touch_controls.cpp @@ -485,43 +485,71 @@ void TouchControls::set_control_pressed(Control control, bool pressed) { mLastLTapTime = {}; break; } - if (pressed && (mLLatched || mManualLLatched)) { + switch (getSettings().game.touchTargeting.getValue()) { + case TouchTargeting::Hold: + mLPressed = pressed; mLLatched = false; mManualLLatched = false; - mLPressed = false; - mLReleasePending = true; + mLReleasePending = false; mLPressStartTime = {}; mLastLTapTime = {}; - set_control_visual(control, false); - } else if (pressed) { - const auto now = clock::now(); - if (!player_attention_locked() && mLastLTapTime != clock::time_point{} && - now - mLastLTapTime <= kLDoubleTapWindow) - { - mManualLLatched = true; + break; + case TouchTargeting::Switch: + if (pressed) { + const bool wasLatched = mLPressed || mLLatched || mManualLLatched; + mLPressed = false; + mLLatched = false; + mManualLLatched = !wasLatched; + mLReleasePending = true; + } else { + mLPressed = false; + mLLatched = false; + mLReleasePending = false; + } + mLPressStartTime = {}; + mLastLTapTime = {}; + break; + case TouchTargeting::Hybrid: + default: + if (pressed && (mLLatched || mManualLLatched)) { + mLLatched = false; + mManualLLatched = false; mLPressed = false; mLReleasePending = true; mLPressStartTime = {}; mLastLTapTime = {}; + set_control_visual(control, false); + } else if (pressed) { + const auto now = clock::now(); + if (!player_attention_locked() && mLastLTapTime != clock::time_point{} && + now - mLastLTapTime <= kLDoubleTapWindow) + { + mManualLLatched = true; + mLPressed = false; + mLReleasePending = true; + mLPressStartTime = {}; + mLastLTapTime = {}; + } else if (!mLReleasePending) { + mLPressed = true; + mLPressStartTime = now; + } } else if (!mLReleasePending) { - mLPressed = true; - mLPressStartTime = now; + mLPressed = false; } - } else if (!mLReleasePending) { - mLPressed = false; - } - if (!pressed) { - const auto now = clock::now(); - if (!mLReleasePending) { - const bool wasQuickTap = mLPressStartTime != clock::time_point{} && - now - mLPressStartTime <= kLDoubleTapWindow; - mLastLTapTime = wasQuickTap ? now : clock::time_point{}; + if (!pressed) { + const auto now = clock::now(); + if (!mLReleasePending) { + const bool wasQuickTap = mLPressStartTime != clock::time_point{} && + now - mLPressStartTime <= kLDoubleTapWindow; + mLastLTapTime = wasQuickTap ? now : clock::time_point{}; + } + mLPressStartTime = {}; + mLReleasePending = false; } - mLPressStartTime = {}; - mLReleasePending = false; - } - if (!pressed && !player_attention_locked()) { - mLLatched = false; + if (!pressed && !player_attention_locked()) { + mLLatched = false; + } + break; } break; case Control::R: @@ -635,6 +663,17 @@ void TouchControls::apply_control_transform(Control control) noexcept { } void TouchControls::sync_l_lock_state() noexcept { + const auto targeting = getSettings().game.touchTargeting.getValue(); + if (targeting == TouchTargeting::Hold) { + mLLatched = false; + mManualLLatched = false; + return; + } + if (targeting == TouchTargeting::Switch) { + mLLatched = false; + return; + } + if (player_attention_locked()) { if (mLPressed) { mLLatched = true;