diff --git a/Limelight/Input/CommandManager.swift b/Limelight/Input/CommandManager.swift index 57f9abd7..cb712418 100644 --- a/Limelight/Input/CommandManager.swift +++ b/Limelight/Input/CommandManager.swift @@ -74,7 +74,7 @@ import UIKit "OSCL2" : 0, "OSCR2" : 0] - @objc public static let touchPadCmds: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD", "MousePAD", "DPAD", "WASDPAD", "ARROWPAD"] + @objc public static let touchPadCmds: [String] = ["LSVPAD", "RSVPAD", "LSPAD", "RSPAD", "MOUSEPAD", "DPAD", "WASDPAD", "ARROWPAD"] static let keyboardButtonMappings: [String: Int16] = [ // Windows Key Codes diff --git a/Limelight/Input/OnScreenButtonView.swift b/Limelight/Input/OnScreenButtonView.swift index 00f03760..857a10d7 100644 --- a/Limelight/Input/OnScreenButtonView.swift +++ b/Limelight/Input/OnScreenButtonView.swift @@ -22,8 +22,6 @@ import UIKit @objc static public var editMode: Bool = false @objc static public var timestampOfButtonBeingDragged: TimeInterval = 0 - private let appWindow: UIView - @objc public var keyLabel: String @objc public var keyString: String @objc public var timestamp: TimeInterval @@ -32,41 +30,65 @@ import UIKit @objc public var heightFactor: CGFloat @objc public var backgroundAlpha: CGFloat @objc public var latestTouchLocation: CGPoint + + private let appWindow: UIView + + + private var touchLockedForMoveEvent: UITouch + + // for LSVPAD, RSVPAD @objc public var deltaX: CGFloat @objc public var deltaY: CGFloat + + // for LSPAD, RSPAD @objc public var offSetX: CGFloat @objc public var offSetY: CGFloat + + // for LSVPAD, RSVPAD, LSPAD, RSPAD + private let crossMarkColor: CGColor = UIColor(white: 1, alpha: 0.7).cgColor + private let stickBallColor: CGColor = UIColor(white: 1, alpha: 0.75).cgColor + private var stickInputScale: CGFloat = 30 + private var l3r3Indicator = CAShapeLayer() + private var crossMarkLayer = CAShapeLayer() + private var stickBallLayer = CAShapeLayer() + @objc public var stickIndicatorXOffset: CGFloat = 120 + // check quick double tap: private var quickDoubleTapDetected: Bool private var touchTapTimeInterval: TimeInterval private var touchTapTimeStamp: TimeInterval private let QUICK_TAP_TIME_INTERVAL = 0.2 - private var onScreenControls: OnScreenControls - private let label: UILabel - // private let originalBackgroundColor: UIColor - private var touchBeganLocation: CGPoint = .zero - private var storedLocation: CGPoint = .zero - private let minimumBorderAlpha: CGFloat = 0.19 - private var defaultBorderColor: CGColor = UIColor(white: 0.2, alpha: 0.3).cgColor - private let moonlightPurple: CGColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 0.86).cgColor - private let crossMarkColor: CGColor = UIColor(white: 1, alpha: 0.7).cgColor - private let stickBallColor: CGColor = UIColor(white: 1, alpha: 0.75).cgColor - private var stickInputScale: CGFloat = 30 - - private let buttonDownVisualEffectLayer = CAShapeLayer() - private var buttonDownVisualEffectWidth: CGFloat - + // for all LRUD pads private var upIndicator = CAShapeLayer() private var downIndicator = CAShapeLayer() private var leftIndicator = CAShapeLayer() private var rightIndicator = CAShapeLayer() - private var l3r3Indicator = CAShapeLayer() - private var crossMarkLayer = CAShapeLayer() - private var stickBallLayer = CAShapeLayer() + // for DPAD LRUD pad private var lrudIndicatorBall = CAShapeLayer() - @objc public var stickIndicatorXOffset: CGFloat = 120 + + // OnScreenControls instance + private var onScreenControls: OnScreenControls + + // key / button label + private let label: UILabel + + // first touch location within the button or pad view (self) + private var touchBeganLocation: CGPoint = .zero + + // for mousePad + private var mousePointerMoved: Bool + private var twoTouchesDetected: Bool + + private var storedLocation: CGPoint = .zero // location from persisted data + private let minimumBorderAlpha: CGFloat = 0.19 + private var defaultBorderColor: CGColor = UIColor(white: 0.2, alpha: 0.3).cgColor + private let moonlightPurple: CGColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 0.86).cgColor + + // whole button press down visual effect + private let buttonDownVisualEffectLayer = CAShapeLayer() + private var buttonDownVisualEffectWidth: CGFloat @objc init(keyString: String, keyLabel: String) { self.keyString = keyString @@ -89,6 +111,9 @@ import UIKit self.touchTapTimeInterval = 100 self.touchTapTimeStamp = 100 self.buttonDownVisualEffectWidth = 0 + self.mousePointerMoved = false + self.touchLockedForMoveEvent = UITouch() + self.twoTouchesDetected = false super.init(frame: .zero) upIndicator = createLrudDirectionLayer() @@ -516,7 +541,44 @@ import UIKit //================================================================================================ + //===== MOUSEPAD related methods============================================================= + private func sendLongMouseLeftButtonClickEvent() { + DispatchQueue.global(qos: .userInitiated).async { + // Logging the press event + NSLog("Sending left mouse button press") + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), BUTTON_LEFT) + + // Wait 200 ms to simulate a real button press + usleep(UInt32(self.QUICK_TAP_TIME_INTERVAL * 1000000)) + + // If quick tap is not detected, release the button + if !self.quickDoubleTapDetected { + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_LEFT) + // NSLog("double click: first long click release") + } + else{NSLog("Left mouse button release cancelled, keep pressing down, turning into dragging...")} + // Don't release the button if we're still dragging, this will prevent the dragging from being interrupted. + } + } + + private func sendShortMouseLeftButtonClickEvent() { + DispatchQueue.global(qos: .userInitiated).async { + NSLog("double click: sending short click") + usleep(UInt32(50 * 1000)) + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), BUTTON_LEFT) + usleep(UInt32(50 * 1000)) + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_LEFT) + } + } + private func sendMouseRightButtonClickEvent() { + DispatchQueue.global(qos: .userInitiated).async { + usleep(UInt32(50 * 1000)) + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), BUTTON_RIGHT) + usleep(UInt32(50 * 1000)) + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_RIGHT) + } + } //==== wholeButtonPress visual effect============================================= private func buttonDownVisualEffect() { @@ -592,18 +654,12 @@ import UIKit //========================================================================================================== - - - - - // Touch event handling override func touchesBegan(_ touches: Set, with event: UIEvent?) { - - // print("touchDown: %f", CACurrentMediaTime()) - - if touches.count == 1 { - + super.touchesBegan(touches, with: event) + self.isMultipleTouchEnabled = self.keyString == "MOUSEPAD" // only enable multi-touch in mousePad mode + + if touches.count == 1 { // to make sure touchBegan location captured properly, don't use event.alltouches.count here let currentTime = CACurrentMediaTime() touchTapTimeInterval = currentTime - touchTapTimeStamp touchTapTimeStamp = currentTime @@ -613,17 +669,16 @@ import UIKit self.touchBeganLocation = touch!.location(in: self) self.latestTouchLocation = touchBeganLocation } + + let allCapturedTouchesCount = event?.allTouches?.filter({ $0.view == self }).count // this will counts all valid touches within the self buttonView, and excludes touches in other buttonViews + if allCapturedTouchesCount == 2 { + self.twoTouchesDetected = true + } self.pressed = true - super.touchesBegan(touches, with: event) - //self.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 0.7) - - // OnScreenControls.testMethod(); - // RelativeTouchHandler.testMethod(); - if !OnScreenButtonView.editMode { - if CommandManager.touchPadCmds.contains(self.keyString) && touches.count == 1{ + if CommandManager.touchPadCmds.contains(self.keyString) && touches.count == 1{ // don't use event?.allTouches?.count here, it will counts all touches including the ones captured by other UIViews switch self.keyString { case "LSPAD": self.showStickIndicator() @@ -699,7 +754,7 @@ import UIKit if !OnScreenButtonView.editMode { if CommandManager.touchPadCmds.contains(self.keyString){ - handleTouchPadMoveEvent(touches: touches) + handleTouchPadMoveEvent(touches, with: event) } } @@ -718,10 +773,10 @@ import UIKit } - private func handleTouchPadMoveEvent (touches: Set){ - if touches.count == 1{ + private func handleTouchPadMoveEvent (_ touches: Set, with event: UIEvent?){ + if touches.count == 1{ // don't use event.alltouches.count here, it will counts all touches + self.mousePointerMoved = true let currentTouchLocation: CGPoint = (touches.first?.location(in: self))! - self.deltaX = currentTouchLocation.x - self.latestTouchLocation.x self.deltaY = currentTouchLocation.y - self.latestTouchLocation.y self.offSetX = currentTouchLocation.x - self.touchBeganLocation.x @@ -729,6 +784,9 @@ import UIKit self.latestTouchLocation = currentTouchLocation switch self.keyString{ + case "MOUSEPAD": + LiSendMouseMoveEvent(Int16(truncatingIfNeeded: Int(deltaX)), Int16(truncatingIfNeeded: Int(deltaY))) + break case "LSPAD": self.sendLeftStickTouchPadEvent(inputX: offSetX, inputY: offSetY) updateStickBallPosition() @@ -749,10 +807,31 @@ import UIKit } override func touchesEnded(_ touches: Set, with event: UIEvent?) { - // self.pressed = false // will be reset outside the class super.touchesEnded(touches, with: event) - quickDoubleTapDetected = false - if !OnScreenButtonView.editMode && CommandManager.touchPadCmds.contains(self.keyString) && touches.count == 1 { + + if self.keyString != "MOUSEPAD" {quickDoubleTapDetected = false} //do not reset this flag here in mousePad mode + + let allCapturedTouchesCount = event?.allTouches?.filter({ $0.view == self }).count // this will counts all valid touches within the self buttonView, and excludes touches in other buttonViews + + // deal with MOUSPAD first + if !OnScreenButtonView.editMode && self.keyString == "MOUSEPAD" && allCapturedTouchesCount == 1 && !twoTouchesDetected { + if !mousePointerMoved && !quickDoubleTapDetected {self.sendLongMouseLeftButtonClickEvent()} // deal with single tap(click) + if quickDoubleTapDetected { //deal with quick double tap + LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), BUTTON_LEFT) //must release the button anyway, because the button is likely being held down since the long click turned into a dragging event. + if !mousePointerMoved {self.sendShortMouseLeftButtonClickEvent()} + quickDoubleTapDetected = false + } + mousePointerMoved = false // reset this flag + } + + if !OnScreenButtonView.editMode && self.keyString == "MOUSEPAD" && twoTouchesDetected && touches.count == allCapturedTouchesCount { // need to enable multi-touch first + // touches.count == allCapturedTouchesCount means allfingers are lifting + self.sendMouseRightButtonClickEvent() + twoTouchesDetected = false + } + + // then other types of pads + if !OnScreenButtonView.editMode && CommandManager.touchPadCmds.contains(self.keyString) { switch self.keyString{ case "LSPAD": self.onScreenControls.clearLeftStickTouchPadFlag() @@ -786,6 +865,7 @@ import UIKit } } + self.buttonUpVisualEffect() self.l3r3Indicator.removeFromSuperlayer() self.upIndicator.removeFromSuperlayer() diff --git a/Limelight/Input/RelativeTouchHandler.m b/Limelight/Input/RelativeTouchHandler.m index 5e259ea3..9ee6e255 100644 --- a/Limelight/Input/RelativeTouchHandler.m +++ b/Limelight/Input/RelativeTouchHandler.m @@ -277,9 +277,10 @@ static const float QUICK_TAP_TIME_INTERVAL = 0.2; // Wait 100 ms to simulate a real button press usleep(QUICK_TAP_TIME_INTERVAL * 1000000); if(!self->quickTapDetected){ - NSLog(@"Left mouse button release cancelled, keep pressing down, turning into dragging..."); LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); } + else NSLog(@"Left mouse button release cancelled, keep pressing down, turning into dragging..."); + // do not release the button if we're still dragging, this will prevent the dragging from being interrupted. }); }