From 8785368913c82456d0a8c0abba7285483b2d53b8 Mon Sep 17 00:00:00 2001 From: HP <2726519488@qq.com> Date: Mon, 8 Jun 2026 12:53:38 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9Tom=E6=80=BB=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E5=AE=8C=E5=96=84=E5=8A=A8=E7=94=BB=E6=9C=BA?= =?UTF-8?q?=E7=9A=84=E7=8A=B6=E6=80=81=E8=AE=BE=E7=BD=AE=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E6=B8=B8=E6=88=8F=E9=80=BB=E8=BE=91=EF=BC=9B=E5=9C=A8core/plat?= =?UTF-8?q?form=E6=96=B0=E5=A2=9Ewin=E5=92=8C=E6=9D=BF=E7=AB=AF=E7=9A=84?= =?UTF-8?q?=E9=BC=A0=E6=A0=87=E7=82=B9=E5=87=BB/=E8=A7=A6=E6=8E=A7?= =?UTF-8?q?=E7=9A=84=E9=80=BB=E8=BE=91=E4=BB=A3=E7=A0=81/=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=EF=BC=9B=E6=B5=8B=E8=AF=95=E6=B8=B8=E6=88=8F=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + src/Apps/Game/CMakeLists.txt | 7 + src/Apps/Game/Main.cpp | 77 +++--- src/Apps/Game/src/app/TomGameApp.cpp | 340 +++++++++++++------------- src/Apps/Game/src/app/TomGameApp.h | 75 ++---- src/Core/Platform/DefaultHardware.h | 4 + src/Core/Platform/EvdevTouchInput.cpp | 205 ++++++++++++++++ src/Core/Platform/EvdevTouchInput.h | 54 ++++ src/Core/Platform/PointerInput.h | 28 +++ src/Core/Platform/SdlPointerInput.cpp | 109 +++++++++ src/Core/Platform/SdlPointerInput.h | 40 +++ 11 files changed, 671 insertions(+), 270 deletions(-) create mode 100644 src/Core/Platform/EvdevTouchInput.cpp create mode 100644 src/Core/Platform/EvdevTouchInput.h create mode 100644 src/Core/Platform/PointerInput.h create mode 100644 src/Core/Platform/SdlPointerInput.cpp create mode 100644 src/Core/Platform/SdlPointerInput.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bddc9d..48efd32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ set(CORE_SOURCES src/Core/Platform/AlsaAudioInput.cpp src/Core/Platform/AlsaAudioOutput.cpp src/Core/Platform/EvdevButtonInput.cpp + src/Core/Platform/EvdevTouchInput.cpp src/Core/Rasterizer/Rasterizer.cpp src/Core/Rasterizer/TriangleRasterizer.cpp src/Core/Scene/Camera.cpp @@ -37,6 +38,7 @@ else() src/Core/Platform/SdlAudioInput.cpp src/Core/Platform/SdlAudioOutput.cpp src/Core/Platform/SdlKeyboardButtonInput.cpp + src/Core/Platform/SdlPointerInput.cpp ) endif() diff --git a/src/Apps/Game/CMakeLists.txt b/src/Apps/Game/CMakeLists.txt index 57af9ce..8c81a30 100644 --- a/src/Apps/Game/CMakeLists.txt +++ b/src/Apps/Game/CMakeLists.txt @@ -4,10 +4,17 @@ set_source_files_properties(${TOM_ATLAS_HEADER} PROPERTIES GENERATED TRUE) add_executable(${TOM_GAME_TARGET} Main.cpp + src/app/TomGameApp.cpp + src/audio/VoiceEffect.cpp + src/audio/VoicePlayer.cpp + src/audio/VoiceRecorder.cpp ${TOM_ATLAS_HEADER} ) target_include_directories(${TOM_GAME_TARGET} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/app + ${CMAKE_CURRENT_SOURCE_DIR}/src/audio ${CMAKE_CURRENT_SOURCE_DIR}/generated ) diff --git a/src/Apps/Game/Main.cpp b/src/Apps/Game/Main.cpp index d5f1533..9614c71 100644 --- a/src/Apps/Game/Main.cpp +++ b/src/Apps/Game/Main.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -6,12 +5,12 @@ #include #include -#include "Color.h" +#include "DefaultHardware.h" #include "Display.h" #include "DrawContext.h" #include "TimeSource.h" #include "Timer.h" -#include "tom_atlas.h" +#include "app/TomGameApp.h" #ifdef USE_FRAMEBUFFER #include "FBDisplay.h" @@ -108,25 +107,6 @@ namespace std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); } } - - static const RenderData::SpriteRegion& SelectTomFrame( - uint32_t animation_time_ms, - const RenderData::SpriteRegion* const* speaking_frames, - size_t speaking_frame_count) - { - const uint32_t phase = (animation_time_ms / 1000u) % 6u; - if (phase < 2u) - { - return TomAtlas::tom_stand; - } - if (phase < 3u) - { - return TomAtlas::tom_listhen; - } - - const size_t frame_index = (animation_time_ms / 120u) % speaking_frame_count; - return *speaking_frames[frame_index]; - } } int main(int argc, char* argv[]) @@ -145,28 +125,37 @@ int main(int argc, char* argv[]) return -1; } - const RenderData::SpriteRegion* speaking_frames[] = { - &TomAtlas::tom_say1, - &TomAtlas::tom_say2, - &TomAtlas::tom_say3, - &TomAtlas::tom_say4, - &TomAtlas::tom_say3, - &TomAtlas::tom_say2 - }; - const size_t speaking_frame_count = sizeof(speaking_frames) / sizeof(speaking_frames[0]); + Platform::DefaultAudioInput audioInput; + Platform::DefaultAudioOutput audioOutput; + Platform::DefaultButtonInput buttonInput; + Platform::DefaultPointerInput pointerInput; + + if (!buttonInput.init()) + { + std::cerr << "[WARN] Button input init failed; physical key trigger is disabled." << std::endl; + } + if (!pointerInput.init("", ScreenWidth, ScreenHeight)) + { + std::cerr << "[WARN] Pointer input init failed; on-screen button trigger is disabled." << std::endl; + } Core::DrawContext ctx(ScreenWidth, ScreenHeight); Core::Timer timer(options.target_fps); Platform::SteadyTimeSource time_source; + Game::TomGameApp app( + ScreenWidth, + ScreenHeight, + &audioInput, + &audioOutput, + &buttonInput, + &pointerInput); std::cout << "[INFO] Tom game started. Target FPS: " << timer.target_fps() << std::endl; bool is_running = true; - uint32_t animation_time_ms = 0; while (is_running) { timer.begin_frame(time_source.get_time_ms()); - animation_time_ms += timer.fixed_delta_ms(); bool should_quit = false; display->poll_events(should_quit); @@ -175,26 +164,16 @@ int main(int argc, char* argv[]) is_running = false; } - ctx.clear(RenderData::Color(18, 18, 24, 255)); - ctx.draw_sprite_region(0, 0, TomAtlas::background); - - const RenderData::SpriteRegion& tom = SelectTomFrame( - animation_time_ms, - speaking_frames, - speaking_frame_count); - - const int32_t tom_x = (ScreenWidth - tom.width) / 2; - const int32_t tom_y = std::max(0, ScreenHeight - tom.height - 72); - ctx.draw_sprite_region(tom_x, tom_y, tom); - - const int32_t button_x = (ScreenWidth - TomAtlas::ui_record.width) / 2; - const int32_t button_y = ScreenHeight - TomAtlas::ui_record.height - 16; - ctx.draw_sprite_region(button_x, button_y, TomAtlas::ui_record); - + app.update(timer.fixed_delta_ms()); + app.draw(ctx); ctx.present(display); SleepRemainingFrameTime(timer, time_source); } + pointerInput.shutdown(); + buttonInput.shutdown(); + audioInput.shutdown(); + audioOutput.shutdown(); display->shutdown(); delete display; return 0; diff --git a/src/Apps/Game/src/app/TomGameApp.cpp b/src/Apps/Game/src/app/TomGameApp.cpp index 114482b..a55c2a6 100644 --- a/src/Apps/Game/src/app/TomGameApp.cpp +++ b/src/Apps/Game/src/app/TomGameApp.cpp @@ -3,56 +3,67 @@ #include "AudioInput.h" #include "AudioOutput.h" #include "ButtonInput.h" -#include "FrameBuffer.h" -#include "SpriteRasterizer.h" -#include "Image.h" +#include "Color.h" +#include "DrawContext.h" +#include "PointerInput.h" +#include "SpriteRegion.h" +#include "tom_atlas.h" +#include +#include +#include + +namespace +{ + const uint32_t DefaultMaxRecordMs = 5000u; + const uint32_t SpeakingFrameMs = 90u; + const int32_t TomBottomPadding = 72; + const int32_t ButtonBottomPadding = 16; + + const RenderData::SpriteRegion* const SpeakingFrames[] = { + &TomAtlas::tom_say1, + &TomAtlas::tom_say2, + &TomAtlas::tom_say3, + &TomAtlas::tom_say4, + &TomAtlas::tom_say3, + &TomAtlas::tom_say2 + }; + + static const RenderData::SpriteRegion& SelectSpeakingFrame(uint32_t animationMs) + { + const size_t frameCount = sizeof(SpeakingFrames) / sizeof(SpeakingFrames[0]); + const size_t frameIndex = (animationMs / SpeakingFrameMs) % frameCount; + return *SpeakingFrames[frameIndex]; + } +} namespace Game { TomGameApp::TomGameApp( - Core::FrameBuffer* frameBuffer, - Rasterizer::SpriteRasterizer* spriteRasterizer, + int32_t screenWidth, + int32_t screenHeight, Platform::IAudioInput* audioInput, Platform::IAudioOutput* audioOutput, - Platform::IButtonInput* buttonInput) : - frameBuffer(frameBuffer), - spriteRasterizer(spriteRasterizer), - audioInput(audioInput), - audioOutput(audioOutput), - buttonInput(buttonInput), - state(TomGameState::Idle), - screenWidth(frameBuffer ? frameBuffer->get_width() : 0), - screenHeight(frameBuffer ? frameBuffer->get_height() : 0), - tomX(0), - tomY(0), - buttonX(0), - buttonY(0), - audioSampleRate(16000), - audioChannels(1), - pitchFactor(1.45f), - outputGain(1.15f) + Platform::IButtonInput* buttonInput, + Platform::IPointerInput* pointerInput) + : audioInput(audioInput), + audioOutput(audioOutput), + buttonInput(buttonInput), + pointerInput(pointerInput), + state(TomGameState::Idle), + screenWidth(screenWidth), + screenHeight(screenHeight), + buttonX(0), + buttonY(0), + audioSampleRate(16000), + audioChannels(1), + maxRecordMs(DefaultMaxRecordMs), + recordingElapsedMs(0), + speakingAnimationMs(0), + pitchFactor(1.45f), + outputGain(1.15f) { - recorder.configure(audioSampleRate, audioChannels); - } - - void TomGameApp::set_assets(const TomGameAssets& assets) - { - this->assets = assets; - - speakingFrames.clear(); - if (assets.tomSay1 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay1)); - if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2)); - if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3)); - if (assets.tomSay4 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay4)); - if (assets.tomSay3 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay3)); - if (assets.tomSay2 != nullptr) speakingFrames.push_back(RenderData::Sprite(assets.tomSay2)); - - speakingAnimator.set_frames(speakingFrames); - speakingAnimator.set_frame_time(0.08f); - speakingAnimator.set_loop(true); - speakingAnimator.stop(); - - calculate_layout(); + configure_audio(audioSampleRate, audioChannels); + calculate_button_layout(); } void TomGameApp::configure_audio(uint32_t sampleRate, uint32_t channels) @@ -66,142 +77,142 @@ namespace Game audioChannels = channels; } - recorder.configure(audioSampleRate, audioChannels); + recorder.configure( + audioSampleRate, + audioChannels, + static_cast(maxRecordMs) / 1000.0f, + 0.0f, + static_cast(maxRecordMs) / 1000.0f, + 0.0f); } - void TomGameApp::update(float deltaTime, bool buttonPressed) + void TomGameApp::update(uint32_t deltaMs) { - bool trigger = buttonPressed; - if (buttonInput != nullptr) - { - buttonInput->update(); - trigger = trigger || buttonInput->was_pressed(); - } + bool recordTrigger = false; + update_input(recordTrigger); switch (state) { case TomGameState::Idle: - if (trigger) + if (recordTrigger) { start_recording(); } break; case TomGameState::Recording: - if (audioInput != nullptr) - { - recorder.update(*audioInput, 512); - } - if (trigger || recorder.is_finished()) - { - finish_recording(); - } - break; - - case TomGameState::Processing: - process_voice(); + update_recording(deltaMs); break; case TomGameState::Speaking: - speakingAnimator.update(deltaTime); - if (audioOutput == nullptr) - { - back_to_idle(); - break; - } - - player.update(*audioOutput, 1024); - if (player.is_finished()) - { - back_to_idle(); - } + update_speaking(deltaMs); break; } } - void TomGameApp::draw() + void TomGameApp::draw(Core::DrawContext& ctx) { - if (spriteRasterizer == nullptr) + ctx.clear_color(RenderData::Color(18, 18, 24, 255)); + ctx.draw_sprite_region(0, 0, TomAtlas::background); + + const RenderData::SpriteRegion* tom = &TomAtlas::tom_stand; + if (state == TomGameState::Recording) { - return; + tom = &TomAtlas::tom_listhen; + } + else if (state == TomGameState::Speaking) + { + tom = &SelectSpeakingFrame(speakingAnimationMs); } - if (assets.background != nullptr) + const int32_t tomX = (screenWidth - tom->width) / 2; + int32_t tomY = screenHeight - tom->height - TomBottomPadding; + if (tomY < 0) { - spriteRasterizer->DrawImage(*assets.background, 0, 0); + tomY = 0; } - const RenderData::Sprite tomSprite = get_current_tom_sprite(); - if (tomSprite.is_valid()) - { - spriteRasterizer->DrawSprite(tomSprite, tomX, tomY); - } - - if (assets.button != nullptr) - { - spriteRasterizer->DrawImage(*assets.button, buttonX, buttonY); - } + ctx.draw_sprite_region(tomX, tomY, *tom); + ctx.draw_sprite_region(buttonX, buttonY, TomAtlas::ui_record); } - bool TomGameApp::is_ready() const + void TomGameApp::update_input(bool& recordTrigger) { - return frameBuffer != nullptr && - spriteRasterizer != nullptr && - assets.background != nullptr && - assets.tomStand != nullptr && - assets.tomListen != nullptr && - assets.button != nullptr && - speakingFrames.size() == 6; - } + recordTrigger = false; - bool TomGameApp::is_button_hit(int32_t x, int32_t y) const - { - if (assets.button == nullptr) + if (buttonInput != nullptr) { - return false; + buttonInput->update(); + recordTrigger = recordTrigger || buttonInput->was_pressed(); } - return x >= buttonX && - x < buttonX + assets.button->get_width() && - y >= buttonY && - y < buttonY + assets.button->get_height(); - } - - void TomGameApp::calculate_layout() - { - screenWidth = frameBuffer ? frameBuffer->get_width() : 0; - screenHeight = frameBuffer ? frameBuffer->get_height() : 0; - - if (assets.button != nullptr) + if (pointerInput != nullptr) { - buttonX = (screenWidth - assets.button->get_width()) / 2; - buttonY = screenHeight - assets.button->get_height() - 16; - } - - const RenderData::Sprite tomSprite = get_current_tom_sprite(); - if (tomSprite.is_valid()) - { - tomX = (screenWidth - tomSprite.width) / 2; - tomY = screenHeight - tomSprite.height - 72; - if (tomY < 0) + pointerInput->update(); + if (pointerInput->was_pressed() && + is_record_button_hit(pointerInput->get_x(), pointerInput->get_y())) { - tomY = 0; + recordTrigger = true; } } } void TomGameApp::start_recording() { - if (audioInput != nullptr && !audioInput->is_open()) + if (audioInput == nullptr) { - audioInput->init("default", audioSampleRate, audioChannels); + std::cerr << "[WARN] Audio input is unavailable." << std::endl; + return; } + if (audioOutput != nullptr && audioOutput->is_open()) + { + audioOutput->shutdown(); + } + + if (!audioInput->is_open() && + !audioInput->init("default", audioSampleRate, audioChannels)) + { + std::cerr << "[WARN] Audio input init failed." << std::endl; + return; + } + + audioSampleRate = audioInput->get_sample_rate(); + audioChannels = audioInput->get_channels(); + player.stop(); - recorder.configure(audioSampleRate, audioChannels); + recorder.configure( + audioSampleRate, + audioChannels, + static_cast(maxRecordMs) / 1000.0f, + 0.0f, + static_cast(maxRecordMs) / 1000.0f, + 0.0f); recorder.start(); + recordingElapsedMs = 0; + speakingAnimationMs = 0; state = TomGameState::Recording; - calculate_layout(); + } + + void TomGameApp::update_recording(uint32_t deltaMs) + { + recordingElapsedMs += deltaMs; + + if (audioInput != nullptr) + { + uint32_t requestCount = (audioSampleRate * audioChannels * deltaMs) / 1000u; + if (requestCount < audioChannels) + { + requestCount = audioChannels; + } + requestCount -= requestCount % audioChannels; + recorder.update(*audioInput, static_cast(std::min(requestCount, 2048u))); + } + + if (recordingElapsedMs >= maxRecordMs || recorder.is_finished()) + { + finish_recording(); + } } void TomGameApp::finish_recording() @@ -211,11 +222,11 @@ namespace Game recorder.stop(); } - state = TomGameState::Processing; - } + if (audioInput != nullptr && audioInput->is_open()) + { + audioInput->shutdown(); + } - void TomGameApp::process_voice() - { std::vector samples = recorder.get_samples(); samples = VoiceEffect::trim_silence(samples, 0.02f, audioChannels); samples = VoiceEffect::pitch_up(samples, pitchFactor, audioChannels); @@ -226,7 +237,7 @@ namespace Game void TomGameApp::start_speaking(const std::vector& samples) { - if (samples.empty()) + if (samples.empty() || audioOutput == nullptr) { back_to_idle(); return; @@ -234,47 +245,46 @@ namespace Game player.set_voice(samples, audioSampleRate, audioChannels); player.play(); - - speakingAnimator.reset(); - speakingAnimator.play(); + speakingAnimationMs = 0; state = TomGameState::Speaking; - calculate_layout(); + } + + void TomGameApp::update_speaking(uint32_t deltaMs) + { + speakingAnimationMs += deltaMs; + + if (audioOutput == nullptr) + { + back_to_idle(); + return; + } + + player.update(*audioOutput, 1024); + if (player.is_finished()) + { + back_to_idle(); + } } void TomGameApp::back_to_idle() { player.stop(); - speakingAnimator.stop(); + recordingElapsedMs = 0; + speakingAnimationMs = 0; state = TomGameState::Idle; - calculate_layout(); } - void TomGameApp::draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y) + void TomGameApp::calculate_button_layout() { - if (spriteRasterizer == nullptr || !sprite.is_valid()) - { - return; - } - - spriteRasterizer->DrawSprite(sprite, centerX - sprite.width / 2, y); + buttonX = (screenWidth - TomAtlas::ui_record.width) / 2; + buttonY = screenHeight - TomAtlas::ui_record.height - ButtonBottomPadding; } - const RenderData::Sprite TomGameApp::get_current_tom_sprite() const + bool TomGameApp::is_record_button_hit(int32_t x, int32_t y) const { - if (state == TomGameState::Recording && assets.tomListen != nullptr) - { - return RenderData::Sprite(assets.tomListen); - } - - if (state == TomGameState::Speaking) - { - const RenderData::Sprite* sprite = speakingAnimator.get_current_sprite(); - if (sprite != nullptr) - { - return *sprite; - } - } - - return RenderData::Sprite(assets.tomStand); + return x >= buttonX && + x < buttonX + TomAtlas::ui_record.width && + y >= buttonY && + y < buttonY + TomAtlas::ui_record.height; } } diff --git a/src/Apps/Game/src/app/TomGameApp.h b/src/Apps/Game/src/app/TomGameApp.h index 3afbbb6..7631628 100644 --- a/src/Apps/Game/src/app/TomGameApp.h +++ b/src/Apps/Game/src/app/TomGameApp.h @@ -1,24 +1,13 @@ #pragma once + #include #include #include "../audio/VoicePlayer.h" #include "../audio/VoiceRecorder.h" -#include "../components/SpriteAnimator.h" -#include "Sprite.h" namespace Core { - class FrameBuffer; -} - -namespace Rasterizer -{ - class SpriteRasterizer; -} - -namespace RenderData -{ - class Image; + class DrawContext; } namespace Platform @@ -26,6 +15,7 @@ namespace Platform class IAudioInput; class IAudioOutput; class IButtonInput; + class IPointerInput; } namespace Game @@ -34,85 +24,58 @@ namespace Game { Idle, Recording, - Processing, Speaking }; - struct TomGameAssets - { - const RenderData::Image* background; - const RenderData::Image* tomStand; - const RenderData::Image* tomListen; - const RenderData::Image* tomSay1; - const RenderData::Image* tomSay2; - const RenderData::Image* tomSay3; - const RenderData::Image* tomSay4; - const RenderData::Image* button; - - TomGameAssets() : - background(nullptr), - tomStand(nullptr), - tomListen(nullptr), - tomSay1(nullptr), - tomSay2(nullptr), - tomSay3(nullptr), - tomSay4(nullptr), - button(nullptr) - {} - }; - class TomGameApp { private: - Core::FrameBuffer* frameBuffer; - Rasterizer::SpriteRasterizer* spriteRasterizer; Platform::IAudioInput* audioInput; Platform::IAudioOutput* audioOutput; Platform::IButtonInput* buttonInput; + Platform::IPointerInput* pointerInput; - TomGameAssets assets; TomGameState state; VoiceRecorder recorder; VoicePlayer player; - SpriteAnimator speakingAnimator; - std::vector speakingFrames; int32_t screenWidth; int32_t screenHeight; - int32_t tomX; - int32_t tomY; int32_t buttonX; int32_t buttonY; uint32_t audioSampleRate; uint32_t audioChannels; + uint32_t maxRecordMs; + uint32_t recordingElapsedMs; + uint32_t speakingAnimationMs; float pitchFactor; float outputGain; - void calculate_layout(); + void update_input(bool& recordTrigger); void start_recording(); + void update_recording(uint32_t deltaMs); void finish_recording(); - void process_voice(); void start_speaking(const std::vector& samples); + void update_speaking(uint32_t deltaMs); void back_to_idle(); - void draw_sprite_centered(const RenderData::Sprite& sprite, int32_t centerX, int32_t y); - const RenderData::Sprite get_current_tom_sprite() const; + + void calculate_button_layout(); + bool is_record_button_hit(int32_t x, int32_t y) const; public: TomGameApp( - Core::FrameBuffer* frameBuffer, - Rasterizer::SpriteRasterizer* spriteRasterizer, + int32_t screenWidth, + int32_t screenHeight, Platform::IAudioInput* audioInput, Platform::IAudioOutput* audioOutput, - Platform::IButtonInput* buttonInput = nullptr); + Platform::IButtonInput* buttonInput, + Platform::IPointerInput* pointerInput); - void set_assets(const TomGameAssets& assets); void configure_audio(uint32_t sampleRate = 16000, uint32_t channels = 1); - void update(float deltaTime, bool buttonPressed = false); - void draw(); + void update(uint32_t deltaMs); + void draw(Core::DrawContext& ctx); - bool is_ready() const; - bool is_button_hit(int32_t x, int32_t y) const; TomGameState get_state() const { return state; } float get_record_volume() const { return recorder.get_last_volume(); } float get_play_progress() const { return player.get_progress(); } diff --git a/src/Core/Platform/DefaultHardware.h b/src/Core/Platform/DefaultHardware.h index 0352102..6099e9b 100644 --- a/src/Core/Platform/DefaultHardware.h +++ b/src/Core/Platform/DefaultHardware.h @@ -4,10 +4,12 @@ #include "AlsaAudioInput.h" #include "AlsaAudioOutput.h" #include "EvdevButtonInput.h" +#include "EvdevTouchInput.h" #else #include "SdlAudioInput.h" #include "SdlAudioOutput.h" #include "SdlKeyboardButtonInput.h" +#include "SdlPointerInput.h" #endif namespace Platform @@ -16,9 +18,11 @@ namespace Platform typedef AlsaAudioInput DefaultAudioInput; typedef AlsaAudioOutput DefaultAudioOutput; typedef EvdevButtonInput DefaultButtonInput; + typedef EvdevTouchInput DefaultPointerInput; #else typedef SdlAudioInput DefaultAudioInput; typedef SdlAudioOutput DefaultAudioOutput; typedef SdlKeyboardButtonInput DefaultButtonInput; + typedef SdlPointerInput DefaultPointerInput; #endif } diff --git a/src/Core/Platform/EvdevTouchInput.cpp b/src/Core/Platform/EvdevTouchInput.cpp new file mode 100644 index 0000000..49f43b3 --- /dev/null +++ b/src/Core/Platform/EvdevTouchInput.cpp @@ -0,0 +1,205 @@ +#include "EvdevTouchInput.h" +#include +#include + +#if defined(__linux__) +#include +#include +#include +#include +#include +#endif + +namespace Platform +{ + EvdevTouchInput::EvdevTouchInput() + : device_path_("/dev/input/event1"), + screen_width_(0), + screen_height_(0), + x_(0), + y_(0), + raw_x_(0), + raw_y_(0), + abs_x_min_(0), + abs_x_max_(0), + abs_y_min_(0), + abs_y_max_(0), + opened_(false), + down_(false), + pressed_(false), + released_(false), + has_abs_x_range_(false), + has_abs_y_range_(false) +#if defined(__linux__) + , + fd_(-1) +#endif + { + } + + EvdevTouchInput::~EvdevTouchInput() + { + shutdown(); + } + + bool EvdevTouchInput::init( + const std::string& device_path, + int32_t screen_width, + int32_t screen_height) + { + shutdown(); + + device_path_ = device_path.empty() ? "/dev/input/event1" : device_path; + screen_width_ = screen_width; + screen_height_ = screen_height; + x_ = 0; + y_ = 0; + raw_x_ = 0; + raw_y_ = 0; + down_ = false; + pressed_ = false; + released_ = false; + has_abs_x_range_ = false; + has_abs_y_range_ = false; + +#if defined(__linux__) + fd_ = open(device_path_.c_str(), O_RDONLY | O_NONBLOCK); + if (fd_ < 0) + { + std::cerr << "EvdevTouchInput open failed: " << device_path_ << std::endl; + return false; + } + + input_absinfo abs_x; + if (ioctl(fd_, EVIOCGABS(ABS_X), &abs_x) == 0 || + ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_X), &abs_x) == 0) + { + abs_x_min_ = abs_x.minimum; + abs_x_max_ = abs_x.maximum; + has_abs_x_range_ = abs_x_max_ > abs_x_min_; + } + + input_absinfo abs_y; + if (ioctl(fd_, EVIOCGABS(ABS_Y), &abs_y) == 0 || + ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_Y), &abs_y) == 0) + { + abs_y_min_ = abs_y.minimum; + abs_y_max_ = abs_y.maximum; + has_abs_y_range_ = abs_y_max_ > abs_y_min_; + } + + opened_ = true; + return true; +#else + std::cerr << "EvdevTouchInput is only implemented on Linux evdev." << std::endl; + return false; +#endif + } + + void EvdevTouchInput::update() + { + pressed_ = false; + released_ = false; + + if (!opened_) + { + return; + } + +#if defined(__linux__) + input_event event; + while (true) + { + const ssize_t bytes = read(fd_, &event, sizeof(event)); + if (bytes == static_cast(sizeof(event))) + { + if (event.type == EV_ABS) + { + if (event.code == ABS_X || event.code == ABS_MT_POSITION_X) + { + raw_x_ = event.value; + x_ = map_x(raw_x_); + } + else if (event.code == ABS_Y || event.code == ABS_MT_POSITION_Y) + { + raw_y_ = event.value; + y_ = map_y(raw_y_); + } + else if (event.code == ABS_MT_TRACKING_ID) + { + set_down(event.value >= 0); + } + } + else if (event.type == EV_KEY) + { + if (event.code == BTN_TOUCH || event.code == BTN_LEFT) + { + set_down(event.value != 0); + } + } + } + else + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + std::cerr << "EvdevTouchInput read failed." << std::endl; + } + break; + } + } +#endif + } + + void EvdevTouchInput::shutdown() + { +#if defined(__linux__) + if (fd_ >= 0) + { + close(fd_); + fd_ = -1; + } +#endif + opened_ = false; + down_ = false; + pressed_ = false; + released_ = false; + } + + int32_t EvdevTouchInput::map_x(int32_t raw_x) const + { + if (screen_width_ <= 0 || !has_abs_x_range_) + { + return raw_x; + } + + const int32_t clamped = std::max(abs_x_min_, std::min(raw_x, abs_x_max_)); + const int64_t numerator = static_cast(clamped - abs_x_min_) * static_cast(screen_width_ - 1); + return static_cast(numerator / static_cast(abs_x_max_ - abs_x_min_)); + } + + int32_t EvdevTouchInput::map_y(int32_t raw_y) const + { + if (screen_height_ <= 0 || !has_abs_y_range_) + { + return raw_y; + } + + const int32_t clamped = std::max(abs_y_min_, std::min(raw_y, abs_y_max_)); + const int64_t numerator = static_cast(clamped - abs_y_min_) * static_cast(screen_height_ - 1); + return static_cast(numerator / static_cast(abs_y_max_ - abs_y_min_)); + } + + void EvdevTouchInput::set_down(bool down) + { + if (down && !down_) + { + pressed_ = true; + } + else if (!down && down_) + { + released_ = true; + } + + down_ = down; + } +} diff --git a/src/Core/Platform/EvdevTouchInput.h b/src/Core/Platform/EvdevTouchInput.h new file mode 100644 index 0000000..1650380 --- /dev/null +++ b/src/Core/Platform/EvdevTouchInput.h @@ -0,0 +1,54 @@ +#pragma once + +#include "PointerInput.h" + +namespace Platform +{ + class EvdevTouchInput : public IPointerInput + { + private: + std::string device_path_; + int32_t screen_width_; + int32_t screen_height_; + int32_t x_; + int32_t y_; + int32_t raw_x_; + int32_t raw_y_; + int32_t abs_x_min_; + int32_t abs_x_max_; + int32_t abs_y_min_; + int32_t abs_y_max_; + bool opened_; + bool down_; + bool pressed_; + bool released_; + bool has_abs_x_range_; + bool has_abs_y_range_; +#if defined(__linux__) + int fd_; +#endif + + int32_t map_x(int32_t raw_x) const; + int32_t map_y(int32_t raw_y) const; + void set_down(bool down); + + public: + EvdevTouchInput(); + ~EvdevTouchInput(); + + bool init( + const std::string& device_path = "/dev/input/event1", + int32_t screen_width = 0, + int32_t screen_height = 0) override; + void update() override; + void shutdown() override; + + bool is_open() const override { return opened_; } + bool is_down() const override { return down_; } + bool was_pressed() const override { return pressed_; } + bool was_released() const override { return released_; } + int32_t get_x() const override { return x_; } + int32_t get_y() const override { return y_; } + const std::string& get_device_path() const override { return device_path_; } + }; +} diff --git a/src/Core/Platform/PointerInput.h b/src/Core/Platform/PointerInput.h new file mode 100644 index 0000000..db83ad3 --- /dev/null +++ b/src/Core/Platform/PointerInput.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace Platform +{ + class IPointerInput + { + public: + virtual ~IPointerInput() {} + + virtual bool init( + const std::string& device_path = "", + int32_t screen_width = 0, + int32_t screen_height = 0) = 0; + virtual void update() = 0; + virtual void shutdown() = 0; + + virtual bool is_open() const = 0; + virtual bool is_down() const = 0; + virtual bool was_pressed() const = 0; + virtual bool was_released() const = 0; + virtual int32_t get_x() const = 0; + virtual int32_t get_y() const = 0; + virtual const std::string& get_device_path() const = 0; + }; +} diff --git a/src/Core/Platform/SdlPointerInput.cpp b/src/Core/Platform/SdlPointerInput.cpp new file mode 100644 index 0000000..0726acd --- /dev/null +++ b/src/Core/Platform/SdlPointerInput.cpp @@ -0,0 +1,109 @@ +#include "SdlPointerInput.h" +#include +#include + +namespace +{ + static bool EnsureSdlEvents() + { + if (SDL_WasInit(0) == 0 && SDL_Init(0) < 0) + { + std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl; + return false; + } + + if ((SDL_WasInit(SDL_INIT_EVENTS) & SDL_INIT_EVENTS) == 0 && + SDL_InitSubSystem(SDL_INIT_EVENTS) < 0) + { + std::cerr << "SDL events init failed: " << SDL_GetError() << std::endl; + return false; + } + + return true; + } +} + +namespace Platform +{ + SdlPointerInput::SdlPointerInput() + : device_path_("mouse"), + screen_width_(0), + screen_height_(0), + x_(0), + y_(0), + opened_(false), + down_(false), + pressed_(false), + released_(false) + { + } + + SdlPointerInput::~SdlPointerInput() + { + shutdown(); + } + + bool SdlPointerInput::init( + const std::string& device_path, + int32_t screen_width, + int32_t screen_height) + { + if (!EnsureSdlEvents()) + { + return false; + } + + device_path_ = device_path.empty() ? "mouse" : device_path; + screen_width_ = screen_width; + screen_height_ = screen_height; + x_ = 0; + y_ = 0; + opened_ = true; + down_ = false; + pressed_ = false; + released_ = false; + return true; + } + + void SdlPointerInput::update() + { + pressed_ = false; + released_ = false; + + if (!opened_) + { + return; + } + + SDL_PumpEvents(); + + int mouse_x = 0; + int mouse_y = 0; + const Uint32 state = SDL_GetMouseState(&mouse_x, &mouse_y); + + if (screen_width_ > 0) + { + mouse_x = std::max(0, std::min(mouse_x, screen_width_ - 1)); + } + if (screen_height_ > 0) + { + mouse_y = std::max(0, std::min(mouse_y, screen_height_ - 1)); + } + + x_ = mouse_x; + y_ = mouse_y; + + const bool current_down = (state & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; + pressed_ = current_down && !down_; + released_ = !current_down && down_; + down_ = current_down; + } + + void SdlPointerInput::shutdown() + { + opened_ = false; + down_ = false; + pressed_ = false; + released_ = false; + } +} diff --git a/src/Core/Platform/SdlPointerInput.h b/src/Core/Platform/SdlPointerInput.h new file mode 100644 index 0000000..b186f51 --- /dev/null +++ b/src/Core/Platform/SdlPointerInput.h @@ -0,0 +1,40 @@ +#pragma once + +#include "PointerInput.h" +#include + +namespace Platform +{ + class SdlPointerInput : public IPointerInput + { + private: + std::string device_path_; + int32_t screen_width_; + int32_t screen_height_; + int32_t x_; + int32_t y_; + bool opened_; + bool down_; + bool pressed_; + bool released_; + + public: + SdlPointerInput(); + ~SdlPointerInput(); + + bool init( + const std::string& device_path = "mouse", + int32_t screen_width = 0, + int32_t screen_height = 0) override; + void update() override; + void shutdown() override; + + bool is_open() const override { return opened_; } + bool is_down() const override { return down_; } + bool was_pressed() const override { return pressed_; } + bool was_released() const override { return released_; } + int32_t get_x() const override { return x_; } + int32_t get_y() const override { return y_; } + const std::string& get_device_path() const override { return device_path_; } + }; +}