diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index c07d25215..d2d264574 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -74,6 +74,8 @@ "vcmi.systemOptions.longTouchMenu.entry" : "%d milliseconds", "vcmi.systemOptions.framerateButton.hover" : "Show FPS", "vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptic feedback", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptic feedback}\n\nToggle the haptic feedback on touch inputs", "vcmi.adventureOptions.infoBarPick.hover" : "Show Messages in Info Panel", "vcmi.adventureOptions.infoBarPick.help" : "{Show Messages in Info Panel}\n\nWhenever possible, game messages from visiting map objects will be shown in the info panel, instead of popping up in a separate window.", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index b585d28de..aab2710e3 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -74,6 +74,8 @@ "vcmi.systemOptions.longTouchMenu.entry" : "%d Millisekunden", "vcmi.systemOptions.framerateButton.hover" : "FPS anzeigen", "vcmi.systemOptions.framerateButton.help" : "{FPS anzeigen}\n\n Schaltet die Sichtbarkeit des Zählers für die Bilder pro Sekunde in der Ecke des Spielfensters um.", + "vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptisches Feedback", + "vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptisches Feedback}\n\nHaptisches Feedback bei Touch-Eingaben.", "vcmi.adventureOptions.infoBarPick.hover" : "Meldungen im Infobereich anzeigen", "vcmi.adventureOptions.infoBarPick.help" : "{Meldungen im Infobereich anzeigen}\n\nWann immer möglich, werden Spielnachrichten von besuchten Kartenobjekten in der Infoleiste angezeigt, anstatt als Popup-Fenster zu erscheinen", diff --git a/android/vcmi-app/src/main/AndroidManifest.xml b/android/vcmi-app/src/main/AndroidManifest.xml index a0bf14dcf..459d0efbd 100644 --- a/android/vcmi-app/src/main/AndroidManifest.xml +++ b/android/vcmi-app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="eu.vcmi.vcmi"> + - \ No newline at end of file + diff --git a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java index 90c3786a9..8470e2d16 100644 --- a/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java +++ b/android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java @@ -3,11 +3,14 @@ package eu.vcmi.vcmi; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Environment; import android.os.Looper; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.VibrationEffect; +import android.os.Vibrator; import org.libsdl.app.SDL; import org.libsdl.app.SDLActivity; @@ -138,6 +141,17 @@ public class NativeMethods { internalProgressDisplay(false); } + + @SuppressWarnings(Const.JNI_METHOD_SUPPRESS) + public static void hapticFeedback() + { + final Context ctx = SDL.getContext(); + if (Build.VERSION.SDK_INT >= 26) { + ((Vibrator) ctx.getSystemService(ctx.VIBRATOR_SERVICE)).vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)); + } else { + ((Vibrator) ctx.getSystemService(ctx.VIBRATOR_SERVICE)).vibrate(30); + } + } private static void internalProgressDisplay(final boolean show) { diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp index 40d816e20..8e627ac27 100644 --- a/client/eventsSDL/InputSourceTouch.cpp +++ b/client/eventsSDL/InputSourceTouch.cpp @@ -22,6 +22,12 @@ #include "../gui/MouseButton.h" #include "../gui/WindowHandler.h" +#if defined(VCMI_ANDROID) +#include "../../lib/CAndroidVMHelper.h" +#elif defined(VCMI_IOS) +#include "../ios/utils.h" +#endif + #include #include #include @@ -32,6 +38,7 @@ InputSourceTouch::InputSourceTouch() params.useRelativeMode = settings["general"]["userRelativePointer"].Bool(); params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float(); params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float(); + params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool(); if (params.useRelativeMode) state = TouchState::RELATIVE_MODE; @@ -100,6 +107,7 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge { // FIXME: better place to update potentially changed settings? params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float(); + params.hapticFeedbackEnabled = settings["general"]["hapticFeedback"].Bool(); lastTapTimeTicks = tfinger.timestamp; @@ -215,6 +223,7 @@ void InputSourceTouch::handleUpdate() if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds) { GH.events().dispatchShowPopup(GH.getCursorPosition()); + hapticFeedback(); if (GH.windows().isTopWindowPopup()) state = TouchState::TAP_DOWN_LONG; @@ -287,3 +296,14 @@ void InputSourceTouch::emitPinchEvent(const SDL_TouchFingerEvent & tfinger) if (distanceOld > params.pinchSensitivityThreshold) GH.events().dispatchGesturePinch(lastTapPosition, distanceNew / distanceOld); } + +void InputSourceTouch::hapticFeedback() { + if(params.hapticFeedbackEnabled) { +#if defined(VCMI_ANDROID) + CAndroidVMHelper vmHelper; + vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "hapticFeedback"); +#elif defined(VCMI_IOS) + iOS_utils::hapticFeedback(); +#endif + } +} diff --git a/client/eventsSDL/InputSourceTouch.h b/client/eventsSDL/InputSourceTouch.h index 233f25fc1..fb7277046 100644 --- a/client/eventsSDL/InputSourceTouch.h +++ b/client/eventsSDL/InputSourceTouch.h @@ -79,6 +79,8 @@ struct TouchInputParameters uint32_t pinchSensitivityThreshold = 10; bool useRelativeMode = false; + + bool hapticFeedbackEnabled = false; }; /// Class that handles touchscreen input from SDL events @@ -94,6 +96,8 @@ class InputSourceTouch void emitPanningEvent(const SDL_TouchFingerEvent & tfinger); void emitPinchEvent(const SDL_TouchFingerEvent & tfinger); + + void hapticFeedback(); public: InputSourceTouch(); diff --git a/client/ios/utils.h b/client/ios/utils.h index f3a643d83..c921ae23b 100644 --- a/client/ios/utils.h +++ b/client/ios/utils.h @@ -15,4 +15,6 @@ double screenScale(); void showLoadingIndicator(); void hideLoadingIndicator(); + +void hapticFeedback(); } diff --git a/client/ios/utils.mm b/client/ios/utils.mm index 41cb65f4f..42cca084e 100644 --- a/client/ios/utils.mm +++ b/client/ios/utils.mm @@ -43,4 +43,10 @@ void hideLoadingIndicator() [indicator removeFromSuperview]; indicator = nil; } + +void hapticFeedback() +{ + auto hapticGen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [hapticGen impactOccurred]; +} } diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp index df35ea811..eb2ffae0e 100644 --- a/client/windows/settings/GeneralOptionsTab.cpp +++ b/client/windows/settings/GeneralOptionsTab.cpp @@ -153,6 +153,10 @@ GeneralOptionsTab::GeneralOptionsTab() { setBoolSetting("video", "showfps", value); }); + addCallback("hapticFeedbackChanged", [](bool value) + { + setBoolSetting("general", "hapticFeedback", value); + }); //moved from "other" tab that is disabled for now to avoid excessible tabs with barely any content addCallback("availableCreaturesAsDwellingChanged", [=](int value) @@ -190,6 +194,10 @@ GeneralOptionsTab::GeneralOptionsTab() std::shared_ptr framerateCheckbox = widget("framerateCheckbox"); framerateCheckbox->setSelected(settings["video"]["showfps"].Bool()); + std::shared_ptr hapticFeedbackCheckbox = widget("hapticFeedbackCheckbox"); + if (hapticFeedbackCheckbox) + hapticFeedbackCheckbox->setSelected(settings["general"]["hapticFeedback"].Bool()); + std::shared_ptr musicSlider = widget("musicSlider"); musicSlider->scrollTo(CCS->musich->getVolume()); diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 9311387a5..bf6005f87 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -33,7 +33,8 @@ "extraDump", "userRelativePointer", "relativePointerSpeedMultiplier", - "longTouchTimeMilliseconds" + "longTouchTimeMilliseconds", + "hapticFeedback" ], "properties" : { "playerName" : { @@ -101,6 +102,10 @@ "longTouchTimeMilliseconds" : { "type" : "number", "default" : 1000 + }, + "hapticFeedback" : { + "type" : "boolean", + "default" : false } } }, diff --git a/config/widgets/settings/generalOptionsTab.json b/config/widgets/settings/generalOptionsTab.json index 78f042adf..f8a5a4602 100644 --- a/config/widgets/settings/generalOptionsTab.json +++ b/config/widgets/settings/generalOptionsTab.json @@ -57,6 +57,10 @@ "name": "longTouchLabel", "text": "vcmi.systemOptions.longTouchButton.hover", "created" : "touchscreen" + }, + { + "text": "vcmi.systemOptions.hapticFeedbackButton.hover", + "created" : "mobile" } ] }, @@ -76,7 +80,7 @@ "name": "scalingButton", "type": "buttonGear", "help": "vcmi.systemOptions.scalingButton", - "callback": "setGameScaling", + "callback": "setGameScaling" }, { "name": "fullscreenBorderlessCheckbox", @@ -106,6 +110,12 @@ "help": "vcmi.systemOptions.longTouchButton", "callback": "setLongTouchDuration", "created" : "touchscreen" + }, + { + "name": "hapticFeedbackCheckbox", + "help": "vcmi.systemOptions.hapticFeedbackButton", + "callback": "hapticFeedbackChanged", + "created" : "mobile" } ] },