diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 25382f2..492e190 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -31,6 +31,18 @@ jobs: - name: cppcheck run: cppcheck --std=c++17 src/ + code-format: + name: Code format check + + runs-on: ubuntu-latest + + steps: + - name: Clone + uses: actions/checkout@v3 + + - name: clang-format + run: find src -type f -name \*.cc -o -name \*.h | xargs clang-format --dry-run --Werror + android: name: Android @@ -41,7 +53,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Java - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin java-version: 11 @@ -78,7 +90,7 @@ jobs: ios: name: iOS - runs-on: macos-11 + runs-on: macos-12 steps: - name: Clone @@ -88,30 +100,31 @@ jobs: uses: actions/cache@v3 with: path: build - key: ios-cmake-v1 + key: ios-cmake-v2 - name: Configure run: | cmake \ -B build \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/ios.toolchain.cmake \ -D ENABLE_BITCODE=0 \ -D PLATFORM=OS64 \ + -G Xcode \ + -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY='' \ # EOL - name: Build run: | cmake \ --build build \ + --config RelWithDebInfo \ -j $(sysctl -n hw.physicalcpu) \ - --target package \ # EOL - # TODO: Should be a part of packaging. - - name: Prepare for uploading + - name: Pack run: | - cp build/fallout2-ce.zip build/fallout2-ce.ipa + cd build + cpack -C RelWithDebInfo - name: Upload uses: actions/upload-artifact@v3 @@ -200,28 +213,34 @@ jobs: uses: actions/cache@v3 with: path: build - key: macos-cmake-v3 + key: macos-cmake-v4 - name: Configure run: | cmake \ -B build \ - -D CMAKE_BUILD_TYPE=RelWithDebInfo \ + -G Xcode \ + -D CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY='' \ # EOL - name: Build run: | cmake \ --build build \ + --config RelWithDebInfo \ -j $(sysctl -n hw.physicalcpu) \ - --target package \ # EOL + - name: Pack + run: | + cd build + cpack -C RelWithDebInfo + - name: Upload uses: actions/upload-artifact@v3 with: name: fallout2-ce-macos.dmg - path: build/fallout2-ce.dmg + path: build/Fallout II Community Edition.dmg retention-days: 7 windows: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cf9e53..dc311d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC "src/sfall_lists.h" "src/sfall_opcodes.cc" "src/sfall_opcodes.h" + "src/touch.cc" + "src/touch.h" ) if(IOS) @@ -303,22 +305,45 @@ if (WIN32) endif() if(APPLE) - target_sources(${EXECUTABLE_NAME} PUBLIC "os/macos/fallout2-ce.icns") - set_source_files_properties("os/macos/fallout2-ce.icns" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") - if(IOS) - target_sources(${EXECUTABLE_NAME} PUBLIC "os/ios/LaunchScreen.storyboard") - set_source_files_properties("os/ios/LaunchScreen.storyboard" PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") - set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/ios/Info.plist") - set_target_properties(${EXECUTABLE_NAME} PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") + set(RESOURCES + "os/ios/AppIcon.xcassets" + "os/ios/LaunchScreen.storyboard" + ) + + target_sources(${EXECUTABLE_NAME} PUBLIC ${RESOURCES}) + set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + set_target_properties(${EXECUTABLE_NAME} PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/ios/Info.plist" + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.alexbatalov.fallout2-ce" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" + ) + + set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}") + set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 2") else() - set_target_properties(${EXECUTABLE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/macos/Info.plist") + set(RESOURCES + "os/macos/fallout2-ce.icns" + ) + + target_sources(${EXECUTABLE_NAME} PUBLIC ${RESOURCES}) + set_source_files_properties(${RESOURCES} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + + set_target_properties(${EXECUTABLE_NAME} PROPERTIES + OUTPUT_NAME "Fallout II Community Edition" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/os/macos/Info.plist" + XCODE_ATTRIBUTE_EXECUTABLE_NAME "${EXECUTABLE_NAME}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.alexbatalov.fallout2-ce" + ) + + set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns") + set(MACOSX_BUNDLE_BUNDLE_NAME "Fallout II: Community Edition") + set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout II") endif() set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.alexbatalov.fallout2-ce") - set(MACOSX_BUNDLE_BUNDLE_NAME "${EXECUTABLE_NAME}") - set(MACOSX_BUNDLE_ICON_FILE "fallout2-ce.icns") - set(MACOSX_BUNDLE_DISPLAY_NAME "Fallout 2") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "1.2.0") set(MACOSX_BUNDLE_BUNDLE_VERSION "1.2.0") endif() @@ -348,24 +373,20 @@ if(APPLE) set(CPACK_GENERATOR "ZIP") set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) set(CPACK_PACKAGE_FILE_NAME "fallout2-ce") + set(CPACK_ARCHIVE_FILE_EXTENSION "ipa") else() install(TARGETS ${EXECUTABLE_NAME} DESTINATION .) - install(CODE " - include(BundleUtilities) - fixup_bundle(${CMAKE_BINARY_DIR}/${MACOSX_BUNDLE_BUNDLE_NAME}.app \"\" \"\") - " - COMPONENT Runtime) if (CPACK_BUNDLE_APPLE_CERT_APP) install(CODE " - execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" ${CMAKE_BINARY_DIR}/${MACOSX_BUNDLE_BUNDLE_NAME}.app) + execute_process(COMMAND codesign --deep --force --options runtime --sign \"${CPACK_BUNDLE_APPLE_CERT_APP}\" ${CMAKE_BINARY_DIR}/Fallout II Community Edition.app) " COMPONENT Runtime) endif() set(CPACK_GENERATOR "DragNDrop") set(CPACK_DMG_DISABLE_APPLICATIONS_SYMLINK ON) - set(CPACK_PACKAGE_FILE_NAME "fallout2-ce") + set(CPACK_PACKAGE_FILE_NAME "Fallout II Community Edition") endif() include(CPack) diff --git a/README.md b/README.md index e58b082..ff0aae7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,11 @@ $ sudo apt install libsdl2-2.0-0 ### Android -> **NOTE**: Fallout 2 was designed with mouse in mind. There are many controls that require precise cursor positioning, which is not possible with fingers. When playing on Android you'll use fingers to move mouse cursor, not a character, or a map. Double tap to "click" left mouse button in the current cursor position, triple tap to "click" right mouse button. It might feel awkward at first, but it's super handy - you can play with just a thumb. This is not set in stone and might change in the future. +> **NOTE**: Fallout 2 was designed with mouse in mind. There are many controls that require precise cursor positioning, which is not possible with fingers. Current control scheme resembles trackpad usage: +- One finger moves mouse cursor around. +- Tap one finger for left mouse click. +- Tap two fingers for right mouse click (switches mouse cursor mode). +- Move two fingers to scroll current view (map view, worldmap view, inventory scrollers). > **NOTE**: From Android standpoint release and debug builds are different apps. Both apps require their own copy of game assets and have their own savegames. This is intentional. As a gamer just stick with release version and check for updates. diff --git a/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png b/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png new file mode 100644 index 0000000..cf365dc Binary files /dev/null and b/os/ios/AppIcon.xcassets/AppIcon.appiconset/AppIcon.png differ diff --git a/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json b/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..cefcc87 --- /dev/null +++ b/os/ios/AppIcon.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "AppIcon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/os/ios/AppIcon.xcassets/Contents.json b/os/ios/AppIcon.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/os/ios/AppIcon.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/os/ios/Info.plist b/os/ios/Info.plist index 3eedf54..81b2bfd 100644 --- a/os/ios/Info.plist +++ b/os/ios/Info.plist @@ -8,8 +8,6 @@ ${MACOSX_BUNDLE_DISPLAY_NAME} CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} - CFBundleIconFile - ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion @@ -22,8 +20,6 @@ ${MACOSX_BUNDLE_SHORT_VERSION_STRING} CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} - LSApplicationCategoryType - public.app-category.role-playing-games LSMinimumSystemVersion 11.0 LSRequiresIPhoneOS @@ -32,11 +28,6 @@ True UIApplicationSupportsIndirectInputEvents - UIDeviceFamily - - 1 - 2 - UIFileSharingEnabled UILaunchStoryboardName diff --git a/os/macos/Info.plist b/os/macos/Info.plist index 5d5b555..38b9b8d 100644 --- a/os/macos/Info.plist +++ b/os/macos/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - English + en CFBundleDisplayName ${MACOSX_BUNDLE_DISPLAY_NAME} CFBundleExecutable @@ -28,6 +28,8 @@ ${MACOSX_BUNDLE_COPYRIGHT} NSHighResolutionCapable True + LSApplicationCategoryType + public.app-category.role-playing-games LSMinimumSystemVersion 10.11 SDL_FILESYSTEM_BASE_DIR_TYPE diff --git a/src/animation.h b/src/animation.h index 540d738..4e146a4 100644 --- a/src/animation.h +++ b/src/animation.h @@ -92,7 +92,7 @@ typedef enum AnimationType { LAST_SF_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD_SF, } AnimationType; -#define FID_ANIM_TYPE(value) ((value) & 0xFF0000) >> 16 +#define FID_ANIM_TYPE(value) ((value)&0xFF0000) >> 16 // Signature of animation callback accepting 2 parameters. typedef int(AnimationCallback)(void* a1, void* a2); diff --git a/src/audio.cc b/src/audio.cc index 77000c6..1d7e6e3 100644 --- a/src/audio.cc +++ b/src/audio.cc @@ -58,7 +58,7 @@ static int audioSoundDecoderReadHandler(void* data, void* buffer, unsigned int s // AudioOpen // 0x41A2EC -int audioOpen(const char* fname, int* channels, int* sampleRate) +int audioOpen(const char* fname, int* sampleRate) { char path[80]; snprintf(path, sizeof(path), "%s", fname); @@ -101,7 +101,6 @@ int audioOpen(const char* fname, int* channels, int* sampleRate) audioFile->soundDecoder = soundDecoderInit(audioSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize)); audioFile->fileSize *= 2; - *channels = audioFile->channels; *sampleRate = audioFile->sampleRate; } else { audioFile->fileSize = fileGetSize(stream); diff --git a/src/audio.h b/src/audio.h index 752b914..b791c41 100644 --- a/src/audio.h +++ b/src/audio.h @@ -5,7 +5,7 @@ namespace fallout { typedef bool(AudioQueryCompressedFunc)(char* filePath); -int audioOpen(const char* fname, int* channels, int* sampleRate); +int audioOpen(const char* fname, int* sampleRate); int audioClose(int handle); int audioRead(int handle, void* buffer, unsigned int size); long audioSeek(int handle, long offset, int origin); diff --git a/src/audio_file.cc b/src/audio_file.cc index 8333c5a..22a0ba8 100644 --- a/src/audio_file.cc +++ b/src/audio_file.cc @@ -57,7 +57,7 @@ static int audioFileSoundDecoderReadHandler(void* data, void* buffer, unsigned i } // 0x41A88C -int audioFileOpen(const char* fname, int* channels, int* sampleRate) +int audioFileOpen(const char* fname, int* sampleRate) { char path[COMPAT_MAX_PATH]; strcpy(path, fname); @@ -99,7 +99,6 @@ int audioFileOpen(const char* fname, int* channels, int* sampleRate) audioFile->soundDecoder = soundDecoderInit(audioFileSoundDecoderReadHandler, audioFile->stream, &(audioFile->channels), &(audioFile->sampleRate), &(audioFile->fileSize)); audioFile->fileSize *= 2; - *channels = audioFile->channels; *sampleRate = audioFile->sampleRate; } else { audioFile->fileSize = getFileSize(stream); diff --git a/src/audio_file.h b/src/audio_file.h index a43bdc7..b9f8bf2 100644 --- a/src/audio_file.h +++ b/src/audio_file.h @@ -5,7 +5,7 @@ namespace fallout { typedef bool(AudioFileQueryCompressedFunc)(char* filePath); -int audioFileOpen(const char* fname, int* channels, int* sampleRate); +int audioFileOpen(const char* fname, int* sampleRate); int audioFileClose(int handle); int audioFileRead(int handle, void* buf, unsigned int size); long audioFileSeek(int handle, long offset, int origin); diff --git a/src/critter.cc b/src/critter.cc index cd896ab..4439eb0 100644 --- a/src/critter.cc +++ b/src/critter.cc @@ -996,8 +996,8 @@ bool _critter_is_prone(Object* critter) int anim = FID_ANIM_TYPE(critter->fid); return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0 - || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM) - || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM); + || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM) + || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM); } // critter_body_type diff --git a/src/dinput.cc b/src/dinput.cc index d84b9f3..4fae607 100644 --- a/src/dinput.cc +++ b/src/dinput.cc @@ -2,30 +2,9 @@ namespace fallout { -enum InputType { - INPUT_TYPE_MOUSE, - INPUT_TYPE_TOUCH, -} InputType; - -static int gLastInputType = INPUT_TYPE_MOUSE; - -static int gTouchMouseLastX = 0; -static int gTouchMouseLastY = 0; -static int gTouchMouseDeltaX = 0; -static int gTouchMouseDeltaY = 0; - -static int gTouchFingers = 0; -static unsigned int gTouchGestureLastTouchDownTimestamp = 0; -static unsigned int gTouchGestureLastTouchUpTimestamp = 0; -static int gTouchGestureTaps = 0; -static bool gTouchGestureHandled = false; - static int gMouseWheelDeltaX = 0; static int gMouseWheelDeltaY = 0; -extern int screenGetWidth(); -extern int screenGetHeight(); - // 0x4E0400 bool directInputInit() { @@ -71,49 +50,14 @@ bool mouseDeviceUnacquire() // 0x4E053C bool mouseDeviceGetData(MouseData* mouseState) { - if (gLastInputType == INPUT_TYPE_TOUCH) { - mouseState->x = gTouchMouseDeltaX; - mouseState->y = gTouchMouseDeltaY; - mouseState->buttons[0] = 0; - mouseState->buttons[1] = 0; - mouseState->wheelX = 0; - mouseState->wheelY = 0; - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; + Uint32 buttons = SDL_GetRelativeMouseState(&(mouseState->x), &(mouseState->y)); + mouseState->buttons[0] = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; + mouseState->buttons[1] = (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; + mouseState->wheelX = gMouseWheelDeltaX; + mouseState->wheelY = gMouseWheelDeltaY; - if (gTouchFingers == 0) { - if (SDL_GetTicks() - gTouchGestureLastTouchUpTimestamp > 150) { - if (!gTouchGestureHandled) { - if (gTouchGestureTaps == 2) { - mouseState->buttons[0] = 1; - gTouchGestureHandled = true; - } else if (gTouchGestureTaps == 3) { - mouseState->buttons[1] = 1; - gTouchGestureHandled = true; - } - } - } - } else if (gTouchFingers == 1) { - if (SDL_GetTicks() - gTouchGestureLastTouchDownTimestamp > 150) { - if (gTouchGestureTaps == 1) { - mouseState->buttons[0] = 1; - gTouchGestureHandled = true; - } else if (gTouchGestureTaps == 2) { - mouseState->buttons[1] = 1; - gTouchGestureHandled = true; - } - } - } - } else { - Uint32 buttons = SDL_GetRelativeMouseState(&(mouseState->x), &(mouseState->y)); - mouseState->buttons[0] = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; - mouseState->buttons[1] = (buttons & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; - mouseState->wheelX = gMouseWheelDeltaX; - mouseState->wheelY = gMouseWheelDeltaY; - - gMouseWheelDeltaX = 0; - gMouseWheelDeltaY = 0; - } + gMouseWheelDeltaX = 0; + gMouseWheelDeltaY = 0; return true; } @@ -174,70 +118,6 @@ void handleMouseEvent(SDL_Event* event) gMouseWheelDeltaX += event->wheel.x; gMouseWheelDeltaY += event->wheel.y; } - - if (gLastInputType != INPUT_TYPE_MOUSE) { - // Reset touch data. - gTouchMouseLastX = 0; - gTouchMouseLastY = 0; - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; - - gTouchFingers = 0; - gTouchGestureLastTouchDownTimestamp = 0; - gTouchGestureLastTouchUpTimestamp = 0; - gTouchGestureTaps = 0; - gTouchGestureHandled = false; - - gLastInputType = INPUT_TYPE_MOUSE; - } -} - -void handleTouchEvent(SDL_Event* event) -{ - int windowWidth = screenGetWidth(); - int windowHeight = screenGetHeight(); - - if (event->tfinger.type == SDL_FINGERDOWN) { - gTouchFingers++; - - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX = 0; - gTouchMouseDeltaY = 0; - - if (event->tfinger.timestamp - gTouchGestureLastTouchDownTimestamp > 250) { - gTouchGestureTaps = 0; - gTouchGestureHandled = false; - } - - gTouchGestureLastTouchDownTimestamp = event->tfinger.timestamp; - } else if (event->tfinger.type == SDL_FINGERMOTION) { - int prevX = gTouchMouseLastX; - int prevY = gTouchMouseLastY; - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX += gTouchMouseLastX - prevX; - gTouchMouseDeltaY += gTouchMouseLastY - prevY; - } else if (event->tfinger.type == SDL_FINGERUP) { - gTouchFingers--; - - int prevX = gTouchMouseLastX; - int prevY = gTouchMouseLastY; - gTouchMouseLastX = (int)(event->tfinger.x * windowWidth); - gTouchMouseLastY = (int)(event->tfinger.y * windowHeight); - gTouchMouseDeltaX += gTouchMouseLastX - prevX; - gTouchMouseDeltaY += gTouchMouseLastY - prevY; - - gTouchGestureTaps++; - gTouchGestureLastTouchUpTimestamp = event->tfinger.timestamp; - } - - if (gLastInputType != INPUT_TYPE_TOUCH) { - // Reset mouse data. - SDL_GetRelativeMouseState(NULL, NULL); - - gLastInputType = INPUT_TYPE_TOUCH; - } } } // namespace fallout diff --git a/src/game.cc b/src/game.cc index 01c87ec..5383fa4 100644 --- a/src/game.cc +++ b/src/game.cc @@ -3,12 +3,6 @@ #include #include -#ifdef _WIN32 -#include -#else -#include // access -#endif - #include "actions.h" #include "animation.h" #include "art.h" @@ -1336,12 +1330,12 @@ static int gameDbInit() for (patch_index = 0; patch_index < 1000; patch_index++) { snprintf(filename, sizeof(filename), "patch%03d.dat", patch_index); - if (access(filename, 0) == 0) { + if (compat_access(filename, 0) == 0) { dbOpen(filename, 0, NULL, 1); } } - if (access("f2_res.dat", 0) == 0) { + if (compat_access("f2_res.dat", 0) == 0) { dbOpen("f2_res.dat", 0, NULL, 1); } diff --git a/src/game_movie.cc b/src/game_movie.cc index b4253a8..c8cb9aa 100644 --- a/src/game_movie.cc +++ b/src/game_movie.cc @@ -18,6 +18,7 @@ #include "settings.h" #include "svga.h" #include "text_font.h" +#include "touch.h" #include "window_manager.h" namespace fallout { @@ -249,6 +250,11 @@ int gameMoviePlay(int movie, int flags) break; } + Gesture gesture; + if (touch_get_gesture(&gesture) && gesture.state == kEnded) { + break; + } + int x; int y; _mouse_get_raw_state(&x, &y, &buttons); diff --git a/src/game_sound.cc b/src/game_sound.cc index b1033ed..618457b 100644 --- a/src/game_sound.cc +++ b/src/game_sound.cc @@ -157,7 +157,7 @@ static int _gsound_speech_volume_get_set(int volume); static void speechPause(); static void speechResume(); static void _gsound_bkg_proc(); -static int gameSoundFileOpen(const char* fname, int* channels, int* sampleRate); +static int gameSoundFileOpen(const char* fname, int* sampleRate); static long _gsound_write_(); static long gameSoundFileTellNotImplemented(int handle); static int gameSoundFileWrite(int handle, const void* buf, unsigned int size); @@ -1548,7 +1548,7 @@ void _gsound_bkg_proc() } // 0x451A08 -int gameSoundFileOpen(const char* fname, int* channels, int* sampleRate) +int gameSoundFileOpen(const char* fname, int* sampleRate) { File* stream = fileOpen(fname, "rb"); if (stream == NULL) { diff --git a/src/input.cc b/src/input.cc index e18cb02..ccfa5a6 100644 --- a/src/input.cc +++ b/src/input.cc @@ -11,6 +11,7 @@ #include "mouse.h" #include "svga.h" #include "text_font.h" +#include "touch.h" #include "vcr.h" #include "win32.h" @@ -1026,24 +1027,24 @@ static void buildNormalizedQwertyKeys() keys[SDL_SCANCODE_F13] = -1; keys[SDL_SCANCODE_F14] = -1; keys[SDL_SCANCODE_F15] = -1; - //keys[DIK_KANA] = -1; - //keys[DIK_CONVERT] = -1; - //keys[DIK_NOCONVERT] = -1; - //keys[DIK_YEN] = -1; + // keys[DIK_KANA] = -1; + // keys[DIK_CONVERT] = -1; + // keys[DIK_NOCONVERT] = -1; + // keys[DIK_YEN] = -1; keys[SDL_SCANCODE_KP_EQUALS] = -1; - //keys[DIK_PREVTRACK] = -1; - //keys[DIK_AT] = -1; - //keys[DIK_COLON] = -1; - //keys[DIK_UNDERLINE] = -1; - //keys[DIK_KANJI] = -1; + // keys[DIK_PREVTRACK] = -1; + // keys[DIK_AT] = -1; + // keys[DIK_COLON] = -1; + // keys[DIK_UNDERLINE] = -1; + // keys[DIK_KANJI] = -1; keys[SDL_SCANCODE_STOP] = -1; - //keys[DIK_AX] = -1; - //keys[DIK_UNLABELED] = -1; + // keys[DIK_AX] = -1; + // keys[DIK_UNLABELED] = -1; keys[SDL_SCANCODE_KP_ENTER] = SDL_SCANCODE_KP_ENTER; keys[SDL_SCANCODE_RCTRL] = SDL_SCANCODE_RCTRL; keys[SDL_SCANCODE_KP_COMMA] = -1; keys[SDL_SCANCODE_KP_DIVIDE] = SDL_SCANCODE_KP_DIVIDE; - //keys[DIK_SYSRQ] = 84; + // keys[DIK_SYSRQ] = 84; keys[SDL_SCANCODE_RALT] = SDL_SCANCODE_RALT; keys[SDL_SCANCODE_HOME] = SDL_SCANCODE_HOME; keys[SDL_SCANCODE_UP] = SDL_SCANCODE_UP; @@ -1084,9 +1085,13 @@ void _GNW95_process_message() handleMouseEvent(&e); break; case SDL_FINGERDOWN: + touch_handle_start(&(e.tfinger)); + break; case SDL_FINGERMOTION: + touch_handle_move(&(e.tfinger)); + break; case SDL_FINGERUP: - handleTouchEvent(&e); + touch_handle_end(&(e.tfinger)); break; case SDL_KEYDOWN: case SDL_KEYUP: @@ -1121,6 +1126,8 @@ void _GNW95_process_message() } } + touch_process_gesture(); + if (gProgramIsActive && !keyboardIsDisabled()) { // NOTE: Uninline int tick = getTicks(); diff --git a/src/inventory.cc b/src/inventory.cc index eaac208..351584c 100644 --- a/src/inventory.cc +++ b/src/inventory.cc @@ -2694,7 +2694,7 @@ void inventoryOpenUseItemOn(Object* a1) inventoryWindowOpenContextMenu(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } else { int inventoryItemIndex = _pud->length - (_stack_offset[_curr_stack] + keyCode - 1000 + 1); - // SFALL: Fix crash when clicking on empty space in the inventory list + // SFALL: Fix crash when clicking on empty space in the inventory list // opened by "Use Inventory Item On" (backpack) action icon if (inventoryItemIndex < _pud->length && inventoryItemIndex >= 0) { InventoryItem* inventoryItem = &(_pud->items[inventoryItemIndex]); diff --git a/src/kb.cc b/src/kb.cc index 552b1fe..b9253c2 100644 --- a/src/kb.cc +++ b/src/kb.cc @@ -1400,11 +1400,11 @@ static void keyboardBuildFrenchConfiguration() gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; switch (gKeyboardLayout) { case KEYBOARD_LAYOUT_QWERTY: @@ -1583,11 +1583,11 @@ static void keyboardBuildGermanConfiguration() gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_BACKSLASH].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = KEY_166; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = KEY_166; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; switch (gKeyboardLayout) { case KEYBOARD_LAYOUT_FRENCH: @@ -1684,11 +1684,11 @@ static void keyboardBuildItalianConfiguration() gLogicalKeyEntries[SDL_SCANCODE_GRAVE].rmenu = -1; gLogicalKeyEntries[SDL_SCANCODE_GRAVE].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; gLogicalKeyEntries[SDL_SCANCODE_1].unmodified = KEY_1; gLogicalKeyEntries[SDL_SCANCODE_1].shift = KEY_EXCLAMATION; @@ -1896,11 +1896,11 @@ static void keyboardBuildSpanishConfiguration() gLogicalKeyEntries[SDL_SCANCODE_RIGHTBRACKET].rmenu = KEY_BRACKET_RIGHT; gLogicalKeyEntries[SDL_SCANCODE_RIGHTBRACKET].ctrl = -1; - //gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; - //gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; - //gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; - //gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; + // gLogicalKeyEntries[DIK_OEM_102].unmodified = KEY_LESS; + // gLogicalKeyEntries[DIK_OEM_102].shift = KEY_GREATER; + // gLogicalKeyEntries[DIK_OEM_102].lmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].rmenu = -1; + // gLogicalKeyEntries[DIK_OEM_102].ctrl = -1; gLogicalKeyEntries[SDL_SCANCODE_SEMICOLON].unmodified = KEY_241; gLogicalKeyEntries[SDL_SCANCODE_SEMICOLON].shift = KEY_209; diff --git a/src/mouse.cc b/src/mouse.cc index 6e8a8bb..7815d70 100644 --- a/src/mouse.cc +++ b/src/mouse.cc @@ -6,6 +6,7 @@ #include "kb.h" #include "memory.h" #include "svga.h" +#include "touch.h" #include "vcr.h" namespace fallout { @@ -381,6 +382,54 @@ void _mouse_info() return; } + Gesture gesture; + if (touch_get_gesture(&gesture)) { + static int prevx; + static int prevy; + + switch (gesture.type) { + case kTap: + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(0, 0, MOUSE_STATE_LEFT_BUTTON_DOWN); + } else if (gesture.numberOfTouches == 2) { + _mouse_simulate_input(0, 0, MOUSE_STATE_RIGHT_BUTTON_DOWN); + } + break; + case kLongPress: + case kPan: + if (gesture.state == kBegan) { + prevx = gesture.x; + prevy = gesture.y; + } + + if (gesture.type == kLongPress) { + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, MOUSE_STATE_LEFT_BUTTON_DOWN); + } else if (gesture.numberOfTouches == 2) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, MOUSE_STATE_RIGHT_BUTTON_DOWN); + } + } else if (gesture.type == kPan) { + if (gesture.numberOfTouches == 1) { + _mouse_simulate_input(gesture.x - prevx, gesture.y - prevy, 0); + } else if (gesture.numberOfTouches == 2) { + gMouseWheelX = (prevx - gesture.x) / 2; + gMouseWheelY = (gesture.y - prevy) / 2; + + if (gMouseWheelX != 0 || gMouseWheelY != 0) { + gMouseEvent |= MOUSE_EVENT_WHEEL; + _raw_buttons |= MOUSE_EVENT_WHEEL; + } + } + } + + prevx = gesture.x; + prevy = gesture.y; + break; + } + + return; + } + int x; int y; int buttons = 0; diff --git a/src/obj_types.h b/src/obj_types.h index c1d1867..8638f90 100644 --- a/src/obj_types.h +++ b/src/obj_types.h @@ -29,7 +29,7 @@ enum { OBJ_TYPE_COUNT, }; -#define FID_TYPE(value) ((value) & 0xF000000) >> 24 +#define FID_TYPE(value) ((value)&0xF000000) >> 24 #define PID_TYPE(value) (value) >> 24 #define SID_TYPE(value) (value) >> 24 diff --git a/src/platform_compat.cc b/src/platform_compat.cc index b629fb6..031cdae 100644 --- a/src/platform_compat.cc +++ b/src/platform_compat.cc @@ -12,6 +12,7 @@ #include #include #else +#include #include #include #endif @@ -204,6 +205,7 @@ int compat_mkdir(const char* path) char nativePath[COMPAT_MAX_PATH]; strcpy(nativePath, path); compat_windows_path_to_native(nativePath); + compat_resolve_path(nativePath); #ifdef _WIN32 return mkdir(nativePath); @@ -228,6 +230,7 @@ FILE* compat_fopen(const char* path, const char* mode) char nativePath[COMPAT_MAX_PATH]; strcpy(nativePath, path); compat_windows_path_to_native(nativePath); + compat_resolve_path(nativePath); return fopen(nativePath, mode); } @@ -236,6 +239,7 @@ gzFile compat_gzopen(const char* path, const char* mode) char nativePath[COMPAT_MAX_PATH]; strcpy(nativePath, path); compat_windows_path_to_native(nativePath); + compat_resolve_path(nativePath); return gzopen(nativePath, mode); } @@ -274,6 +278,7 @@ int compat_remove(const char* path) char nativePath[COMPAT_MAX_PATH]; strcpy(nativePath, path); compat_windows_path_to_native(nativePath); + compat_resolve_path(nativePath); return remove(nativePath); } @@ -282,10 +287,12 @@ int compat_rename(const char* oldFileName, const char* newFileName) char nativeOldFileName[COMPAT_MAX_PATH]; strcpy(nativeOldFileName, oldFileName); compat_windows_path_to_native(nativeOldFileName); + compat_resolve_path(nativeOldFileName); char nativeNewFileName[COMPAT_MAX_PATH]; strcpy(nativeNewFileName, newFileName); compat_windows_path_to_native(nativeNewFileName); + compat_resolve_path(nativeNewFileName); return rename(nativeOldFileName, nativeNewFileName); } @@ -303,6 +310,69 @@ void compat_windows_path_to_native(char* path) #endif } +void compat_resolve_path(char* path) +{ +#ifndef _WIN32 + char* pch = path; + + DIR* dir; + if (pch[0] == '/') { + dir = opendir("/"); + pch++; + } else { + dir = opendir("."); + } + + while (dir != NULL) { + char* sep = strchr(pch, '/'); + size_t length; + if (sep != NULL) { + length = sep - pch; + } else { + length = strlen(pch); + } + + bool found = false; + + struct dirent* entry = readdir(dir); + while (entry != NULL) { + if (strlen(entry->d_name) == length && compat_strnicmp(pch, entry->d_name, length) == 0) { + strncpy(pch, entry->d_name, length); + found = true; + break; + } + entry = readdir(dir); + } + + closedir(dir); + dir = NULL; + + if (!found) { + break; + } + + if (sep == NULL) { + break; + } + + *sep = '\0'; + dir = opendir(path); + *sep = '/'; + + pch = sep + 1; + } +#endif +} + +int compat_access(const char* path, int mode) +{ + char nativePath[COMPAT_MAX_PATH]; + strcpy(nativePath, path); + compat_windows_path_to_native(nativePath); + compat_resolve_path(nativePath); + return access(nativePath, mode); +} + char* compat_strdup(const char* string) { return SDL_strdup(string); diff --git a/src/platform_compat.h b/src/platform_compat.h index 9fb7574..66db2bc 100644 --- a/src/platform_compat.h +++ b/src/platform_compat.h @@ -40,6 +40,8 @@ char* compat_gzgets(gzFile stream, char* buffer, int maxCount); int compat_remove(const char* path); int compat_rename(const char* oldFileName, const char* newFileName); void compat_windows_path_to_native(char* path); +void compat_resolve_path(char* path); +int compat_access(const char* path, int mode); char* compat_strdup(const char* string); long getFileSize(FILE* stream); diff --git a/src/sound.cc b/src/sound.cc index 0576358..5306543 100644 --- a/src/sound.cc +++ b/src/sound.cc @@ -49,7 +49,7 @@ static long soundFileSize(int fileHandle); static long soundTellData(int fileHandle); static int soundWriteData(int fileHandle, const void* buf, unsigned int size); static int soundReadData(int fileHandle, void* buf, unsigned int size); -static int soundOpenData(const char* filePath, int* channels, int* sampleRate); +static int soundOpenData(const char* filePath, int* sampleRate); static long soundSeekData(int fileHandle, long offset, int origin); static int soundCloseData(int fileHandle); static char* soundFileManglerDefaultImpl(char* fname); @@ -223,7 +223,7 @@ static int soundReadData(int fileHandle, void* buf, unsigned int size) } // 0x4AC768 -static int soundOpenData(const char* filePath, int* channels, int* sampleRate) +static int soundOpenData(const char* filePath, int* sampleRate) { int flags; @@ -616,7 +616,7 @@ int soundLoad(Sound* sound, char* filePath) return gSoundLastError; } - sound->io.fd = sound->io.open(gSoundFileNameMangler(filePath), &(sound->channels), &(sound->rate)); + sound->io.fd = sound->io.open(gSoundFileNameMangler(filePath), &(sound->rate)); if (sound->io.fd == -1) { gSoundLastError = SOUND_FILE_NOT_FOUND; return gSoundLastError; diff --git a/src/sound.h b/src/sound.h index 427b5f8..625bb90 100644 --- a/src/sound.h +++ b/src/sound.h @@ -46,7 +46,7 @@ typedef enum SoundError { SOUND_ERR_COUNT, } SoundError; -typedef int SoundOpenProc(const char* filePath, int* channels, int* sampleRate); +typedef int SoundOpenProc(const char* filePath, int* sampleRate); typedef int SoundCloseProc(int fileHandle); typedef int SoundReadProc(int fileHandle, void* buf, unsigned int size); typedef int SoundWriteProc(int fileHandle, const void* buf, unsigned int size); diff --git a/src/sound_effects_cache.cc b/src/sound_effects_cache.cc index 30f91ae..dd6094c 100644 --- a/src/sound_effects_cache.cc +++ b/src/sound_effects_cache.cc @@ -154,7 +154,7 @@ void soundEffectsCacheFlush() // sfxc_cached_open // 0x4A915C -int soundEffectsCacheFileOpen(const char* fname, int* channels, int* sampleRate) +int soundEffectsCacheFileOpen(const char* fname, int* sampleRate) { if (_sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) { return -1; diff --git a/src/sound_effects_cache.h b/src/sound_effects_cache.h index 579c856..483cb86 100644 --- a/src/sound_effects_cache.h +++ b/src/sound_effects_cache.h @@ -11,7 +11,7 @@ int soundEffectsCacheInit(int cache_size, const char* effectsPath); void soundEffectsCacheExit(); int soundEffectsCacheInitialized(); void soundEffectsCacheFlush(); -int soundEffectsCacheFileOpen(const char* fname, int* channels, int* sampleRate); +int soundEffectsCacheFileOpen(const char* fname, int* sampleRate); int soundEffectsCacheFileClose(int handle); int soundEffectsCacheFileRead(int handle, void* buf, unsigned int size); int soundEffectsCacheFileWrite(int handle, const void* buf, unsigned int size); diff --git a/src/touch.cc b/src/touch.cc new file mode 100644 index 0000000..473fe70 --- /dev/null +++ b/src/touch.cc @@ -0,0 +1,290 @@ +#include "touch.h" + +#include +#include + +#include "svga.h" + +namespace fallout { + +#define TOUCH_PHASE_BEGAN 0 +#define TOUCH_PHASE_MOVED 1 +#define TOUCH_PHASE_ENDED 2 + +#define MAX_TOUCHES 10 + +#define TAP_MAXIMUM_DURATION 75 +#define PAN_MINIMUM_MOVEMENT 4 +#define LONG_PRESS_MINIMUM_DURATION 500 + +struct TouchLocation { + int x; + int y; +}; + +struct Touch { + bool used; + SDL_FingerID fingerId; + TouchLocation startLocation; + Uint32 startTimestamp; + TouchLocation currentLocation; + Uint32 currentTimestamp; + int phase; +}; + +static Touch touches[MAX_TOUCHES]; +static Gesture currentGesture; +static std::stack gestureEventsQueue; + +static int find_touch(SDL_FingerID fingerId) +{ + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].fingerId == fingerId) { + return index; + } + } + return -1; +} + +static int find_unused_touch_index() +{ + for (int index = 0; index < MAX_TOUCHES; index++) { + if (!touches[index].used) { + return index; + } + } + return -1; +} + +static TouchLocation touch_get_start_location_centroid(int* indexes, int length) +{ + TouchLocation centroid; + centroid.x = 0; + centroid.y = 0; + for (int index = 0; index < length; index++) { + centroid.x += touches[indexes[index]].startLocation.x; + centroid.y += touches[indexes[index]].startLocation.y; + } + centroid.x /= length; + centroid.y /= length; + return centroid; +} + +static TouchLocation touch_get_current_location_centroid(int* indexes, int length) +{ + TouchLocation centroid; + centroid.x = 0; + centroid.y = 0; + for (int index = 0; index < length; index++) { + centroid.x += touches[indexes[index]].currentLocation.x; + centroid.y += touches[indexes[index]].currentLocation.y; + } + centroid.x /= length; + centroid.y /= length; + return centroid; +} + +void touch_handle_start(SDL_TouchFingerEvent* event) +{ + // On iOS `fingerId` is an address of underlying `UITouch` object. When + // `touchesBegan` is called this object might be reused, but with + // incresed `tapCount` (which is ignored in this implementation). + int index = find_touch(event->fingerId); + if (index == -1) { + index = find_unused_touch_index(); + } + + if (index != -1) { + Touch* touch = &(touches[index]); + touch->used = true; + touch->fingerId = event->fingerId; + touch->startTimestamp = event->timestamp; + touch->startLocation.x = static_cast(event->x * screenGetWidth()); + touch->startLocation.y = static_cast(event->y * screenGetHeight()); + touch->currentTimestamp = touch->startTimestamp; + touch->currentLocation = touch->startLocation; + touch->phase = TOUCH_PHASE_BEGAN; + } +} + +void touch_handle_move(SDL_TouchFingerEvent* event) +{ + int index = find_touch(event->fingerId); + if (index != -1) { + Touch* touch = &(touches[index]); + touch->currentTimestamp = event->timestamp; + touch->currentLocation.x = static_cast(event->x * screenGetWidth()); + touch->currentLocation.y = static_cast(event->y * screenGetHeight()); + touch->phase = TOUCH_PHASE_MOVED; + } +} + +void touch_handle_end(SDL_TouchFingerEvent* event) +{ + int index = find_touch(event->fingerId); + if (index != -1) { + Touch* touch = &(touches[index]); + touch->currentTimestamp = event->timestamp; + touch->currentLocation.x = static_cast(event->x * screenGetWidth()); + touch->currentLocation.y = static_cast(event->y * screenGetHeight()); + touch->phase = TOUCH_PHASE_ENDED; + } +} + +void touch_process_gesture() +{ + Uint32 sequenceStartTimestamp = -1; + int sequenceStartIndex = -1; + + // Find start of sequence (earliest touch). + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used) { + if (sequenceStartTimestamp > touches[index].startTimestamp) { + sequenceStartTimestamp = touches[index].startTimestamp; + sequenceStartIndex = index; + } + } + } + + if (sequenceStartIndex == -1) { + return; + } + + Uint32 sequenceEndTimestamp = -1; + if (touches[sequenceStartIndex].phase == TOUCH_PHASE_ENDED) { + sequenceEndTimestamp = touches[sequenceStartIndex].currentTimestamp; + + // Find end timestamp of sequence. + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used + && touches[index].startTimestamp >= sequenceStartTimestamp + && touches[index].startTimestamp <= sequenceEndTimestamp) { + if (touches[index].phase == TOUCH_PHASE_ENDED) { + if (sequenceEndTimestamp < touches[index].currentTimestamp) { + sequenceEndTimestamp = touches[index].currentTimestamp; + + // Start over since we can have fingers missed. + index = -1; + } + } else { + // Sequence is current. + sequenceEndTimestamp = -1; + break; + } + } + } + } + + int active[MAX_TOUCHES]; + int activeCount = 0; + + int ended[MAX_TOUCHES]; + int endedCount = 0; + + // Split participating fingers into two buckets - active fingers (currently + // on screen) and ended (lifted up). + for (int index = 0; index < MAX_TOUCHES; index++) { + if (touches[index].used + && touches[index].currentTimestamp >= sequenceStartTimestamp + && touches[index].currentTimestamp <= sequenceEndTimestamp) { + if (touches[index].phase == TOUCH_PHASE_ENDED) { + ended[endedCount++] = index; + } else { + active[activeCount++] = index; + } + + // If this sequence is over, unmark participating finger as used. + if (sequenceEndTimestamp != -1) { + touches[index].used = false; + } + } + } + + if (currentGesture.type == kPan || currentGesture.type == kLongPress) { + if (currentGesture.state != kEnded) { + // For continuous gestures we want number of fingers to remain the + // same as it was when gesture was recognized. + if (activeCount == currentGesture.numberOfTouches && endedCount == 0) { + TouchLocation centroid = touch_get_current_location_centroid(active, activeCount); + currentGesture.state = kChanged; + currentGesture.x = centroid.x; + currentGesture.y = centroid.y; + gestureEventsQueue.push(currentGesture); + } else { + currentGesture.state = kEnded; + gestureEventsQueue.push(currentGesture); + } + } + + // Reset continuous gesture if when current sequence is over. + if (currentGesture.state == kEnded && sequenceEndTimestamp != -1) { + currentGesture.type = kUnrecognized; + } + } else { + if (activeCount == 0 && endedCount != 0) { + // For taps we need all participating fingers to be both started + // and ended simultaneously (within predefined threshold). + Uint32 startEarliestTimestamp = -1; + Uint32 startLatestTimestamp = 0; + Uint32 endEarliestTimestamp = -1; + Uint32 endLatestTimestamp = 0; + + for (int index = 0; index < endedCount; index++) { + startEarliestTimestamp = std::min(startEarliestTimestamp, touches[ended[index]].startTimestamp); + startLatestTimestamp = std::max(startLatestTimestamp, touches[ended[index]].startTimestamp); + endEarliestTimestamp = std::min(endEarliestTimestamp, touches[ended[index]].currentTimestamp); + endLatestTimestamp = std::max(endLatestTimestamp, touches[ended[index]].currentTimestamp); + } + + if (startLatestTimestamp - startEarliestTimestamp <= TAP_MAXIMUM_DURATION + && endLatestTimestamp - endEarliestTimestamp <= TAP_MAXIMUM_DURATION) { + TouchLocation currentCentroid = touch_get_current_location_centroid(ended, endedCount); + + currentGesture.type = kTap; + currentGesture.state = kEnded; + currentGesture.numberOfTouches = endedCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + + // Reset tap gesture immediately. + currentGesture.type = kUnrecognized; + } + } else if (activeCount != 0 && endedCount == 0) { + TouchLocation startCentroid = touch_get_start_location_centroid(active, activeCount); + TouchLocation currentCentroid = touch_get_current_location_centroid(active, activeCount); + + // Disambiguate between pan and long press. + if (abs(currentCentroid.x - startCentroid.x) >= PAN_MINIMUM_MOVEMENT + || abs(currentCentroid.y - startCentroid.y) >= PAN_MINIMUM_MOVEMENT) { + currentGesture.type = kPan; + currentGesture.state = kBegan; + currentGesture.numberOfTouches = activeCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + } else if (SDL_GetTicks() - touches[active[0]].startTimestamp >= LONG_PRESS_MINIMUM_DURATION) { + currentGesture.type = kLongPress; + currentGesture.state = kBegan; + currentGesture.numberOfTouches = activeCount; + currentGesture.x = currentCentroid.x; + currentGesture.y = currentCentroid.y; + gestureEventsQueue.push(currentGesture); + } + } + } +} + +bool touch_get_gesture(Gesture* gesture) +{ + if (gestureEventsQueue.empty()) { + return false; + } + + *gesture = gestureEventsQueue.top(); + gestureEventsQueue.pop(); + + return true; +} + +} // namespace fallout diff --git a/src/touch.h b/src/touch.h new file mode 100644 index 0000000..2a76702 --- /dev/null +++ b/src/touch.h @@ -0,0 +1,38 @@ +#ifndef FALLOUT_TOUCH_H_ +#define FALLOUT_TOUCH_H_ + +#include + +namespace fallout { + +enum GestureType { + kUnrecognized, + kTap, + kLongPress, + kPan, +}; + +enum GestureState { + kPossible, + kBegan, + kChanged, + kEnded, +}; + +struct Gesture { + GestureType type; + GestureState state; + int numberOfTouches; + int x; + int y; +}; + +void touch_handle_start(SDL_TouchFingerEvent* event); +void touch_handle_move(SDL_TouchFingerEvent* event); +void touch_handle_end(SDL_TouchFingerEvent* event); +void touch_process_gesture(); +bool touch_get_gesture(Gesture* gesture); + +} // namespace fallout + +#endif /* FALLOUT_TOUCH_H_ */