From ffdac314e934ed35f19d531109a3433addf4ef9c Mon Sep 17 00:00:00 2001
From: Ivan Savenko <saven.ivan@gmail.com>
Date: Fri, 16 Jun 2023 12:59:20 +0300
Subject: [PATCH] Added selection of long touch duration

---
 Mods/vcmi/config/vcmi/english.json            |  5 ++
 client/eventsSDL/InputSourceTouch.cpp         |  6 +-
 client/eventsSDL/InputSourceTouch.h           |  2 +-
 client/windows/settings/BattleOptionsTab.cpp  |  3 -
 client/windows/settings/GeneralOptionsTab.cpp | 90 ++++++++++++++++---
 client/windows/settings/GeneralOptionsTab.h   |  4 +
 config/schemas/settings.json                  | 19 ++--
 .../widgets/settings/generalOptionsTab.json   | 12 +++
 8 files changed, 118 insertions(+), 23 deletions(-)

diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json
index fc71c6212..afb31e8ae 100644
--- a/Mods/vcmi/config/vcmi/english.json
+++ b/Mods/vcmi/config/vcmi/english.json
@@ -67,6 +67,11 @@
 	"vcmi.systemOptions.scalingButton.help"    : "{Interface Scaling}\n\nChanges scaling of in-game interface",
 	"vcmi.systemOptions.scalingMenu.hover"     : "Select Interface Scaling",
 	"vcmi.systemOptions.scalingMenu.help"      : "Change in-game interface scaling.",
+	"vcmi.systemOptions.longTouchButton.hover"   : "Long Touch Interval: %d ms", // Translation note: "ms" = "milliseconds"
+	"vcmi.systemOptions.longTouchButton.help"    : "{Long Touch Interval}\n\nWhen using touchscreen, popup windows will appear after touching screen for specified duration, in milliseconds",
+	"vcmi.systemOptions.longTouchMenu.hover"     : "Select Long Touch Interval",
+	"vcmi.systemOptions.longTouchMenu.help"      : "Change duration of long touch interval.",
+	"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",
 
diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp
index b9349824f..589effd22 100644
--- a/client/eventsSDL/InputSourceTouch.cpp
+++ b/client/eventsSDL/InputSourceTouch.cpp
@@ -31,6 +31,7 @@ InputSourceTouch::InputSourceTouch()
 {
 	params.useRelativeMode = settings["general"]["userRelativePointer"].Bool();
 	params.relativeModeSpeedFactor = settings["general"]["relativePointerSpeedMultiplier"].Float();
+	params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
 
 	if (params.useRelativeMode)
 		state = TouchState::RELATIVE_MODE;
@@ -93,6 +94,9 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
 
 void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
 {
+	// FIXME: better place to update potentially changed settings?
+	params.longTouchTimeMilliseconds = settings["general"]["longTouchTimeMilliseconds"].Float();
+
 	lastTapTimeTicks = tfinger.timestamp;
 
 	switch(state)
@@ -204,7 +208,7 @@ void InputSourceTouch::handleUpdate()
 	if ( state == TouchState::TAP_DOWN_SHORT)
 	{
 		uint32_t currentTime = SDL_GetTicks();
-		if (currentTime > lastTapTimeTicks + params.longPressTimeMilliseconds)
+		if (currentTime > lastTapTimeTicks + params.longTouchTimeMilliseconds)
 		{
 			GH.events().dispatchShowPopup(GH.getCursorPosition());
 
diff --git a/client/eventsSDL/InputSourceTouch.h b/client/eventsSDL/InputSourceTouch.h
index 1bfb982dc..832435a05 100644
--- a/client/eventsSDL/InputSourceTouch.h
+++ b/client/eventsSDL/InputSourceTouch.h
@@ -76,7 +76,7 @@ struct TouchInputParameters
 	double relativeModeSpeedFactor = 1.0;
 
 	/// tap for period longer than specified here will be qualified as "long tap", triggering corresponding gesture
-	uint32_t longPressTimeMilliseconds = 750;
+	uint32_t longTouchTimeMilliseconds = 750;
 
 	/// moving finger for distance larger than specified will be qualified as panning gesture instead of long press
 	uint32_t panningSensitivityThreshold = 10;
diff --git a/client/windows/settings/BattleOptionsTab.cpp b/client/windows/settings/BattleOptionsTab.cpp
index 8e6490905..3547e2c31 100644
--- a/client/windows/settings/BattleOptionsTab.cpp
+++ b/client/windows/settings/BattleOptionsTab.cpp
@@ -12,7 +12,6 @@
 
 #include "../../battle/BattleInterface.h"
 #include "../../gui/CGuiHandler.h"
-#include "../../eventsSDL/InputHandler.h"
 #include "../../../lib/CConfigHandler.h"
 #include "../../../lib/filesystem/ResourceID.h"
 #include "../../../lib/CGeneralTextHandler.h"
@@ -24,8 +23,6 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	type |= REDRAW_PARENT;
 
-	//addConditional("touchscreen", GH.input().hasTouchInputDevice());
-
 	const JsonNode config(ResourceID("config/widgets/settings/battleOptionsTab.json"));
 	addCallback("viewGridChanged", [this, owner](bool value)
 	{
diff --git a/client/windows/settings/GeneralOptionsTab.cpp b/client/windows/settings/GeneralOptionsTab.cpp
index cc8db39ad..3d14d4380 100644
--- a/client/windows/settings/GeneralOptionsTab.cpp
+++ b/client/windows/settings/GeneralOptionsTab.cpp
@@ -8,24 +8,25 @@
  *
  */
 #include "StdInc.h"
-
 #include "GeneralOptionsTab.h"
 
-#include "../../../lib/CGeneralTextHandler.h"
-#include "../../../lib/filesystem/ResourceID.h"
-#include "../../gui/CGuiHandler.h"
-#include "../../gui/WindowHandler.h"
-#include "../../widgets/Buttons.h"
-#include "../../widgets/Slider.h"
-#include "../../widgets/TextControls.h"
-#include "../../widgets/Images.h"
 #include "CGameInfo.h"
 #include "CMusicHandler.h"
 #include "CPlayerInterface.h"
-#include "windows/GUIClasses.h"
 #include "CServerHandler.h"
 #include "render/IScreenHandler.h"
+#include "windows/GUIClasses.h"
 
+#include "../../eventsSDL/InputHandler.h"
+#include "../../gui/CGuiHandler.h"
+#include "../../gui/WindowHandler.h"
+#include "../../widgets/Buttons.h"
+#include "../../widgets/Images.h"
+#include "../../widgets/Slider.h"
+#include "../../widgets/TextControls.h"
+
+#include "../../../lib/CGeneralTextHandler.h"
+#include "../../../lib/filesystem/ResourceID.h"
 
 static void setIntSetting(std::string group, std::string field, int value)
 {
@@ -52,6 +53,22 @@ static std::string scalingToLabelString( int scaling)
 	return string;
 }
 
+static std::string longTouchToEntryString( int duration)
+{
+	std::string string = CGI->generaltexth->translate("vcmi.systemOptions.longTouchMenu.entry");
+	boost::replace_all(string, "%d", std::to_string(duration));
+
+	return string;
+}
+
+static std::string longTouchToLabelString( int duration)
+{
+	std::string string = CGI->generaltexth->translate("vcmi.systemOptions.longTouchButton.hover");
+	boost::replace_all(string, "%d", std::to_string(duration));
+
+	return string;
+}
+
 static std::string resolutionToEntryString( int w, int h)
 {
 	std::string string = "%wx%h";
@@ -79,6 +96,7 @@ GeneralOptionsTab::GeneralOptionsTab()
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	type |= REDRAW_PARENT;
 
+	addConditional("touchscreen", GH.input().hasTouchInputDevice());
 #ifdef VCMI_MOBILE
 	addConditional("mobile", true);
 	addConditional("desktop", false);
@@ -127,6 +145,10 @@ GeneralOptionsTab::GeneralOptionsTab()
 	{
 		selectGameScaling();
 	});
+	addCallback("setLongTouchDuration", [this](int dummyValue)
+	{
+		selectLongTouchDuration();
+	});
 	addCallback("framerateChanged", [](bool value)
 	{
 		setBoolSetting("video", "showfps", value);
@@ -150,6 +172,9 @@ GeneralOptionsTab::GeneralOptionsTab()
 	std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
 	scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer()));
 
+	std::shared_ptr<CLabel> longTouchLabel = widget<CLabel>("longTouchLabel");
+	longTouchLabel->setText(longTouchToLabelString(settings["general"]["longTouchTimeMilliseconds"].Integer()));
+
 	std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
 	spellbookAnimationCheckbox->setSelected(settings["video"]["spellbookAnimation"].Bool());
 
@@ -324,3 +349,48 @@ void GeneralOptionsTab::setGameScaling(int index)
 
 	widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
 }
+
+void GeneralOptionsTab::selectLongTouchDuration()
+{
+	longTouchDurations = { 500, 750, 1000, 1250, 1500, 1750, 2000 };
+
+	std::vector<std::string> items;
+	size_t currentIndex = 0;
+	size_t i = 0;
+	for(const auto & it : longTouchDurations)
+	{
+		auto resolutionStr = longTouchToEntryString(it);
+		if(widget<CLabel>("longTouchLabel")->getText() == longTouchToLabelString(it))
+			currentIndex = i;
+
+		items.push_back(std::move(resolutionStr));
+		++i;
+	}
+
+	GH.windows().createAndPushWindow<CObjectListWindow>(
+		items,
+		nullptr,
+		CGI->generaltexth->translate("vcmi.systemOptions.longTouchMenu.hover"),
+		CGI->generaltexth->translate("vcmi.systemOptions.longTouchMenu.help"),
+		[this](int index)
+		{
+			setLongTouchDuration(index);
+		},
+		currentIndex
+	);
+}
+
+void GeneralOptionsTab::setLongTouchDuration(int index)
+{
+	assert(index >= 0 && index < longTouchDurations.size());
+
+	if ( index < 0 || index >= longTouchDurations.size() )
+		return;
+
+	int scaling = longTouchDurations[index];
+
+	Settings longTouchTime = settings.write["general"]["longTouchTimeMilliseconds"];
+	longTouchTime->Float() = scaling;
+
+	widget<CLabel>("longTouchLabel")->setText(longTouchToLabelString(scaling));
+}
diff --git a/client/windows/settings/GeneralOptionsTab.h b/client/windows/settings/GeneralOptionsTab.h
index 2f5603248..360389aee 100644
--- a/client/windows/settings/GeneralOptionsTab.h
+++ b/client/windows/settings/GeneralOptionsTab.h
@@ -20,6 +20,7 @@ private:
 
 	std::vector<Point> supportedResolutions;
 	std::vector<int> supportedScaling;
+	std::vector<int> longTouchDurations;
 
 	void setFullscreenMode( bool on, bool exclusive);
 
@@ -29,6 +30,9 @@ private:
 	void selectGameScaling();
 	void setGameScaling(int index);
 
+	void selectLongTouchDuration();
+	void setLongTouchDuration(int index);
+
 public:
 	GeneralOptionsTab();
 
diff --git a/config/schemas/settings.json b/config/schemas/settings.json
index 3a0fa90f8..8b208b008 100644
--- a/config/schemas/settings.json
+++ b/config/schemas/settings.json
@@ -21,15 +21,19 @@
 				"playerName",
 				"music",
 				"sound",
+				"saveRandomMaps",
+				"lastMap",
 				"language",
 				"gameDataLanguage",
-				"saveRandomMaps",
+				"lastSave",
+				"lastSettingsTab"
+				"lastCampaign",
 				"saveFrequency",
 				"notifications",
 				"extraDump",
 				"userRelativePointer",
 				"relativePointerSpeedMultiplier",
-				"lastSettingsTab"
+				"longTouchTimeMilliseconds"
 			],
 			"properties" : {
 				"playerName" : {
@@ -93,6 +97,10 @@
 				"relativePointerSpeedMultiplier" : {
 					"type" : "number",
 					"default" : 1
+				},
+				"longTouchTimeMilliseconds" : {
+					"type" : "number",
+					"default" : 1000
 				}
 			}
 		},
@@ -102,13 +110,12 @@
 			"default" : {},
 			"required" : [ 
 				"resolution", 
-				"bitsPerPixel", 
 				"fullscreen", 
 				"realFullscreen", 
 				"cursor", 
+				"showIntro", 
 				"spellbookAnimation", 
 				"driver", 
-				"showIntro", 
 				"displayIndex",
 				"showfps",
 				"targetfps"
@@ -125,10 +132,6 @@
 					},
 					"default" : {"width" : 800, "height" : 600, "scaling" : 100 }
 				},
-				"bitsPerPixel" : {
-					"type" : "number",
-					"default" : 32
-				},
 				"fullscreen" : {
 					"type" : "boolean",
 					"default" : false
diff --git a/config/widgets/settings/generalOptionsTab.json b/config/widgets/settings/generalOptionsTab.json
index c9d687872..78f042adf 100644
--- a/config/widgets/settings/generalOptionsTab.json
+++ b/config/widgets/settings/generalOptionsTab.json
@@ -52,6 +52,11 @@
 				},
 				{
 					"text": "core.genrltxt.577"
+				},
+				{
+					"name": "longTouchLabel",
+					"text": "vcmi.systemOptions.longTouchButton.hover",
+					"created" : "touchscreen"
 				}
 			]
 		},
@@ -95,6 +100,13 @@
 					"help": "core.help.364",
 					"callback": "spellbookAnimationChanged"
 				},
+				{
+					"name": "longTouchButton",
+					"type": "buttonGear",
+					"help": "vcmi.systemOptions.longTouchButton",
+					"callback": "setLongTouchDuration",
+					"created" : "touchscreen"
+				}
 			]
 		},
 /////////////////////////////////////// Right section - Audio Settings