Files
VoidLink/Limelight/Input/OnScreenWidgetView.swift
T
2025-06-06 12:00:23 +08:00

1237 lines
57 KiB
Swift

//
// OnScreenKey.swift
// Voidex
//
// Created by True砖家 on 2024/8/4.
// Copyright © 2024 True砖家 on Bilibili. All rights reserved.
//
import UIKit
@objc class OnScreenWidgetView: UIView, InstanceProviderDelegate {
// receiving the OnScreenControls instance from delegate
@objc func getOnScreenControlsInstance(_ sender: Any) {
if let controls = sender as? OnScreenControls {
self.onScreenControls = controls
print("ClassA received OnScreenControls instance: \(controls)")
} else {
print("ClassA received an unknown sender")
}
}
enum WidgetTypeEnum: UInt8 {
case uninitialized = 0
case button = 1
case touchPad = 2
}
private var widgetType: WidgetTypeEnum = WidgetTypeEnum.uninitialized
@objc static public var editMode: Bool = false
@objc public var buttonLabel: String
@objc public var cmdString: String
private var buttonString: String = ""
private var touchPadString: String = ""
// super combo key string set
private var comboButtonStrings: [String] = []
private var comboKeyTimeIntervalMs: UInt32 = 0
@objc public var pressed: Bool
@objc public var widthFactor: CGFloat = 1.0
@objc public var heightFactor: CGFloat = 1.0
@objc public var borderWidth: CGFloat = 0.0
@objc public var backgroundAlpha: CGFloat = 0.5
@objc public var latestTouchLocation: CGPoint
@objc public var selfViewOnTheRight: Bool = false
@objc public var shape: String = "default"
private var storedCenter: CGPoint = .zero // location from persisted data
private let appWindow: UIView
// for all touchPad or buttons hybrid with touchPads
@objc public var hasStickIndicator: Bool = false
@objc public var hasSensitivityTweak: Bool = false
// for all stick pads
@objc public var minStickOffset: CGFloat = 0
public let stickMaxOffset: CGFloat = 0x7FFE
// 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
private let crossMarkColor: CGColor = UIColor(white: 1, alpha: 0.70).cgColor
private let stickBallColor: CGColor = UIColor(white: 1, alpha: 0.75).cgColor
private var stickInputScale: CGFloat = 35
private var l3r3Indicator = CAShapeLayer()
private let stickBallMaxOffset = 18.0
@objc public var crossMarkLayer = CAShapeLayer()
@objc public var stickBallLayer = CAShapeLayer()
// this is for all stick pads and mouse Pad
@objc public var sensitivityFactor: CGFloat = 1.0
// check quick double tap:
private var quickDoubleTapDetected: Bool
private var touchTapTimeInterval: TimeInterval
private var touchTapTimeStamp: TimeInterval
private let QUICK_TAP_TIME_INTERVAL = 0.2
@objc public var stickIndicatorOffset: CGFloat = 120
// for all LRUD pads
private var upIndicator = CAShapeLayer()
private var downIndicator = CAShapeLayer()
private var leftIndicator = CAShapeLayer()
private var rightIndicator = CAShapeLayer()
// for DPAD LRUD pad
private var lrudIndicatorBall = CAShapeLayer()
// OnScreenControls instance
private var onScreenControls: OnScreenControls
// key / button label
private let label: UILabel
// first touch location within the button or pad view (self)
@objc public var touchBeganLocation: CGPoint = .zero
// for mousePad
private var touchLockedForMoveEvent: UITouch
private var touchBegan: Bool = false
private var mousePointerMoved: Bool
private var twoTouchesDetected: Bool
// border & visual effect
private var 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
@objc public let buttonDownVisualEffectLayer = CAShapeLayer()
private var buttonDownVisualEffectWidth: CGFloat
@objc init(cmdString: String, buttonLabel: String, shape:String) {
self.cmdString = cmdString
self.touchPadString = ""
if !self.cmdString.contains("+"){
// 安全解包并处理 `comboKeyStrings`
if var comboStrings = CommandManager.shared.extractSinglCmdStringsFromComboKeys(from: self.cmdString) {
// extract timeInterval
if let lastString = comboStrings.last, lastString.contains("MS") {
// 移除 "MS" 后的部分并转换为整数
let timeIntervalString = lastString.replacingOccurrences(of: "MS", with: "")
// 安全地将字符串转换为整数
if let timeInterval = UInt32(timeIntervalString) {
self.comboKeyTimeIntervalMs = timeInterval
} else {print("无法将时间字符串转换为整数")}
comboStrings.removeLast()
}
if CommandManager.touchPadCmds.contains(comboStrings.first ?? "") {self.widgetType = WidgetTypeEnum.touchPad}
else {self.widgetType = WidgetTypeEnum.button}
let touchPadString = Set(comboStrings).intersection(Set(CommandManager.touchPadCmds)).first ?? ""
self.comboButtonStrings = comboStrings.filter{$0 != touchPadString}
self.touchPadString = touchPadString
self.buttonString = self.comboButtonStrings.first ?? ""
// let stickAndMouseTouchpads = ["LSPAD", "RSPAD", "LSVPAD", "RSVPAD", "MOUSEPAD"]
let nonVectorStickPads = ["LSPAD", "RSPAD"]
if CommandManager.touchPadCmds.contains(self.touchPadString) {self.hasSensitivityTweak = true}
if nonVectorStickPads.contains(self.touchPadString) && widgetType == WidgetTypeEnum.touchPad {self.hasStickIndicator = true}
switch self.cmdString {
case "LSPAD", "LSVPAD":
self.comboButtonStrings = ["OSCL3"]
case "RSPAD", "RSVPAD":
self.comboButtonStrings = ["OSCR3"]
default: break
}
}
else {print("无法从 keyString 提取 comboKeyStrings")}
}
else {
self.widgetType = WidgetTypeEnum.button // legacy combo button connected by "+"
}
print("widgetType: \(self.widgetType)")
print("touchPadString: \(self.touchPadString)")
for comboButtonString in comboButtonStrings {
print("comboButtonString: \(comboButtonString)")
}
self.buttonLabel = buttonLabel
self.shape = shape
self.label = UILabel()
// self.originalBackgroundColor = UIColor(white: 0.2, alpha: 0.7)
self.pressed = false
// self.widthFactor = 1.0
// self.heightFactor = 1.0
// self.backgroundAlpha = 0.5
// self.velocityFactor = 1.0
self.latestTouchLocation = CGPoint(x: 0, y: 0)
self.deltaX = 0
self.deltaY = 0
self.offSetX = 0
self.offSetY = 0
self.onScreenControls = OnScreenControls()
self.appWindow = UIApplication.shared.windows.first!
self.quickDoubleTapDetected = false
self.touchTapTimeInterval = 100
self.touchTapTimeStamp = 100
self.buttonDownVisualEffectWidth = 0
self.mousePointerMoved = false
self.touchLockedForMoveEvent = UITouch()
self.twoTouchesDetected = false
self.stickIndicatorOffset = 95
self.sensitivityFactor = 1.0
super.init(frame: .zero)
upIndicator = createLrudDirectionLayer()
upIndicator.anchorPoint = CGPoint(x: 0.5, y: 1)
downIndicator = createLrudDirectionLayer()
downIndicator.anchorPoint = CGPoint(x: 0.5, y: 0)
leftIndicator = createLrudDirectionLayer()
leftIndicator.anchorPoint = CGPoint(x: 1, y: 0.5)
rightIndicator = createLrudDirectionLayer()
rightIndicator.anchorPoint = CGPoint(x: 0, y: 0.5)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ======================================================================================================
@objc public func setLocation(position: CGPoint) {
/*
NSLayoutConstraint.activate([
self.centerXAnchor.constraint(equalTo: self.superview!.leadingAnchor, constant: xOffset),
self.centerYAnchor.constraint(equalTo: self.superview!.topAnchor, constant: yOffset),
])
*/
storedCenter = position
center = storedCenter
}
@objc public func enableRelocationMode(enabled: Bool){
OnScreenWidgetView.editMode = enabled
}
@objc public func adjustTransparency(alpha: CGFloat){
if alpha != 0 {
self.backgroundAlpha = alpha
}
else{
// self.backgroundAlpha = 0.5
self.backgroundAlpha = alpha
}
self.tweakAlpha()
}
@objc public func adjustBorder(width: CGFloat){
self.borderWidth = width
// self.layer.borderWidth = borderWidth
// if CommandManager.touchPadCmds.contains(self.keyString) && width == 0 {self.layer.borderWidth = 1}
setupView()
}
@objc public func resizeWidgetView(){
guard let superview = superview else { return }
// Deactivate existing constraints if necessary
NSLayoutConstraint.deactivate(self.constraints)
// To resize the button, we must set this to false temporarily
translatesAutoresizingMaskIntoConstraints = false
// replace invalid factor values
if self.widthFactor == 0 {self.widthFactor = 1.0}
if self.heightFactor == 0 {self.heightFactor = 1.0}
/*
NSLayoutConstraint.activate([
self.centerXAnchor.constraint(equalTo: self.superview!.leadingAnchor, constant: storedLocation.x),
self.centerYAnchor.constraint(equalTo: self.superview!.topAnchor, constant: storedLocation.y)])
*/
// Constraints for resizing
self.changeAndActivateContraints()
// Trigger layout update
superview.layoutIfNeeded()
// Re-setup widgetView style
setupView()
}
private func tweakAlpha(){
// setup default border from self.backgroundAlpha
let realBackgroundAlpha = self.backgroundAlpha - 0.18 // offset to be consistent with legacy onScreen controller layer opacity
self.backgroundColor = UIColor(white: 0.2, alpha: realBackgroundAlpha) // offset to be consistent with legacy onScreen controller layer opacity
var borderAlpha = realBackgroundAlpha * 1.01
if widgetType == WidgetTypeEnum.touchPad {
minimumBorderAlpha = 0.0
}
if borderAlpha < minimumBorderAlpha {
borderAlpha = minimumBorderAlpha
}
defaultBorderColor = UIColor(white: 0.2, alpha: borderAlpha).cgColor
self.layer.borderColor = defaultBorderColor
if widgetType == WidgetTypeEnum.touchPad {
self.backgroundColor = UIColor.clear // make touchPad transparent
self.layer.borderColor = UIColor(white: 0.2, alpha: borderAlpha - 0.15).cgColor // reduced border alpha for touchPad
}
}
private func changeAndActivateContraints(){
if self.shape == "round"{ // we'll make custom osc buttons round & smaller
NSLayoutConstraint.activate([
self.widthAnchor.constraint(equalToConstant: CGFloat(Int(60 * self.widthFactor / 2) * 2)),
self.heightAnchor.constraint(equalToConstant: CGFloat(Int(60 * self.heightFactor / 2) * 2)),])
}
if self.shape == "square" {
NSLayoutConstraint.activate([
self.widthAnchor.constraint(equalToConstant: CGFloat(Int(70 * self.widthFactor / 2) * 2)),
self.heightAnchor.constraint(equalToConstant: CGFloat(Int(65 * self.heightFactor / 2) * 2)),])
}
if self.shape == "largeSquare" { // override all shape strings
NSLayoutConstraint.activate([
self.widthAnchor.constraint(equalToConstant: CGFloat(Int(170 * self.widthFactor / 2) * 2)),
self.heightAnchor.constraint(equalToConstant: CGFloat(Int(150 * self.heightFactor / 2) * 2)),])
}
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 10), // set up label size contrain within UIView
label.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -10),
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor),])
}
private func setupView() {
label.text = self.buttonLabel
label.font = UIFont.boldSystemFont(ofSize: 19)
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.1 // Adjust the scale factor as needed
label.textColor = UIColor(white: 1.0, alpha: 0.82)
label.textAlignment = .center
label.shadowColor = .black
label.shadowOffset = CGSize(width: 1, height: 1)
label.translatesAutoresizingMaskIntoConstraints = false // enable auto alignment for the label
self.translatesAutoresizingMaskIntoConstraints = true // this is mandatory to prevent unexpected key view location change
self.layer.cornerRadius = 16
self.layer.borderWidth = self.borderWidth
self.tweakAlpha()
if self.shape == "default" || self.shape.isEmpty {
if CommandManager.oscButtonMappings.keys.contains(self.buttonString) && !CommandManager.oscRectangleButtonCmds.contains(self.buttonString){ //make oscButtons round
self.shape = "round"
}
else {
self.shape = "square"
}
}
if self.widgetType == WidgetTypeEnum.touchPad {
self.shape = "largeSquare" // override shape from user input
if(self.borderWidth < 1) {self.layer.borderWidth = 1}
else {self.layer.borderWidth = self.borderWidth}
label.text = "" // make touchPad display no text
if OnScreenWidgetView.editMode { //display label in edit mode to make the pad more visible
label.text = self.buttonLabel
}
}
if CommandManager.specialOverlayButtonCmds.contains(self.cmdString){
self.layer.borderWidth = 0
}
if self.shape == "round" {
//setup round buttons
self.layer.cornerRadius = self.frame.width/2
// self.layer.borderWidth = self.borderWidth
label.minimumScaleFactor = 0.15 // Adjust the scale factor for oscButtons
label.font = UIFont.boldSystemFont(ofSize: 22)
}
if self.shape == "square" || self.shape == "largeSquare" {
//just do nothing here
}
// self.layer.shadowColor = UIColor.clear.cgColor
// self.layer.shadowRadius = 8
// self.layer.shadowOpacity = 0.5
addSubview(label)
self.changeAndActivateContraints()
center = storedCenter //anchor the center while resizing self
setupButtonDownVisualEffectLayer();
}
private func createAndShowl3r3Indicator() -> CAShapeLayer{
let indicatorFrame = CAShapeLayer();
let indicatorBorder = CAShapeLayer();
indicatorFrame.frame = CGRectMake(0, 0, 75, 75)
indicatorFrame.cornerRadius = 9
indicatorBorder.borderWidth = 7
indicatorBorder.frame = indicatorFrame.bounds.insetBy(dx: -indicatorBorder.borderWidth, dy: -indicatorBorder.borderWidth) // Adjust the inset as needed
indicatorBorder.borderColor = UIColor.clear.cgColor
indicatorBorder.cornerRadius = indicatorFrame.cornerRadius + indicatorBorder.borderWidth
indicatorBorder.backgroundColor = UIColor.clear.cgColor
indicatorBorder.fillColor = UIColor.clear.cgColor
let path = UIBezierPath(roundedRect: indicatorBorder.bounds, cornerRadius: indicatorBorder.cornerRadius)
indicatorBorder.path = path.cgPath
indicatorBorder.borderColor = moonlightPurple
self.layer.superlayer?.addSublayer(indicatorBorder)
indicatorBorder.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y)
return indicatorBorder
}
//================================================================================================
//Indicator overlay for on-screen game controller left or right sticks (non-vector mode)
private func handleStickBallReachingBorder(){
stickBallLayer.lineWidth = 0.6
// stickBallLayer.shadowOffset = CGSize(width: 0.0, height: 0.0)
stickBallLayer.shadowOffset = CGSize(width: 0.0, height: 0.0)
// stickBallLayer.shadowColor = stickBallLayer.strokeColor
}
private func handleStickBallLeavingBorder(){
stickBallLayer.lineWidth = 0
stickBallLayer.shadowOffset = CGSize(width: 0.5, height: 0.5)
stickBallLayer.shadowOpacity = 0.8
stickBallLayer.shadowColor = UIColor.black.cgColor
}
// create stick indicator: the crossMark & stickBall:
@objc public func showStickIndicator(){
// tell if the self button is located on the left or right
// self.selfViewOnTheRight = (self.storedCenter.x > self.appWindow.frame.width*0.5), deprecated
// let offsetSign = selfViewOnTheRight ? -1 : 1
let stickMarkerRelativeLocation:CGPoint
if !OnScreenWidgetView.editMode {
stickMarkerRelativeLocation = CGPointMake(touchBeganLocation.x, touchBeganLocation.y - self.stickIndicatorOffset)
}
else{
stickMarkerRelativeLocation = CGPointMake(touchBeganLocation.x, touchBeganLocation.y)
}
self.crossMarkLayer = createAndShowCrossMarkOnTouchPoint(at: stickMarkerRelativeLocation)
self.stickBallLayer = createAndShowStickBall(at: stickMarkerRelativeLocation)
}
// cross mark for left & right gamePad
private func createAndShowCrossMarkOnTouchPoint(at point: CGPoint) -> CAShapeLayer {
let crossLayer = CAShapeLayer()
let path = UIBezierPath()
let crossSize = 26.0
path.move(to: CGPoint(x: point.x - crossSize / 2, y: point.y))
path.addLine(to: CGPoint(x: point.x + crossSize / 2, y: point.y))
// 竖线
path.move(to: CGPoint(x: point.x, y: point.y - crossSize / 2))
path.addLine(to: CGPoint(x: point.x, y: point.y + crossSize / 2))
crossLayer.path = path.cgPath
crossLayer.strokeColor = crossMarkColor
crossLayer.lineWidth = 1.2
crossLayer.fillColor = crossMarkColor
self.layer.superlayer?.addSublayer(crossLayer)
crossLayer.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame))
// crossLayer.shadowRadius = 1
crossLayer.shadowColor = UIColor.black.cgColor
crossLayer.shadowOffset = CGSize(width: 1, height: 1)
crossLayer.shadowRadius = 0;
crossLayer.shadowOpacity = 0.8
return crossLayer
}
private func createAndShowStickBall(at center: CGPoint) -> CAShapeLayer {
// Create a circular path using UIBezierPath
let path = UIBezierPath(arcCenter: center, radius: 8, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
// Create a CAShapeLayer
let stickBallLayer = CAShapeLayer()
stickBallLayer.path = path.cgPath // Assign the circular path to the shape layer
self.layer.superlayer?.addSublayer(stickBallLayer)
stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame))
// stickBallLayer.position = CG
// Set the stroke color and width (border of the circle)
stickBallLayer.strokeColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0).cgColor
//stickBallLayer.
stickBallLayer.lineWidth = 0
stickBallLayer.shadowOffset = CGSize(width: 0.5, height: 0.5)
stickBallLayer.shadowRadius = 0;
stickBallLayer.shadowOpacity = 0.8
// Set the fill color (inside of the circle)
stickBallLayer.fillColor = stickBallColor // Light fill with some transparency
return stickBallLayer
}
@objc public func updateStickIndicator(){
CATransaction.begin()
CATransaction.setDisableActions(true)
self.stickBallLayer.removeAllAnimations()
if !OnScreenWidgetView.editMode {
let realOffsetX = touchInputToStickBallCoord(input: offSetX*sensitivityFactor)
let realOffsetY = touchInputToStickBallCoord(input: offSetY*sensitivityFactor)
self.stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame) + realOffsetX, CGRectGetMidY(self.crossMarkLayer.frame) + realOffsetY)
if fabs(realOffsetX) == stickBallMaxOffset || fabs(realOffsetY) == stickBallMaxOffset {
handleStickBallReachingBorder()
}
else{
handleStickBallLeavingBorder()
}
}
else{
// illustrate offset distance in edit mode
// let offsetSign = self.selfViewOnTheRight ? -1 : 1 // dprecated
// let illlustrationPoint = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame)/4)
self.crossMarkLayer.position = CGPointMake(CGRectGetMidX(self.stickBallLayer.frame), CGRectGetMidY(self.stickBallLayer.frame)-stickIndicatorOffset)
}
CATransaction.commit()
}
private func resetStickBallPositionAndRemoveIndicator(){
handleStickBallLeavingBorder()
CATransaction.begin()
// CATransaction.setDisableActions(true)
CATransaction.setAnimationDuration(0.15)
self.stickBallLayer.position = CGPointMake(CGRectGetMidX(self.crossMarkLayer.frame), CGRectGetMidY(self.crossMarkLayer.frame))
CATransaction.setCompletionBlock {
DispatchQueue.global().async {
// 后台执行耗时操作
usleep(200000)
DispatchQueue.main.async { // switch to main thread to update UI
if !self.touchBegan {
self.crossMarkLayer.removeFromSuperlayer()
self.stickBallLayer.removeFromSuperlayer()
}
}
}
// 动画结束后执行的代码
}
CATransaction.commit()
}
//================================================================================================
//=====LRUD(left right up & down buttons) touchPad touch =========================================
private func createAndShowLrudBall(at point: CGPoint) -> CAShapeLayer {
// Create a circular path using UIBezierPath
let path = UIBezierPath(arcCenter: point, radius: 10, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
// Create a CAShapeLayer
let ballLayer = CAShapeLayer()
ballLayer.path = path.cgPath // Assign the circular path to the shape layer
self.layer.superlayer?.addSublayer(ballLayer)
ballLayer.position = CGPointMake(CGRectGetMinX(self.frame), CGRectGetMinY(self.frame))
// Set the stroke color and width (border of the circle)
ballLayer.strokeColor = stickBallColor
ballLayer.lineWidth = 0
ballLayer.shadowOffset = CGSize(width: 0.5, height: 0.5)
ballLayer.shadowRadius = 0;
ballLayer.shadowOpacity = 0.8
// Set the fill color (inside of the circle)
ballLayer.fillColor = stickBallColor // Light fill with some transparency
return ballLayer
}
private func createLrudDirectionLayer() -> CAShapeLayer {
let indicatorFrame = CAShapeLayer();
let indicatorBorder = CAShapeLayer();
indicatorFrame.frame = CGRectMake(0, 0, 75, 75)
indicatorFrame.cornerRadius = 9
indicatorBorder.borderWidth = 6
indicatorBorder.frame = indicatorFrame.bounds.insetBy(dx: -indicatorBorder.borderWidth, dy: -indicatorBorder.borderWidth) // Adjust the inset as needed
indicatorBorder.borderColor = UIColor.clear.cgColor
indicatorBorder.cornerRadius = indicatorFrame.cornerRadius + indicatorBorder.borderWidth
indicatorBorder.backgroundColor = UIColor.clear.cgColor
indicatorBorder.fillColor = UIColor.clear.cgColor
let path = UIBezierPath(roundedRect: indicatorBorder.bounds, cornerRadius: indicatorBorder.cornerRadius)
indicatorBorder.path = path.cgPath
indicatorBorder.borderColor = moonlightPurple
return indicatorBorder
}
private func showLrudDirectionIndicator(with indicatorLayer:CAShapeLayer){
// Add the border layer below the super layer
self.layer.superlayer?.insertSublayer(indicatorLayer, below: self.layer)
// show the indicator based on the touchBeganLocation
indicatorLayer.position = CGPointMake(CGRectGetMinX(self.frame)+touchBeganLocation.x, CGRectGetMinY(self.frame)+touchBeganLocation.y)
}
private func handleLrudTouchMove(){
CATransaction.begin()
CATransaction.setDisableActions(true)
let triggeringAngle = 67.5
let radians = atan2(-offSetY,offSetX)
let degrees = radians * 180 / .pi
enum Direction: Int {
case right = 1
case up = 2
case left = 4
case down = 8
}
let nearZeroPoint = abs(offSetX) < 16/sensitivityFactor && abs(offSetY) < 16/sensitivityFactor
// NSLog("deltaX: %f, detalY: %f", deltaX, deltaY)
var buttonPressed = 0;
if abs(degrees) < triggeringAngle {
// NSLog("button pressed: right")
buttonPressed = buttonPressed | Direction.right.rawValue
}
if 180.0 - abs(degrees) < triggeringAngle {
// NSLog("button pressed: left")
buttonPressed = buttonPressed | Direction.left.rawValue
}
if abs(90.0 - degrees) < triggeringAngle {
// NSLog("button pressed: up")
buttonPressed = buttonPressed | Direction.up.rawValue
}
if abs(-90.0 - degrees) < triggeringAngle {
// NSLog("button pressed: down")
buttonPressed = buttonPressed | Direction.down.rawValue
}
if nearZeroPoint {buttonPressed = 0}
if(buttonPressed & Direction.up.rawValue == Direction.up.rawValue){
showLrudDirectionIndicator(with: upIndicator)
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["W"]!,Int8(KEY_ACTION_DOWN), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["UP_ARROW"]!,Int8(KEY_ACTION_DOWN), 0)
case "DPAD": self.onScreenControls.pressDownControllerButton(UP_FLAG)
default: break
}
}
else{
self.upIndicator.removeFromSuperlayer()
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["W"]!,Int8(KEY_ACTION_UP), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["UP_ARROW"]!,Int8(KEY_ACTION_UP), 0)
case "DPAD": self.onScreenControls.releaseControllerButton(UP_FLAG)
default: break
}
}
if(buttonPressed & Direction.down.rawValue == Direction.down.rawValue){
showLrudDirectionIndicator(with: downIndicator)
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["S"]!,Int8(KEY_ACTION_DOWN), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["DOWN_ARROW"]!,Int8(KEY_ACTION_DOWN), 0)
case "DPAD": self.onScreenControls.pressDownControllerButton(DOWN_FLAG)
default: break
}
}
else{
self.downIndicator.removeFromSuperlayer()
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["S"]!,Int8(KEY_ACTION_UP), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["DOWN_ARROW"]!,Int8(KEY_ACTION_UP), 0)
case "DPAD": self.onScreenControls.releaseControllerButton(DOWN_FLAG)
default: break
}
}
if(buttonPressed & Direction.left.rawValue == Direction.left.rawValue){
showLrudDirectionIndicator(with: leftIndicator)
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["A"]!,Int8(KEY_ACTION_DOWN), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["LEFT_ARROW"]!,Int8(KEY_ACTION_DOWN), 0)
case "DPAD": self.onScreenControls.pressDownControllerButton(LEFT_FLAG)
default: break
}
}
else{
self.leftIndicator.removeFromSuperlayer()
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["A"]!,Int8(KEY_ACTION_UP), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["LEFT_ARROW"]!,Int8(KEY_ACTION_UP), 0)
case "DPAD": self.onScreenControls.releaseControllerButton(LEFT_FLAG)
default: break
}
}
if(buttonPressed & Direction.right.rawValue == Direction.right.rawValue){
showLrudDirectionIndicator(with: rightIndicator)
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["D"]!,Int8(KEY_ACTION_DOWN), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["RIGHT_ARROW"]!,Int8(KEY_ACTION_DOWN), 0)
case "DPAD": self.onScreenControls.pressDownControllerButton(RIGHT_FLAG)
default: break
}
}
else{
self.rightIndicator.removeFromSuperlayer()
switch touchPadString {
case "WASDPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["D"]!,Int8(KEY_ACTION_UP), 0)
case "ARROWPAD": LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["RIGHT_ARROW"]!,Int8(KEY_ACTION_UP), 0)
case "DPAD": self.onScreenControls.releaseControllerButton(RIGHT_FLAG)
default: break
}
}
CATransaction.commit()
}
//================================================================================================
//===== MOUSEPAD related methods=============================================================
private func sendLongMouseLeftButtonClickEvent() {
DispatchQueue.global(qos: .userInteractive).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: .userInteractive).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: .userInteractive).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() {
// setupBorderLayer()
CATransaction.begin()
CATransaction.setDisableActions(true)
// self.layer.borderWidth = 0
buttonDownVisualEffectLayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)) // update position every time we press down the button
buttonDownVisualEffectLayer.borderWidth = self.buttonDownVisualEffectWidth // this will show the visual effect
buttonDownVisualEffectLayer.borderColor = moonlightPurple
CATransaction.commit()
}
private func buttonUpVisualEffect() {
CATransaction.begin()
CATransaction.setDisableActions(true)
// self.layer.borderWidth = 1
buttonDownVisualEffectLayer.borderWidth = 0
buttonDownVisualEffectLayer.borderColor = defaultBorderColor
CATransaction.commit()
}
private func setupButtonDownVisualEffectLayer() {
self.buttonDownVisualEffectWidth = 8
if self.shape == "round" {
if widthFactor < 1.3 {self.buttonDownVisualEffectWidth = 15.3} // wider visual effect for osc buttons
else {self.buttonDownVisualEffectWidth = 9}
}
// Set the frame to be larger than the view to expand outward
buttonDownVisualEffectLayer.borderWidth = 0 // set this 0 to hide the visual effect first
buttonDownVisualEffectLayer.frame = self.bounds.insetBy(dx: -self.buttonDownVisualEffectWidth, dy: -self.buttonDownVisualEffectWidth) // Adjust the inset as needed
buttonDownVisualEffectLayer.cornerRadius = self.layer.cornerRadius + self.buttonDownVisualEffectWidth
buttonDownVisualEffectLayer.backgroundColor = UIColor.clear.cgColor;
buttonDownVisualEffectLayer.fillColor = UIColor.clear.cgColor;
// Create a path for the border
let path = UIBezierPath(roundedRect: buttonDownVisualEffectLayer.bounds, cornerRadius: buttonDownVisualEffectLayer.cornerRadius)
buttonDownVisualEffectLayer.path = path.cgPath
// Add the border layer below the main view layer
self.layer.superlayer?.insertSublayer(buttonDownVisualEffectLayer, below: self.layer)
// Retrieve the current frame to account for transformations, this will update the coords for new position CGPointMake
buttonDownVisualEffectLayer.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
}
//==========================================================================================================
//=========================================send on screen controller stick events
private func touchInputToStickInput(input: CGFloat) -> CGFloat{
var target = stickMaxOffset * input / stickInputScale
if target > stickMaxOffset {target = stickMaxOffset}
if target < -stickMaxOffset {target = -stickMaxOffset}
return target
}
private func touchInputToStickBallCoord(input: CGFloat) -> CGFloat {
if input > stickInputScale {
return stickBallMaxOffset
}
if input < -stickInputScale {
return -stickBallMaxOffset
}
return input * (18/stickInputScale)
}
private func sendRightStickTouchPadEvent(inputX: CGFloat, inputY: CGFloat){
var targetX = self.touchInputToStickInput(input: inputX)
var targetY = -self.touchInputToStickInput(input: inputY)
// vertical input must be inverted
targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset)
targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset)
self.onScreenControls.sendRightStickTouchPadEvent(targetX, targetY)
}
private func sendLeftStickTouchPadEvent(inputX: CGFloat, inputY: CGFloat){
var targetX = self.touchInputToStickInput(input: inputX)
var targetY = -self.touchInputToStickInput(input: inputY)
targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset)
targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset)
self.onScreenControls.sendLeftStickTouchPadEvent(targetX, targetY)
}
//==========================================================================================================
private func sendOscButtonDownEvent(oscString: String){
let buttonFlag = CommandManager.oscButtonMappings[oscString]
if buttonFlag != 0 {self.onScreenControls.pressDownControllerButton(buttonFlag!)}
else {switch oscString {
case "OSCL2", "L2", "LT":
self.onScreenControls.updateLeftTrigger(0xFF)
case "OSCR2", "R2", "RT":
self.onScreenControls.updateRightTrigger(0xFF)
default:break
}}
}
private func sendOscButtonUpEvent(oscString: String){
let buttonFlag = CommandManager.oscButtonMappings[oscString]
if buttonFlag != 0 {self.onScreenControls.releaseControllerButton(buttonFlag!)}
else {switch oscString {
case "OSCL2", "L2", "LT":
self.onScreenControls.updateLeftTrigger(0x00)
case "OSCR2", "R2", "RT":
self.onScreenControls.updateRightTrigger(0x00)
default:break
}}
}
//==============================================================================
private func sendComboButtonsDownEvent(comboStrings: [String]) {
DispatchQueue.global(qos: .userInteractive).async {
for comboString in comboStrings {
if CommandManager.oscButtonMappings.keys.contains(comboString) {
self.sendOscButtonDownEvent(oscString: comboString)
}
if CommandManager.keyboardButtonMappings.keys.contains(comboString) {
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[comboString]!,Int8(KEY_ACTION_DOWN), 0)
}
if CommandManager.mouseButtonMappings.keys.contains(comboString) {
LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[comboString]!))
}
if comboString != comboStrings.last {
usleep(self.comboKeyTimeIntervalMs*1000) // delay xxx ms
}
}
}
}
private func sendComboButtonsUpEvent(comboStrings: [String]) {
DispatchQueue.global(qos: .userInteractive).async {
for comboString in comboStrings {
if CommandManager.oscButtonMappings.keys.contains(comboString) {
self.sendOscButtonUpEvent(oscString: comboString)
}
if CommandManager.keyboardButtonMappings.keys.contains(comboString) {
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[comboString]!,Int8(KEY_ACTION_UP), 0)
}
if CommandManager.mouseButtonMappings.keys.contains(comboString) {
LiSendMouseButtonEvent(CChar(BUTTON_ACTION_RELEASE), Int32(CommandManager.mouseButtonMappings[comboString]!))
}
if comboString != comboStrings.last {
usleep(self.comboKeyTimeIntervalMs*1000) // delay xxx ms
}
}
}
}
//==============================================================================
// Touch event handling
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchBegan = true
super.touchesBegan(touches, with: event)
self.isMultipleTouchEnabled = self.touchPadString == "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
quickDoubleTapDetected = touchTapTimeInterval < QUICK_TAP_TIME_INTERVAL
let touch = touches.first
if OnScreenWidgetView.editMode {self.touchBeganLocation = touch!.location(in: superview)}
else {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 widgetView, and excludes touches in other widgetViews
if allCapturedTouchesCount == 2 {
self.twoTouchesDetected = true
}
self.pressed = true
if !OnScreenWidgetView.editMode {
if self.widgetType == WidgetTypeEnum.touchPad && touches.count == 1{ // don't use event?.allTouches?.count here, it will counts all touches including the ones captured by other UIViews
switch self.touchPadString {
case "LSPAD":
self.crossMarkLayer.removeFromSuperlayer()
self.stickBallLayer.removeFromSuperlayer()
self.l3r3Indicator.removeFromSuperlayer()
self.showStickIndicator()
if quickDoubleTapDetected {
self.l3r3Indicator = self.createAndShowl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "RSPAD":
self.crossMarkLayer.removeFromSuperlayer()
self.stickBallLayer.removeFromSuperlayer()
self.l3r3Indicator.removeFromSuperlayer()
self.showStickIndicator()
if quickDoubleTapDetected {
self.l3r3Indicator = self.createAndShowl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "LSVPAD":
if quickDoubleTapDetected {
self.l3r3Indicator = self.createAndShowl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "RSVPAD":
if quickDoubleTapDetected {
self.l3r3Indicator = self.createAndShowl3r3Indicator()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)}
case "DPAD", "WASDPAD", "ARROWPAD":
self.lrudIndicatorBall = createAndShowLrudBall(at: touchBeganLocation)
if quickDoubleTapDetected {
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)
DispatchQueue.global(qos: .userInteractive).async {
usleep(100000)
self.sendComboButtonsUpEvent(comboStrings: self.comboButtonStrings)
}
}
default:
break
}
}
/*
if !self.buttonString.isEmpty {
// if there's no "+" in the keystring, treat it as a regular button:
if CommandManager.oscButtonMappings.keys.contains(self.cmdString) {
self.sendOscButtonDownEvent(keyString: self.cmdString)
}
if CommandManager.keyboardButtonMappings.keys.contains(self.cmdString) {
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings[self.cmdString]!,Int8(KEY_ACTION_DOWN), 0)
}
if CommandManager.mouseButtonMappings.keys.contains(self.cmdString) {
LiSendMouseButtonEvent(CChar(BUTTON_ACTION_PRESS), Int32(CommandManager.mouseButtonMappings[self.cmdString]!))
}
}
*/
// this will also deal with button events
if self.widgetType == WidgetTypeEnum.button && !self.comboButtonStrings.isEmpty {
self.buttonDownVisualEffect()
self.sendComboButtonsDownEvent(comboStrings: self.comboButtonStrings)
}
// legacy keyboard button combo connected by "+"
if self.cmdString.contains("+") && !self.cmdString.contains("-"){
let keyboardCmdStrings = CommandManager.shared.extractKeyStringsFromComboCommand(from: self.cmdString)!
CommandManager.shared.sendKeyComboCommand(keyboardCmdStrings: keyboardCmdStrings) // send multi-key command
DispatchQueue.main.asyncAfter(deadline: .now() + 0.03) { // reset shadow color immediately 50ms later
self.buttonUpVisualEffect()
}
}
}
// here is in edit mode:
else{
self.buttonDownVisualEffect()
NotificationCenter.default.post(name: Notification.Name("OnScreenWidgetViewSelected"),object: self) // inform layout tool controller to fetch button size factors. self will be passed as the object of the notification
}
}
private func moveByTouch(touch: UITouch){
let currentLocation: CGPoint
if OnScreenWidgetView.editMode {currentLocation = touch.location(in: superview)}
else {currentLocation = touch.location(in: self)}
let offsetX = currentLocation.x - latestTouchLocation.x
let offsetY = currentLocation.y - latestTouchLocation.y
center = CGPoint(x: center.x + offsetX, y: center.y + offsetY)
latestTouchLocation = currentLocation
// center = currentLocation;
//NSLog("x coord: %f, y coord: %f", self.frame.origin.x, self.frame.origin.y)
storedCenter = center // Update initial center for next movement
if OnScreenWidgetView.editMode {
NotificationCenter.default.post(name: Notification.Name("OnScreenWidgetMovedByTouch"), object:self) // inform the layoutOnScreenControl to update guideLines for this widget view
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesMoved(touches, with: event)
if !OnScreenWidgetView.editMode {
handleTouchPadMoveEvent(touches, with: event)
if CommandManager.specialOverlayButtonCmds.contains(self.cmdString){
if let touch = touches.first {
NSLog("touchTapTimeStamp %f", self.touchTapTimeStamp)
if CACurrentMediaTime() - self.touchTapTimeStamp > 0.3 { // temporarily relocate special buttons
self.moveByTouch(touch: touch)
self.buttonUpVisualEffect()
}
}
}
}
// Move the widgetView based on touch movement in relocation mode
if OnScreenWidgetView.editMode {
if let touch = touches.first {
self.moveByTouch(touch: touch)
}
self.stickBallLayer.removeFromSuperlayer()
self.crossMarkLayer.removeFromSuperlayer()
}
}
private func updateTouchLocation (touch: UITouch) {
self.mousePointerMoved = true
let currentTouchLocation: CGPoint = (touch.location(in: self))
self.deltaX = currentTouchLocation.x - self.latestTouchLocation.x
self.deltaY = currentTouchLocation.y - self.latestTouchLocation.y
self.offSetX = currentTouchLocation.x - self.touchBeganLocation.x
self.offSetY = currentTouchLocation.y - self.touchBeganLocation.y
self.latestTouchLocation = currentTouchLocation
}
private func handleTouchPadMoveEvent (_ touches: Set<UITouch>, with event: UIEvent?){
if touches.count == 1{ // don't use event.alltouches.count here, it will counts all touches
switch self.touchPadString{
case "MOUSEPAD":
DispatchQueue.global(qos: .userInteractive).async {
self.updateTouchLocation(touch: touches.first!)
LiSendMouseMoveEvent(Int16(truncatingIfNeeded: Int(self.deltaX * 1.7 * self.sensitivityFactor)), Int16(truncatingIfNeeded: Int(self.deltaY * 1.7 * self.sensitivityFactor)))
}
break
case "LSPAD":
self.updateTouchLocation(touch: touches.first!)
DispatchQueue.global(qos: .userInteractive).async {
self.sendLeftStickTouchPadEvent(inputX: self.offSetX * self.sensitivityFactor, inputY: self.offSetY * self.sensitivityFactor)
}
if widgetType == WidgetTypeEnum.touchPad {updateStickIndicator()}
case "RSPAD":
self.updateTouchLocation(touch: touches.first!)
DispatchQueue.global(qos: .userInteractive).async {
self.sendRightStickTouchPadEvent(inputX: self.offSetX * self.sensitivityFactor, inputY: self.offSetY * self.sensitivityFactor)
}
if widgetType == WidgetTypeEnum.touchPad {updateStickIndicator()}
case "LSVPAD":
DispatchQueue.global(qos: .userInteractive).async {
self.updateTouchLocation(touch: touches.first!)
self.sendLeftStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactor, inputY: self.deltaY*1.5167*self.sensitivityFactor)
}
case "RSVPAD":
DispatchQueue.global(qos: .userInteractive).async {
self.updateTouchLocation(touch: touches.first!)
self.sendRightStickTouchPadEvent(inputX: self.deltaX*1.5167*self.sensitivityFactor, inputY: self.deltaY*1.5167*self.sensitivityFactor)
}
case "DPAD", "WASDPAD", "ARROWPAD":
self.updateTouchLocation(touch: touches.first!)
handleLrudTouchMove()
default:
break
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchBegan = false
super.touchesEnded(touches, with: event)
// for checking stationary touch points
if self.touchPadString != "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 widgetView, and excludes touches in other widgetViews
// deal with pure MOUSPAD first
if !OnScreenWidgetView.editMode && self.widgetType == WidgetTypeEnum.touchPad && self.touchPadString == "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 !OnScreenWidgetView.editMode && self.widgetType == WidgetTypeEnum.touchPad && self.touchPadString == "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 or buttons with touchPad function
if !OnScreenWidgetView.editMode && !self.touchPadString.isEmpty {
switch self.touchPadString{
case "LSPAD":
self.onScreenControls.clearLeftStickTouchPadFlag()
if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndRemoveIndicator()}
case "RSPAD":
self.onScreenControls.clearRightStickTouchPadFlag()
if widgetType == WidgetTypeEnum.touchPad {self.resetStickBallPositionAndRemoveIndicator()}
case "LSVPAD":
self.onScreenControls.clearLeftStickTouchPadFlag()
case "RSVPAD":
self.onScreenControls.clearRightStickTouchPadFlag()
case "WASDPAD":
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["W"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["A"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["S"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["D"]!,Int8(KEY_ACTION_UP), 0)
case "ARROWPAD":
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["LEFT_ARROW"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["RIGHT_ARROW"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["UP_ARROW"]!,Int8(KEY_ACTION_UP), 0)
LiSendKeyboardEvent(CommandManager.keyboardButtonMappings["DOWN_ARROW"]!,Int8(KEY_ACTION_UP), 0)
case "DPAD":
self.onScreenControls.releaseControllerButton(LEFT_FLAG)
self.onScreenControls.releaseControllerButton(RIGHT_FLAG)
self.onScreenControls.releaseControllerButton(UP_FLAG)
self.onScreenControls.releaseControllerButton(DOWN_FLAG)
default:
break
}
}
self.l3r3Indicator.removeFromSuperlayer()
self.upIndicator.removeFromSuperlayer()
self.downIndicator.removeFromSuperlayer()
self.leftIndicator.removeFromSuperlayer()
self.rightIndicator.removeFromSuperlayer()
self.lrudIndicatorBall.removeFromSuperlayer()
if !OnScreenWidgetView.editMode && !self.cmdString.contains("+") && !self.comboButtonStrings.isEmpty { // if the command(keystring contains "+", it's a legacy multi-key command
self.sendComboButtonsUpEvent(comboStrings: self.comboButtonStrings)
}
if !OnScreenWidgetView.editMode && CommandManager.specialOverlayButtonCmds.contains(self.cmdString){
if CACurrentMediaTime() - self.touchTapTimeStamp < 0.3 {
switch self.cmdString {
case "SETTINGS":
NotificationCenter.default.post(name: Notification.Name("SettingsOverlayButtonPressedNotification"), object:nil) // inform layout tool controller to fetch button size factors. self will be passed as the object of the notification
default:
break
}
}
}
if OnScreenWidgetView.editMode {
guard let superview = superview else { return }
// Deactivate existing constraints if necessary
NSLayoutConstraint.deactivate(self.constraints)
// Add new constraints based on the current center position
translatesAutoresizingMaskIntoConstraints = true
// Create new constraints
let newLeadingConstraint = self.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: self.frame.origin.x)
let newTopConstraint = self.topAnchor.constraint(equalTo: superview.topAnchor, constant: self.frame.origin.y)
// Activate the new location constraints
NSLayoutConstraint.activate([newLeadingConstraint, newTopConstraint])
// Trigger layout update
superview.layoutIfNeeded()
setupView(); //re-setup widgetView style
self.crossMarkLayer.removeFromSuperlayer()
self.stickBallLayer.removeFromSuperlayer()
if self.widgetType == WidgetTypeEnum.touchPad{
switch self.touchPadString{
case "LSPAD", "RSPAD":
self.showStickIndicator()
self.updateStickIndicator()
default: break
}
}
}
self.buttonUpVisualEffect()
}
}