Files
wiicompiled/runtime/tests/touch_auto_accelerate_tests.cpp
T
Michael G 1a40b8c841 ios: Add auto-accelerate for touch controls (#250)
* ios: add mobile touch auto-accelerate support

Port KartPad's auto-accelerate accessibility feature to WiiCompiled:
- Holding button A on touch controls for 1 second locks acceleration.
- Latched acceleration renders with a prominent cyan highlight ring/glow,
  matching KartPad's visual feedback.
- Tapping button A while locked immediately unlocks acceleration.
- Auto-accelerate is enabled by default with persistent opt-out in
  Config.toml (`touch_auto_accelerate = false`) and an in-game checkbox
  under Controller Settings.
- Disabling auto-accelerate or connecting a physical controller immediately
  cancels any active latch while preserving genuine physical holds.
- Includes comprehensive automated unit tests in touch_auto_accelerate_tests.

* test, ios: address review feedback on touch auto-accelerate

* touch_pad: ignore active controls in PollFpsTap and use triggerL/triggerR
2026-09-24 12:35:21 +02:00

190 lines
7.6 KiB
C++

#include "touch_pad.h"
#include "runtime_config.h"
#include "touch_art.h"
#include "settings_overlay.h"
#include <imgui.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_touch.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#define CHECK(condition) \
do { \
if (!(condition)) { \
std::cerr << "CHECK failed at " << __FILE__ << ":" << __LINE__ << " (" #condition ")" << std::endl; \
return 1; \
} \
} while (false)
// Stubs for headless execution
namespace settings_overlay {
void ToggleTopBar() noexcept {}
bool TopBarVisible() noexcept { return false; }
bool FpsOverlayBounds(float*, float*, float*, float*) noexcept { return false; }
} // namespace settings_overlay
ImTextureID TouchArt::Get(const char*) { return ImTextureID{}; }
ImVec2 ImGui::CalcTextSize(const char*, const char*, bool, float) { return ImVec2(0, 0); }
ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport*) { return nullptr; }
ImGuiIO::ImGuiIO() {}
void ImGui::MemFree(void* p) { std::free(p); }
ImGuiIO& ImGui::GetIO() { static ImGuiIO io{}; return io; }
void ImDrawList::AddCircleFilled(const ImVec2&, float, ImU32, int) {}
void ImDrawList::AddCircle(const ImVec2&, float, ImU32, int, float) {}
void ImDrawList::AddText(const ImVec2&, ImU32, const char*, const char*) {}
void ImDrawList::AddImage(ImTextureID, const ImVec2&, const ImVec2&, const ImVec2&, const ImVec2&, ImU32) {}
extern "C" {
SDL_JoystickID* SDL_GetGamepads(int* count) { if (count) *count = 0; return nullptr; }
SDL_TouchID* SDL_GetTouchDevices(int* count) { if (count) *count = 0; return nullptr; }
SDL_Finger** SDL_GetTouchFingers(SDL_TouchID, int* count) { if (count) *count = 0; return nullptr; }
void SDL_free(void*) {}
}
int main() {
std::cout << "[test] Starting touch auto-accelerate tests..." << std::endl;
// Isolate configuration path to a temporary directory so tests never touch real user config
std::error_code ec;
const auto tempDir = std::filesystem::temp_directory_path() / "mkw_touch_test_isolation";
std::filesystem::create_directories(tempDir, ec);
const auto tempConfig = tempDir / "Config.toml";
std::filesystem::remove(tempConfig, ec);
RuntimeConfigFile::SetConfigPathOverride(tempConfig);
struct CleanupGuard {
std::filesystem::path dir;
~CleanupGuard() {
RuntimeConfigFile::SetConfigPathOverride({});
std::error_code err;
std::filesystem::remove_all(dir, err);
}
} cleanup{tempDir};
// 1. Initial / default state
TouchPad::Reset();
TouchPad::SetAutoAccelerate(true);
CHECK(TouchPad::AutoAccelerateEnabled());
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Default state verified." << std::endl;
// 2. Normal tap (< 1000ms) does not lock
TouchPad::UpdateAutoAccelerate(true, 100, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(true, 500, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 600, true);
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Short tap does not latch verified." << std::endl;
// 3. Hold for 1000ms latches acceleration
TouchPad::UpdateAutoAccelerate(true, 1000, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(true, 1500, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(true, 1999, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(true, 2000, true); // 1000ms elapsed
CHECK(TouchPad::IsGasLocked());
std::cout << "[test] 1-second hold locks acceleration verified." << std::endl;
// 4. Releasing finger after latch retains lock
TouchPad::UpdateAutoAccelerate(false, 2500, true);
CHECK(TouchPad::IsGasLocked());
std::cout << "[test] Finger lift retains lock verified." << std::endl;
// 5. Tapping while locked unlocks acceleration
TouchPad::UpdateAutoAccelerate(true, 3000, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 3100, true);
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Tap while locked unlocks acceleration verified." << std::endl;
// 6. Holding the unlocking tap for > 1000ms does NOT immediately re-lock in same touch contact
TouchPad::UpdateAutoAccelerate(true, 4000, true);
TouchPad::UpdateAutoAccelerate(true, 5000, true);
CHECK(TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 5100, true);
CHECK(TouchPad::IsGasLocked());
// Unlocking touch begins:
TouchPad::UpdateAutoAccelerate(true, 6000, true);
CHECK(!TouchPad::IsGasLocked());
// Continuous hold past 1000ms:
TouchPad::UpdateAutoAccelerate(true, 7500, true);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 7600, true);
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Unlocking tap cannot immediately re-lock in same gesture verified." << std::endl;
// 7. Disabling auto-accelerate immediately clears active lock
TouchPad::UpdateAutoAccelerate(true, 8000, true);
TouchPad::UpdateAutoAccelerate(true, 9000, true);
CHECK(TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 9100, true);
CHECK(TouchPad::IsGasLocked());
TouchPad::SetAutoAccelerate(false);
CHECK(!TouchPad::AutoAccelerateEnabled());
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Disabling auto-accelerate cancels active lock verified." << std::endl;
// 8. Holding A when disabled never locks
TouchPad::UpdateAutoAccelerate(true, 10000, false);
TouchPad::UpdateAutoAccelerate(true, 12000, false);
CHECK(!TouchPad::IsGasLocked());
TouchPad::UpdateAutoAccelerate(false, 12100, false);
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Holding A while disabled does not latch verified." << std::endl;
// 9. Reset clears lock
TouchPad::SetAutoAccelerate(true);
TouchPad::UpdateAutoAccelerate(true, 13000, true);
TouchPad::UpdateAutoAccelerate(true, 14000, true);
CHECK(TouchPad::IsGasLocked());
TouchPad::Reset();
CHECK(!TouchPad::IsGasLocked());
std::cout << "[test] Reset clears lock verified." << std::endl;
// 10. Config persistence round-trip & stream parsing
CHECK(RuntimeConfigFile::SetTouchAutoAccelerate(false));
CHECK(!RuntimeConfigFile::TouchAutoAccelerate());
{
std::ifstream in(tempConfig);
const auto parsed = RuntimeConfigFile::ParseConfig(in);
CHECK(parsed.touchAutoAccelerate.has_value());
CHECK(*parsed.touchAutoAccelerate == false);
}
CHECK(RuntimeConfigFile::SetTouchAutoAccelerate(true));
CHECK(RuntimeConfigFile::TouchAutoAccelerate());
{
std::ifstream in(tempConfig);
const auto parsed = RuntimeConfigFile::ParseConfig(in);
CHECK(parsed.touchAutoAccelerate.has_value());
CHECK(*parsed.touchAutoAccelerate == true);
}
// Direct in-memory stream validation
{
std::istringstream streamOn("[controller]\ntouch_auto_accelerate = true\n");
const auto parsedOn = RuntimeConfigFile::ParseConfig(streamOn);
CHECK(parsedOn.touchAutoAccelerate.has_value());
CHECK(*parsedOn.touchAutoAccelerate == true);
std::istringstream streamOff("[controller]\ntouch_auto_accelerate = false\n");
const auto parsedOff = RuntimeConfigFile::ParseConfig(streamOff);
CHECK(parsedOff.touchAutoAccelerate.has_value());
CHECK(*parsedOff.touchAutoAccelerate == false);
}
std::cout << "[test] Config persistence round-trip and ParseConfig verified." << std::endl;
std::cout << "[test] All touch auto-accelerate tests passed successfully!" << std::endl;
return 0;
}