diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard
index e918a80c..5f84ad8d 100644
--- a/VoidLink/Base.lproj/iPad.storyboard
+++ b/VoidLink/Base.lproj/iPad.storyboard
@@ -2626,7 +2626,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -3513,6 +3597,10 @@
+
+
+
+
@@ -3523,6 +3611,10 @@
+
+
+
+
diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard
index 087c9c37..bc724629 100644
--- a/VoidLink/Base.lproj/iPhone.storyboard
+++ b/VoidLink/Base.lproj/iPhone.storyboard
@@ -315,7 +315,7 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1239,6 +1329,10 @@
+
+
+
+
@@ -1251,6 +1345,10 @@
+
+
+
+
@@ -3471,16 +3569,19 @@
-
+
-
+
-
+
+
+
+
-
+
diff --git a/VoidLink/Input/ControllerUtil.swift b/VoidLink/Input/ControllerUtil.swift
index 662a3adb..7265683c 100644
--- a/VoidLink/Input/ControllerUtil.swift
+++ b/VoidLink/Input/ControllerUtil.swift
@@ -78,31 +78,6 @@ import Foundation
// 单一 gamepad.valueChangedHandler
gamepad.valueChangedHandler = { gamepad, element in
- /*
- navigationActionTriggeredPrivate = (gamepad.dpad.up.isPressed
- || gamepad.dpad.down.isPressed
- || gamepad.dpad.left.isPressed
- || gamepad.dpad.right.isPressed
- || gamepad.buttonA.isPressed
- || gamepad.buttonB.isPressed
- || gamepad.buttonX.isPressed
- || gamepad.leftShoulder.isPressed
- || gamepad.rightShoulder.isPressed
- || gamepad.leftTrigger.value != 0
- || gamepad.rightTrigger.value != 0
- || gamepad.rightThumbstick.xAxis.value != 0
- || gamepad.rightThumbstick.yAxis.value != 0
- || gamepad.leftThumbstick.xAxis.value != 0
- || gamepad.leftThumbstick.yAxis.value != 0)
- if navigationActionTriggeredPrivate { navigationActionTriggered = true}
- else {
- DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
- if !navigationActionTriggeredPrivate {
- navigationActionTriggered = false
- }
- }
- }
- */
handler(buttonDict, gamepad, element)
if #available(iOS 13.0, *) {
if controller.playerIndex == .index1 {
diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.m b/VoidLink/Input/CustomOSC/OSCProfilesManager.m
index d909e9f1..e7802f94 100644
--- a/VoidLink/Input/CustomOSC/OSCProfilesManager.m
+++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.m
@@ -478,7 +478,11 @@ static CGRect layoutViewBounds;
buttonState.walkModeThreshold = widgetView.dWheelWalkModeThreshold;
buttonState.minStickOffset = widgetView.minStickOffset;
buttonState.buttonMode = widgetView.buttonMode;
-
+ buttonState.sprintKeyActionType = widgetView.sprintKeyActionType;
+ buttonState.sprintKeyThreshold = widgetView.sprintKeyThreshold;
+ buttonState.walkKeyActionType = widgetView.walkKeyActionType;
+ buttonState.walkKeyThreshold = widgetView.walkKeyThreshold;
+
NSData *buttonStateEncoded = [NSKeyedArchiver archivedDataWithRootObject:buttonState requiringSecureCoding:YES error:nil];
[buttonStatesEncoded addObject: buttonStateEncoded];
}
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.h b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
index b3127521..073545a9 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.h
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.h
@@ -65,6 +65,12 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) uint8_t mouseButtonAction;
@property (nonatomic, assign) uint16_t autoTapInterval;
+@property (nonatomic, assign) uint8_t walkKeyActionType;
+@property (nonatomic, assign) CGFloat walkKeyThreshold;
+@property (nonatomic, assign) uint8_t sprintKeyActionType;
+@property (nonatomic, assign) CGFloat sprintKeyThreshold;
+
+
// @property (nonatomic, assign) BOOL hasValidPosition;
typedef NS_ENUM(NSInteger, OnScreenWidgetType) {
diff --git a/VoidLink/Input/CustomOSC/OnScreenButtonState.m b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
index 899933a2..4a5652f9 100644
--- a/VoidLink/Input/CustomOSC/OnScreenButtonState.m
+++ b/VoidLink/Input/CustomOSC/OnScreenButtonState.m
@@ -72,6 +72,10 @@
[encoder encodeObject:self.widgetShape forKey:@"widgetShape"];
[encoder encodeFloat:self.walkModeThreshold forKey:@"walkModeThreshold"];
[encoder encodeFloat:self.minStickOffset forKey:@"minStickOffset"];
+ [encoder encodeInt:self.sprintKeyActionType forKey:@"sprintKeyActionType"];
+ [encoder encodeFloat:self.sprintKeyThreshold forKey:@"sprintKeyThreshold"];
+ [encoder encodeInt:self.walkKeyActionType forKey:@"walkKeyActionType"];
+ [encoder encodeFloat:self.walkKeyThreshold forKey:@"walkKeyThreshold"];
}
- (id) initWithCoder:(NSCoder*)decoder {
@@ -117,6 +121,12 @@
self.widgetShape = [decoder decodeObjectForKey:@"widgetShape"];
self.walkModeThreshold = [decoder containsValueForKey:@"walkModeThreshold"] ? [decoder decodeFloatForKey:@"walkModeThreshold"] : 16383;
self.minStickOffset = [decoder decodeFloatForKey:@"minStickOffset"];
+
+ self.sprintKeyActionType = [decoder containsValueForKey:@"sprintKeyActionType"] ? [decoder decodeIntForKey:@"sprintKeyActionType"] : WalkSprintKeyActionTypeHold;
+ self.sprintKeyThreshold = [decoder containsValueForKey:@"sprintKeyThreshold"] ? [decoder decodeFloatForKey:@"sprintKeyThreshold"] : 0.6;
+ self.walkKeyActionType = [decoder containsValueForKey:@"walkKeyActionType"] ? [decoder decodeIntForKey:@"walkKeyActionType"] : WalkSprintKeyActionTypeHold;
+ self.walkKeyThreshold = [decoder containsValueForKey:@"walkKeyThreshold"] ? [decoder decodeFloatForKey:@"walkKeyThreshold"] : 0.08;
+
}
return self;
}
diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift
index 408367a9..6c799182 100644
--- a/VoidLink/Input/OnScreenWidgetView.swift
+++ b/VoidLink/Input/OnScreenWidgetView.swift
@@ -174,6 +174,7 @@ import ObjectiveC.runtime
@objc public var isTapToToggleException: Bool = false
@objc public var hasHapticFeedback: Bool = false
@objc public var isDirectionPad: Bool = false
+ @objc public var hasWalkSprintKeys: Bool = false
@objc public var hasL3R3Indicator: Bool = false
@objc public var isStickWheel: Bool = false
@@ -232,10 +233,24 @@ import ObjectiveC.runtime
@objc public var stickIndicatorOffset: CGFloat = 120
// for all LRUD pads
+
+ @objc public enum WalkSprintKeyActionType: UInt8 {
+ case hold
+ case toggle
+ }
+
+ @objc public var keyboardDpadThresholdRef: CGFloat = 300
+ @objc public var walkKeyThreshold: CGFloat = 0.08
+ @objc public var walkKeyActionType: WalkSprintKeyActionType = .hold
+ @objc public var sprintKeyThreshold: CGFloat = 0.6
+ @objc public var sprintKeyActionType: WalkSprintKeyActionType = .hold
+
@objc public var upIndicator = CAShapeLayer()
@objc public var downIndicator = CAShapeLayer()
@objc public var leftIndicator = CAShapeLayer()
@objc public var rightIndicator = CAShapeLayer()
+ @objc public var walkKeyThresholdPreviewLayer = CAShapeLayer()
+ @objc public var sprintKeyThresholdPreviewLayer = CAShapeLayer()
// for DPAD LRUD pad
@objc public var lrudIndicatorBall = CAShapeLayer()
@@ -497,6 +512,8 @@ import ObjectiveC.runtime
)
self.hasHapticFeedback = !self.comboButtonStrings.isEmpty || CommandManager.directionPads.contains(self.touchPadString)
self.isDirectionPad = self.widgetType == WidgetTypeEnum.touchPad && CommandManager.directionPads.contains(self.touchPadString)
+ self.hasWalkSprintKeys = self.isDirectionPad && (self.touchPadString == "WASDPAD"
+ || self.touchPadString == "ARROWPAD")
self.isStickWheel = self.widgetType == WidgetTypeEnum.touchPad && CommandManager.stickWheels.contains(self.touchPadString)
self.isFolder = self.cmdString == "FOLDER"
@@ -635,6 +652,7 @@ import ObjectiveC.runtime
// Re-setup widgetView style
setupView()
+ updateMovementThresholdPreview()
CATransaction.commit()
}
@@ -669,6 +687,8 @@ import ObjectiveC.runtime
self.downIndicator.borderColor = self.buttonDownVisualEffectLayer.borderColor
self.leftIndicator.borderColor = self.buttonDownVisualEffectLayer.borderColor
self.rightIndicator.borderColor = self.buttonDownVisualEffectLayer.borderColor
+ self.walkKeyThresholdPreviewLayer.strokeColor = self.buttonDownVisualEffectLayer.borderColor
+ self.sprintKeyThresholdPreviewLayer.strokeColor = self.buttonDownVisualEffectLayer.borderColor
}
private func tweakAlpha(tweakBorderAlpha:Bool, tweakLabelAlpha:Bool = true){
@@ -1352,10 +1372,64 @@ import ObjectiveC.runtime
}
}
+ private var previousWalkMode:Bool = false
+ private var previousSprintMode:Bool = false
private func handleLrudTouchMove(){
CATransaction.begin()
CATransaction.setDisableActions(true)
+
+
+ if self.hasWalkSprintKeys {
+ let dist = hypot(self.offSetX, self.offSetY)
+ let isInWalkMode = dist < self.walkKeyThreshold * keyboardDpadThresholdRef * 0.5
+ let isInSprintMode = dist > self.sprintKeyThreshold * keyboardDpadThresholdRef * 0.5
+
+ if (isInSprintMode != previousSprintMode || directionPadTouchBegan)
+ , self.comboButtonStrings.count>0 {
+ switch sprintKeyActionType {
+ case .hold:
+ if isInSprintMode {sendComboButtonsDownEvent(comboStrings: [comboButtonStrings[0]])}
+ else {sendComboButtonsUpEvent(comboStrings: [comboButtonStrings[0]])}
+ case .toggle :
+ if directionPadTouchBegan {break}
+ sendComboButtonsDownEvent(comboStrings: [comboButtonStrings[0]])
+ DispatchQueue.global().asyncAfter(deadline: .now() + 0.025) {
+ self.sendComboButtonsUpEvent(comboStrings: [self.comboButtonStrings[0]])
+ }
+ }
+
+ if !directionPadTouchBegan, isInSprintMode, vibrationOn {
+ vibrationGenerator.prepare()
+ vibrationGenerator.impactOccurred()
+ }
+ }
+
+ if (isInWalkMode != previousWalkMode || directionPadTouchBegan)
+ , self.comboButtonStrings.count>1 {
+ switch walkKeyActionType {
+ case .hold:
+ if isInWalkMode {sendComboButtonsDownEvent(comboStrings: [comboButtonStrings[1]])}
+ else {sendComboButtonsUpEvent(comboStrings: [comboButtonStrings[1]])}
+ case .toggle :
+ if directionPadTouchBegan {break}
+ sendComboButtonsDownEvent(comboStrings: [comboButtonStrings[1]])
+ DispatchQueue.global().asyncAfter(deadline: .now() + 0.025) {
+ self.sendComboButtonsUpEvent(comboStrings: [self.comboButtonStrings[1]])
+ }
+ }
+
+ if !directionPadTouchBegan, isInWalkMode, vibrationOn {
+ vibrationGenerator.prepare()
+ vibrationGenerator.impactOccurred()
+ }
+ }
+
+ previousWalkMode = isInWalkMode
+ previousSprintMode = isInSprintMode
+ }
+
+
let radians = atan2(-offSetY,offSetX)
let degrees = radians * 180 / .pi
@@ -1649,9 +1723,55 @@ import ObjectiveC.runtime
self.layer.addSublayer(self.stickWheelAxis)
self.stickWheelAxis.isHidden = false
}
+
+ setupMovementThresholdPreviewLayersIfNeeded()
+ updateMovementThresholdPreview()
CATransaction.commit()
}
+
+ private func setupMovementThresholdPreviewLayersIfNeeded() {
+ for previewLayer in [sprintKeyThresholdPreviewLayer, walkKeyThresholdPreviewLayer] {
+ previewLayer.fillColor = UIColor.clear.cgColor
+ previewLayer.strokeColor = standardHighlightColor.cgColor
+ previewLayer.lineWidth = 3
+ previewLayer.lineDashPattern = [8, 6]
+ previewLayer.isHidden = true
+ if previewLayer.superlayer == nil {
+ layer.insertSublayer(previewLayer, below: lrudIndicatorBall)
+ }
+ }
+ }
+
+ private func updateMovementThresholdPreview(layer: CAShapeLayer, threshold: CGFloat) {
+ let clampedThreshold = max(0, min(1, threshold))
+ let diameter: CGFloat = keyboardDpadThresholdRef * clampedThreshold
+ let radius = max(diameter / 2, 1)
+ layer.position = CGPoint(x: bounds.midX, y: bounds.midY)
+ layer.path = UIBezierPath(
+ arcCenter: .zero,
+ radius: radius,
+ startAngle: 0,
+ endAngle: .pi * 2,
+ clockwise: true
+ ).cgPath
+ }
+
+ @objc public func updateMovementThresholdPreview() {
+ guard isDirectionPad, touchPadString == "WASDPAD" || touchPadString == "ARROWPAD" else {
+ walkKeyThresholdPreviewLayer.isHidden = true
+ sprintKeyThresholdPreviewLayer.isHidden = true
+ return
+ }
+
+ setupMovementThresholdPreviewLayersIfNeeded()
+ updateMovementThresholdPreview(layer: sprintKeyThresholdPreviewLayer, threshold: sprintKeyThreshold)
+ if self.comboButtonStrings.count>1 {updateMovementThresholdPreview(layer: walkKeyThresholdPreviewLayer, threshold: walkKeyThreshold)}
+
+ let shouldShow = OnScreenWidgetView.editMode
+ sprintKeyThresholdPreviewLayer.isHidden = !shouldShow
+ walkKeyThresholdPreviewLayer.isHidden = !(shouldShow && self.comboButtonStrings.count>1)
+ }
@objc public func hideAllHighlightLayersOfAllWidgets(selfIncluded:Bool) {
self.forEachWidget(){ widget in
@@ -1663,6 +1783,8 @@ import ObjectiveC.runtime
widget.downIndicator.isHidden = true
widget.leftIndicator.isHidden = true
widget.rightIndicator.isHidden = true
+ widget.walkKeyThresholdPreviewLayer.isHidden = true
+ widget.sprintKeyThresholdPreviewLayer.isHidden = true
}
}
@@ -1921,7 +2043,7 @@ import ObjectiveC.runtime
self.getVector(touch: touch)
self.handleLrudTouchMove()
}
- if quickDoubleTapDetected {
+ if quickDoubleTapDetected && self.touchPadString == "DPAD" {
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)
DispatchQueue.global(qos: .userInteractive).async {
usleep(100000)
@@ -3445,7 +3567,13 @@ import ObjectiveC.runtime
parentFolder?.sequenceSet.remove(widget.sequence)
}
widget.parentSequence = -1
- widget.sequenceSet.removeAll()
+ if widget.isFolder {
+ for sequence in widget.sequenceSet {
+ guard let subWidget = OnScreenWidgetView.mapping[sequence] else {continue}
+ subWidget.parentSequence = -1
+ }
+ widget.sequenceSet.removeAll()
+ }
}
@objc static func restoreFoldedStates(){
diff --git a/VoidLink/Input/StreamView.m b/VoidLink/Input/StreamView.m
index c75b184c..9234bd2a 100644
--- a/VoidLink/Input/StreamView.m
+++ b/VoidLink/Input/StreamView.m
@@ -675,6 +675,11 @@ static const double X1_MOUSE_SPEED_DIVISOR = 2.5;
widgetView.minStickOffset = buttonState.minStickOffset;
widgetView.dWheelWalkModeThreshold = buttonState.walkModeThreshold;
widgetView.buttonMode = buttonState.buttonMode;
+ widgetView.sprintKeyActionType = buttonState.sprintKeyActionType;
+ widgetView.sprintKeyThreshold = buttonState.sprintKeyThreshold;
+ widgetView.walkKeyActionType = buttonState.walkKeyActionType;
+ widgetView.walkKeyThreshold = buttonState.walkKeyThreshold;
+
// Add the widgetView to the view controller's view
[self->_streamFrameTopLayerView addSubview:widgetView]; // add keyboard button to the stream frame view. must add it to the target view before setting location.
buttonState.position = [self denormalizeWidgetPosition:buttonState.position];
diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings
index 18d17bd5..83548894 100644
--- a/VoidLink/Localization/Localizable.xcstrings
+++ b/VoidLink/Localization/Localizable.xcstrings
@@ -47,6 +47,29 @@
}
}
},
+ " Cmd: %@ Sprint: %@ Walk: %@ Parent: %@" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 命令: %@ 疾走: %@ 静步: %@ 收纳于: %@"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 命令: %@ 疾走: %@ 靜步: %@ 收納於: %@"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 命令: %@ 疾走: %@ 靜步: %@ 收納於: %@"
+ }
+ }
+ }
+ },
" disconnect " : {
"extractionState" : "manual",
"localizations" : {
@@ -162,7 +185,7 @@
}
}
},
- " Profile: %@ Alias: %@ Cmd: %@ Parent:%@" : {
+ " Profile: %@ Alias: %@ Cmd: %@ Parent: %@" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
@@ -300,6 +323,29 @@
}
}
},
+ " Slide threshold: %0.2f " : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 触发距离: %0.2f "
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 觸發距離: %0.2f "
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : " 觸發距離: %0.2f "
+ }
+ }
+ }
+ },
"!ML_TEST_RESULT_INCONCLUSIVE" : {
"extractionState" : "manual",
"localizations" : {
@@ -8533,6 +8579,29 @@
}
}
},
+ "Please select a sprint key" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "请选择疾走键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "請選擇疾走鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "請選擇疾走鍵"
+ }
+ }
+ }
+ },
"Pofiles imported" : {
"extractionState" : "manual",
"localizations" : {
@@ -10901,6 +10970,52 @@
}
}
},
+ "Sprint and walk keys are set" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已设定疾走键和静步键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已設定疾走鍵和靜步鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已設定疾走鍵和靜步鍵"
+ }
+ }
+ }
+ },
+ "Sprint key set. Select a walk key (optional, you can skip and create)" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已设定疾走键,请选择静步键(可跳过并创建)"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已設定疾走鍵,請選擇靜步鍵(可跳過並建立)"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "已設定疾走鍵,請選擇靜步鍵(可跳過並建立)"
+ }
+ }
+ }
+ },
"Square" : {
"extractionState" : "manual",
"localizations" : {
diff --git a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift
index 5edee7b8..baa2995d 100644
--- a/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift
+++ b/VoidLink/ViewControllers/CustomOSCViewControl/LayoutOnScreenControlsViewController.swift
@@ -165,6 +165,16 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
@IBOutlet weak var dockedAlphaLabel: UILabel!
@IBOutlet weak var dockedAlphaSlider: UISlider!
+ @IBOutlet weak var sprintKeyStack: UIStackView!
+ @IBOutlet weak var sprintKeySelector: UISegmentedControl!
+ @IBOutlet weak var sprintKeyThresholdLabel: UILabel!
+ @IBOutlet weak var sprintKeyThresholdSlider: UISlider!
+
+ @IBOutlet weak var walkKeyStack: UIStackView!
+ @IBOutlet weak var walkKeySelector: UISegmentedControl!
+ @IBOutlet weak var walkKeyThresholdLabel: UILabel!
+ @IBOutlet weak var walkKeyThresholdSlider: UISlider!
+
@IBOutlet weak var widgetPanelStack: UIStackView!
@IBOutlet private weak var toolbarTopConstraintiPhone: NSLayoutConstraint!
@@ -293,6 +303,9 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
override func viewWillDisappear(_ animated: Bool) {
OnScreenWidgetView.editMode = false
OnScreenWidgetView.isTweakingHighlight = false
+ for case let widget as OnScreenWidgetView in onScreenWidgetViews {
+ widget.updateMovementThresholdPreview()
+ }
super.viewWillDisappear(animated)
NotificationCenter.default.post(name: Notification.Name("OscLayoutCloseNotification"), object: self)
}
@@ -388,6 +401,11 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
widgetView.dWheelWalkModeThreshold = buttonState.walkModeThreshold
widgetView.minStickOffset = buttonState.minStickOffset
widgetView.buttonMode = ButtonMode(rawValue: Int(buttonState.buttonMode)) ?? .slideToToggle
+ widgetView.sprintKeyActionType = OnScreenWidgetView.WalkSprintKeyActionType(rawValue: buttonState.sprintKeyActionType) ?? .hold
+ widgetView.sprintKeyThreshold = buttonState.sprintKeyThreshold
+ widgetView.walkKeyActionType = OnScreenWidgetView.WalkSprintKeyActionType(rawValue: buttonState.walkKeyActionType) ?? .hold
+ widgetView.walkKeyThreshold = buttonState.walkKeyThreshold
+
self.view.insertSubview(widgetView, belowSubview: self.widgetPanelStack)
buttonState.position = self.denormalizeWidgetPosition(buttonState.position)
widgetView.setLocation(position: buttonState.position)
@@ -717,7 +735,14 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
autoFitLabel(currentProfileLabel)
currentProfileLabel.textAlignment = .left
- currentProfileLabel.text = LocalizationHelper.localizedString(forKey: " Profile: %@ Alias: %@ Cmd: %@ Parent:%@", profilesManager.getSelectedProfile().name, widgetView.widgetLabel, widgetView.cmdString, LocalizationHelper.localizedString(forKey:OnScreenWidgetView.mapping[widgetView.parentSequence]?.widgetLabel ?? "null"))
+ currentProfileLabel.text = LocalizationHelper.localizedString(forKey: " Profile: %@ Alias: %@ Cmd: %@ Parent: %@", profilesManager.getSelectedProfile().name, widgetView.widgetLabel, widgetView.cmdString, LocalizationHelper.localizedString(forKey:OnScreenWidgetView.mapping[widgetView.parentSequence]?.widgetLabel ?? "null"))
+ if selectedWidgetView?.hasWalkSprintKeys == true {
+ let cmdCounts = widgetView.comboButtonStrings.count
+ currentProfileLabel.text = LocalizationHelper.localizedString(forKey: " Cmd: %@ Sprint: %@ Walk: %@ Parent: %@",
+ widgetView.cmdString, cmdCounts>0 ? widgetView.comboButtonStrings[0] : LocalizationHelper.localizedString(forKey: "null"),
+ cmdCounts>1 ? widgetView.comboButtonStrings[1] : LocalizationHelper.localizedString(forKey: "null"), LocalizationHelper.localizedString(forKey:OnScreenWidgetView.mapping[widgetView.parentSequence]?.widgetLabel ?? "null"))
+ }
+
undoButton.alpha = widgetView.layoutChanges.count > 1 ? 1.0 : 0.3
widgetView.accessWidgetAttributes()
@@ -755,12 +780,37 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
autoFitLabel(walkModeThresholdLabel)
}
+
+ sprintKeyStack.isHidden = !(widgetView.hasWalkSprintKeys && !widgetView.comboButtonStrings.isEmpty)
+ walkKeyStack.isHidden = !(widgetView.hasWalkSprintKeys && widgetView.comboButtonStrings.count > 1)
+ if widgetView.hasWalkSprintKeys {
+ let clampedWalkThreshold = min(widgetView.walkKeyThreshold, widgetView.sprintKeyThreshold)
+ if clampedWalkThreshold != widgetView.walkKeyThreshold {
+ widgetView.walkKeyThreshold = clampedWalkThreshold
+ }
+
+ sprintKeyThresholdSlider.value = Float(widgetView.sprintKeyThreshold)
+ sprintKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %0.2f ", sprintKeyThresholdSlider.value)
+ autoFitLabel(sprintKeyThresholdLabel)
+ sprintKeySelector.selectedSegmentIndex = Int(widgetView.sprintKeyActionType.rawValue)
+
+ walkKeyThresholdSlider.value = Float(widgetView.walkKeyThreshold)
+ walkKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %0.2f ", walkKeyThresholdSlider.value)
+ walkKeySelector.selectedSegmentIndex = Int(widgetView.walkKeyActionType.rawValue)
+ autoFitLabel(walkKeyThresholdLabel)
+
+ widgetView.updateMovementThresholdPreview()
+ } else {
+ widgetView.updateMovementThresholdPreview()
+ }
+
+
slideThresholdStack.isHidden = !widgetView.hasSlideThreshold
if widgetView.hasSlideThreshold {
slideThresholdSlider.minimumValue = Float(widgetView.slideThresholdMin)
slideThresholdSlider.maximumValue = Float(widgetView.slideThresholdMax)
slideThresholdSlider.value = Float(widgetView.slideThreshold)
- slideThresholdLabel.text = LocalizationHelper.localizedString(forKey: "Slide threshold: %.1f", widgetView.slideThreshold)
+ slideThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %.1f ", widgetView.slideThreshold)
autoFitLabel(slideThresholdLabel)
}
@@ -833,7 +883,7 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
mouseDownButtonStack.isHidden = !widgetView.isMousePadWithButtonActions
mouseButtonDownSelector.selectedSegmentIndex = Int(widgetView.mouseButtonAction.rawValue)
-
+
autoDockTimerStack.isHidden = !(widgetView.isFolder && widgetView.parentSequence == -1);
autoDockTimerSlider.value = Float(widgetView.autoDockIdleDuration)
@@ -900,6 +950,8 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
stickIndicatorOffsetStack.isHidden = true
sensitivityXStack.isHidden = true
sensitivityYStack.isHidden = true
+ sprintKeyStack.isHidden = true
+ walkKeyStack.isHidden = true
mouseDownButtonStack.isHidden = true
decelerationRateStack.isHidden = true
@@ -1068,6 +1120,10 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
newWidget.setVibration(style: Int(widget.vibrationStyle))
newWidget.mouseButtonAction = widget.mouseButtonAction
newWidget.buttonMode = widget.buttonMode
+ newWidget.sprintKeyActionType = widget.sprintKeyActionType
+ newWidget.sprintKeyThreshold = widget.sprintKeyThreshold
+ newWidget.walkKeyActionType = widget.walkKeyActionType
+ newWidget.walkKeyThreshold = widget.walkKeyThreshold
guard newWidget.widgetType == widget.widgetType else { return }
view.insertSubview(newWidget, belowSubview: widgetPanelStack)
@@ -1386,9 +1442,21 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
sensitivityYSlider.addTarget(self, action: #selector(sensitivityYSliderMoved(_:)), for: .valueChanged)
sensitivityYLabel.text = LocalizationHelper.localizedString(forKey: "SensitivityY")
sensitivityYStack.isHidden = true
+
walkModeThresholdSlider.addTarget(self, action: #selector(walkModeThresholdSliderMoved(_:)), for: .valueChanged)
walkModeThresholdLabel.text = LocalizationHelper.localizedString(forKey: "Walkmode threshold")
walkModeThresholdStack.isHidden = true
+
+ sprintKeyThresholdSlider.addTarget(self, action: #selector(sprintKeyThresholdSliderMoved(_:)), for: .valueChanged)
+ sprintKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: "Slide threshold")
+ sprintKeySelector.addTarget(self, action: #selector(sprintKeyActionChanged(_:)), for: .valueChanged)
+ sprintKeyStack.isHidden = true
+
+ walkKeyThresholdSlider.addTarget(self, action: #selector(walkKeyThresholdSliderMoved(_:)), for: .valueChanged)
+ walkKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: "Slide threshold")
+ walkKeySelector.addTarget(self, action: #selector(walkKeyActionChanged(_:)), for: .valueChanged)
+ walkKeyStack.isHidden = true
+
minStickOffsetSlider.addTarget(self, action: #selector(minStickOffsetSliderMoved(_:)), for: .valueChanged)
minStickOffsetLabel.text = LocalizationHelper.localizedString(forKey: "Minimum offset")
minStickOffsetStack.isHidden = true
@@ -1851,6 +1919,45 @@ final class LayoutOnScreenControlsViewController: UIViewController, OnScreenWidg
slideThresholdLabel.text = LocalizationHelper.localizedString(forKey: "Slide threshold: %.1f", sender.value)
selectedWidgetView?.slideThreshold = CGFloat(sender.value)
}
+
+ @objc private func sprintKeyActionChanged(_ sender: UISegmentedControl) {
+ selectedWidgetView?.sprintKeyActionType = OnScreenWidgetView.WalkSprintKeyActionType(rawValue: UInt8(sender.selectedSegmentIndex)) ?? .hold
+ }
+
+ @objc private func sprintKeyThresholdSliderMoved(_ sender: UISlider) {
+ sprintKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %0.2f ", sender.value)
+ autoFitLabel(sprintKeyThresholdLabel)
+
+ guard OnScreenWidgetView.editMode, let selectedWidgetView, widgetViewSelected else { return }
+
+ if sender.value < walkKeyThresholdSlider.value {
+ walkKeyThresholdSlider.value = sender.value
+ walkKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %0.2f ", walkKeyThresholdSlider.value)
+ autoFitLabel(walkKeyThresholdLabel)
+ }
+
+ selectedWidgetView.sprintKeyThreshold = CGFloat(sender.value)
+ selectedWidgetView.walkKeyThreshold = CGFloat(walkKeyThresholdSlider.value)
+ selectedWidgetView.updateMovementThresholdPreview()
+ }
+
+ @objc private func walkKeyActionChanged(_ sender: UISegmentedControl) {
+ selectedWidgetView?.walkKeyActionType = OnScreenWidgetView.WalkSprintKeyActionType(rawValue: UInt8(sender.selectedSegmentIndex)) ?? .hold
+ }
+
+ @objc private func walkKeyThresholdSliderMoved(_ sender: UISlider) {
+ if sender.value > sprintKeyThresholdSlider.value {
+ sender.value = sprintKeyThresholdSlider.value
+ }
+
+ walkKeyThresholdLabel.text = LocalizationHelper.localizedString(forKey: " Slide threshold: %0.2f ", sender.value)
+ autoFitLabel(walkKeyThresholdLabel)
+
+ guard OnScreenWidgetView.editMode, let selectedWidgetView, widgetViewSelected else { return }
+
+ selectedWidgetView.walkKeyThreshold = CGFloat(sender.value)
+ selectedWidgetView.updateMovementThresholdPreview()
+ }
@objc private func yawFactorSliderMoved(_ sender: UISlider) {
yawFactorLabel.text = LocalizationHelper.localizedString(forKey: "Yaw factor: %.2f", sender.value)
diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings
index 0cadec6e..9c53f302 100644
--- a/VoidLink/mul.lproj/iPad.xcstrings
+++ b/VoidLink/mul.lproj/iPad.xcstrings
@@ -187,6 +187,66 @@
}
}
},
+ "3fM-1s-S0k.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; 3fM-1s-S0k.segmentTitles[0] = \"hold\"; ObjectID = \"3fM-1s-S0k\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "hold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ }
+ }
+ },
+ "3fM-1s-S0k.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; 3fM-1s-S0k.segmentTitles[1] = \"toggle\"; ObjectID = \"3fM-1s-S0k\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "toggle"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切換"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切換"
+ }
+ }
+ }
+ },
"3rP-dh-TJi.text" : {
"comment" : "Class = \"UILabel\"; text = \"Enable PiP\"; ObjectID = \"3rP-dh-TJi\";",
"extractionState" : "extracted_with_value",
@@ -427,6 +487,36 @@
}
}
},
+ "5Uu-1K-ov7.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Sprint key\"; ObjectID = \"5Uu-1K-ov7\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Sprint key"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走鍵"
+ }
+ }
+ }
+ },
"6Cy-aH-DXp.text" : {
"comment" : "Class = \"UILabel\"; text = \"Curve Exponent\"; ObjectID = \"6Cy-aH-DXp\";",
"extractionState" : "extracted_with_value",
@@ -2305,6 +2395,66 @@
}
}
},
+ "csH-cx-ZWH.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; csH-cx-ZWH.segmentTitles[0] = \"hold\"; ObjectID = \"csH-cx-ZWH\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "hold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ }
+ }
+ },
+ "csH-cx-ZWH.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; csH-cx-ZWH.segmentTitles[1] = \"toggle\"; ObjectID = \"csH-cx-ZWH\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "toggle"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切換"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切換"
+ }
+ }
+ }
+ },
"cyH-Fh-vW6.normalTitle" : {
"comment" : "Class = \"UIButton\"; normalTitle = \"Button\"; ObjectID = \"cyH-Fh-vW6\";",
"extractionState" : "extracted_with_value",
@@ -2672,6 +2822,36 @@
}
}
},
+ "eRk-iM-nN4.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Walk key\"; ObjectID = \"eRk-iM-nN4\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Walk key"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "静步键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "靜步鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "靜步鍵"
+ }
+ }
+ }
+ },
"eTT-y7-oTM.configuration.title" : {
"comment" : "Class = \"UIButton\"; configuration.title = \"9999999\"; ObjectID = \"eTT-y7-oTM\";",
"extractionState" : "stale",
@@ -3812,6 +3992,18 @@
}
}
},
+ "kex-PV-gvq.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Slide threshold\"; ObjectID = \"kex-PV-gvq\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Slide threshold"
+ }
+ }
+ }
+ },
"kho-5R-daP.segmentTitles[0]" : {
"comment" : "Class = \"UISegmentedControl\"; kho-5R-daP.segmentTitles[0] = \"3 Finger Tap\"; ObjectID = \"kho-5R-daP\";",
"extractionState" : "extracted_with_value",
@@ -5884,6 +6076,18 @@
}
}
},
+ "v4J-ot-qpz.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Slide threshold\"; ObjectID = \"v4J-ot-qpz\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Slide threshold"
+ }
+ }
+ }
+ },
"v5f-j3-pEc.text" : {
"comment" : "Class = \"UILabel\"; text = \"Custom Double Tap Keyboard Shortcut\"; ObjectID = \"v5f-j3-pEc\";",
"extractionState" : "extracted_with_value",
diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings
index 62ccf246..b62a58bc 100644
--- a/VoidLink/mul.lproj/iPhone.xcstrings
+++ b/VoidLink/mul.lproj/iPhone.xcstrings
@@ -2521,7 +2521,7 @@
},
"zh-Hans" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "内置陀螺仪映射为"
}
},
@@ -2551,7 +2551,7 @@
},
"zh-Hans" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "指针速度"
}
},
@@ -2995,6 +2995,66 @@
}
}
},
+ "Jz7-vJ-mMl.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; Jz7-vJ-mMl.segmentTitles[0] = \"hold\"; ObjectID = \"Jz7-vJ-mMl\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "hold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ }
+ }
+ },
+ "Jz7-vJ-mMl.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; Jz7-vJ-mMl.segmentTitles[1] = \"toggle\"; ObjectID = \"Jz7-vJ-mMl\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "toggle"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ }
+ }
+ },
"KbX-bp-pYg.text" : {
"comment" : "Class = \"UILabel\"; text = \" \"; ObjectID = \"KbX-bp-pYg\";",
"extractionState" : "extracted_with_value",
@@ -3061,6 +3121,36 @@
}
}
},
+ "KpH-R8-kny.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Walk key\"; ObjectID = \"KpH-R8-kny\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Walk key"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "静步键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "靜步鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "靜步鍵"
+ }
+ }
+ }
+ },
"kva-gQ-CZh.text" : {
"comment" : "Class = \"UILabel\"; text = \"Size\"; ObjectID = \"kva-gQ-CZh\";",
"extractionState" : "extracted_with_value",
@@ -3139,6 +3229,36 @@
}
}
},
+ "lJx-Tn-7we.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Sprint key\"; ObjectID = \"lJx-Tn-7we\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Sprint key"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走键"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走鍵"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "疾走鍵"
+ }
+ }
+ }
+ },
"LVq-7c-3zg.text" : {
"comment" : "Class = \"UILabel\"; text = \"Performance Graph \"; ObjectID = \"LVq-7c-3zg\";",
"extractionState" : "extracted_with_value",
@@ -4009,7 +4129,7 @@
},
"zh-HK" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "滑出時鬆開"
}
}
@@ -4039,7 +4159,7 @@
},
"zh-HK" : {
"stringUnit" : {
- "state" : "needs_review",
+ "state" : "translated",
"value" : "滑動時按下"
}
}
@@ -5119,6 +5239,18 @@
}
}
},
+ "sYb-ef-Vkq.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Slide threshold\"; ObjectID = \"sYb-ef-Vkq\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Slide threshold"
+ }
+ }
+ }
+ },
"T0w-xM-ze7.text" : {
"comment" : "Class = \"UILabel\"; text = \"Resolution \"; ObjectID = \"T0w-xM-ze7\";",
"extractionState" : "extracted_with_value",
@@ -5389,6 +5521,66 @@
}
}
},
+ "TsJ-mx-pbL.segmentTitles[0]" : {
+ "comment" : "Class = \"UISegmentedControl\"; TsJ-mx-pbL.segmentTitles[0] = \"hold\"; ObjectID = \"TsJ-mx-pbL\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "hold"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "保持"
+ }
+ }
+ }
+ },
+ "TsJ-mx-pbL.segmentTitles[1]" : {
+ "comment" : "Class = \"UISegmentedControl\"; TsJ-mx-pbL.segmentTitles[1] = \"toggle\"; ObjectID = \"TsJ-mx-pbL\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "toggle"
+ }
+ },
+ "zh-Hans" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-Hant" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ },
+ "zh-HK" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "切换"
+ }
+ }
+ }
+ },
"ttV-kY-jCi.text" : {
"comment" : "Class = \"UILabel\"; text = \"The recommended value is 2. 1 frame provides the lowest latency, but may require an Ethernet connection.\"; ObjectID = \"ttV-kY-jCi\";",
"extractionState" : "stale",
@@ -6007,6 +6199,18 @@
}
}
},
+ "YQo-bc-mwR.text" : {
+ "comment" : "Class = \"UILabel\"; text = \"Slide threshold\"; ObjectID = \"YQo-bc-mwR\";",
+ "extractionState" : "extracted_with_value",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "new",
+ "value" : "Slide threshold"
+ }
+ }
+ }
+ },
"Z9M-yq-an2.title" : {
"comment" : "Class = \"UIBarButtonItem\"; title = \"Import\"; ObjectID = \"Z9M-yq-an2\";",
"extractionState" : "extracted_with_value",
diff --git a/VoidLinkPreview/WidgetPickerView.swift b/VoidLinkPreview/WidgetPickerView.swift
index 2ddfb41c..4c171bbe 100644
--- a/VoidLinkPreview/WidgetPickerView.swift
+++ b/VoidLinkPreview/WidgetPickerView.swift
@@ -612,9 +612,36 @@ struct WidgetPickerView: View {
return isPad(firstItem) && !isPad(lastItem) && poolItems.contains(where: isPad) && poolItems.contains(where: { !isPad($0) })
}
+ private var selectedMovementPadCommand: String? {
+ poolItems.first(where: { $0.cmd == "WASDPAD" || $0.cmd == "ARROWPAD" })?.cmd
+ }
+
+ private var movementPadSingleButtonCount: Int {
+ guard selectedMovementPadCommand != nil else { return 0 }
+ return poolItems.filter { !isPad($0) }.count
+ }
+
+ private var movementPadNeedsWalkKeyTip: Bool {
+ selectedMovementPadCommand != nil && movementPadSingleButtonCount == 0
+ }
+
+ private var movementPadNeedsSprintKeyTip: Bool {
+ selectedMovementPadCommand != nil && movementPadSingleButtonCount == 1
+ }
+
+ private var movementPadHasReachedSingleKeyLimit: Bool {
+ selectedMovementPadCommand != nil && movementPadSingleButtonCount >= 2
+ }
+
private func updateTipMessageForCurrentPoolState() {
if poolItems.isEmpty {
resetTipMessage()
+ } else if movementPadNeedsWalkKeyTip {
+ setTipMessage(SwiftLocalizationHelper.localizedString(forKey: "Please select a sprint key"))
+ } else if movementPadNeedsSprintKeyTip {
+ setTipMessage(SwiftLocalizationHelper.localizedString(forKey: "Sprint key set. Select a walk key (optional, you can skip and create)"))
+ } else if movementPadHasReachedSingleKeyLimit {
+ setTipMessage(SwiftLocalizationHelper.localizedString(forKey: "Sprint and walk keys are set"))
} else if hasButtonThenPadCombination {
setTipMessage(SwiftLocalizationHelper.localizedString(forKey: "A button with touchpad functionality will be created"))
} else if hasPadThenButtonCombination {
@@ -1332,6 +1359,14 @@ struct WidgetPickerView: View {
return false
}
+ if movementPadHasReachedSingleKeyLimit {
+ setTipMessage(
+ SwiftLocalizationHelper.localizedString(forKey: "Walk and sprint keys are already set. No more keys can be added"),
+ type: .error
+ )
+ return false
+ }
+
let candidate = WidgetPoolItem(
cmd: cmd,
source: source,
@@ -1774,11 +1809,19 @@ struct WidgetPickerView: View {
keyboardPickerMode == .shortcutPicker
}
+ private var containsMovementDirectionPad: Bool {
+ poolItems.contains { $0.cmd == "WASDPAD" || $0.cmd == "ARROWPAD" }
+ }
+
private var shouldBypassCreateWidgetSheet: Bool {
if isShortcutPickerMode {
return true
}
+ if containsMovementDirectionPad {
+ return true
+ }
+
guard !poolItems.isEmpty else { return false }
let hasVisibleConfigurationSection =