- genshin touch control enhancement preview for settings menu
- controller navigation & first connection tips - enhancedTouchPreview bug fix
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
2AC1E3832F507D80004F76C0 /* GenericUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AC1E3822F507D80004F76C0 /* GenericUtils.swift */; };
|
||||
2AC1EAFB2F92260F004EDA80 /* UIStackExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AC1EAFA2F922602004EDA80 /* UIStackExtension.swift */; };
|
||||
2AC1EAFC2F922889004EDA80 /* UIStackExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AC1EAFA2F922602004EDA80 /* UIStackExtension.swift */; };
|
||||
2B7A10022FFB000100000001 /* SettingsTouchVelocityPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B7A10012FFB000100000001 /* SettingsTouchVelocityPreview.swift */; };
|
||||
3A4A3A112F0C000100000001 /* ProfileSelectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4A3A102F0C000100000001 /* ProfileSelectorViewController.swift */; };
|
||||
3A4A3A132F0C000100000003 /* LayoutOnScreenControlsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4A3A122F0C000100000002 /* LayoutOnScreenControlsViewController.swift */; };
|
||||
840751B32BF49C050096B657 /* CustomTapGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 840751B22BF49C050096B657 /* CustomTapGestureRecognizer.m */; };
|
||||
@@ -218,6 +219,7 @@
|
||||
2A7114002F5A6C65001D852D /* GamepadNavigationIllustrationHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamepadNavigationIllustrationHud.swift; sourceTree = "<group>"; };
|
||||
2AC1E3822F507D80004F76C0 /* GenericUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericUtils.swift; sourceTree = "<group>"; };
|
||||
2AC1EAFA2F922602004EDA80 /* UIStackExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackExtension.swift; sourceTree = "<group>"; };
|
||||
2B7A10012FFB000100000001 /* SettingsTouchVelocityPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTouchVelocityPreview.swift; sourceTree = "<group>"; };
|
||||
3A4A3A102F0C000100000001 /* ProfileSelectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSelectorViewController.swift; sourceTree = "<group>"; };
|
||||
3A4A3A122F0C000100000002 /* LayoutOnScreenControlsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutOnScreenControlsViewController.swift; sourceTree = "<group>"; };
|
||||
693B3A9A218638CD00982F7B /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||
@@ -864,6 +866,7 @@
|
||||
FB89462719F646E200339C8A /* StreamFrameViewController.m */,
|
||||
FBD3494E19FF2174002D2A60 /* SettingsViewController.h */,
|
||||
FBD3494F19FF2174002D2A60 /* SettingsViewController.m */,
|
||||
2B7A10012FFB000100000001 /* SettingsTouchVelocityPreview.swift */,
|
||||
2AC1EAFA2F922602004EDA80 /* UIStackExtension.swift */,
|
||||
FB4A23B61A9D3637004D2EF2 /* LoadingFrameViewController.h */,
|
||||
FB4A23B71A9D3637004D2EF2 /* LoadingFrameViewController.m */,
|
||||
@@ -1188,6 +1191,7 @@
|
||||
97E20DEA2E216532000D5DCA /* SceneDelegate.m in Sources */,
|
||||
1817B61E2E6FD04D000D938D /* SafeTimer.swift in Sources */,
|
||||
FBD3495019FF2174002D2A60 /* SettingsViewController.m in Sources */,
|
||||
2B7A10022FFB000100000001 /* SettingsTouchVelocityPreview.swift in Sources */,
|
||||
843CC7F02C1E757D00BBC475 /* NativeTouchPointer.m in Sources */,
|
||||
2A7113FD2F5A6C65001D852D /* ThemeManager.swift in Sources */,
|
||||
FB89462C19F646E200339C8A /* HttpManager.m in Sources */,
|
||||
|
||||
@@ -338,6 +338,7 @@ final class ControllerNavigator: NSObject {
|
||||
}
|
||||
|
||||
static func updateUINavigationDelegateState() {
|
||||
ControllerUtil.disableSysGestures(ControllerUtil.primaryGCController)
|
||||
if uiNavigationDelegate is StreamFrameViewController {
|
||||
if radialMenuState == .mouseModeEnabled {
|
||||
startControllerMouse()
|
||||
@@ -2273,7 +2274,8 @@ extension ControllerCollectionNavigationDelegate {
|
||||
|
||||
extension MainFrameViewController {
|
||||
func forceLaunchApp(_ app: TemporaryApp?){
|
||||
if let currentRunningApp = self.findRunningApp(app?.host) {
|
||||
guard let app = app else { return }
|
||||
if let currentRunningApp = self.findRunningApp(app.host) {
|
||||
if currentRunningApp !== app {
|
||||
self.quitRunningAppAndStart(app)
|
||||
}
|
||||
|
||||
@@ -466,6 +466,18 @@ import Foundation
|
||||
|
||||
guard GCController.controllers().count == 1 else { return }
|
||||
|
||||
if let mainFrameVC = delegate as? MainFrameViewController {
|
||||
let vc = mainFrameVC.isStreaming() ? StreamFrameViewController.sharedInstance() : mainFrameVC
|
||||
GenericUtils.handleFirstGamepadConnection(in: vc) {
|
||||
setGCControllerToPrimary(controller)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setGCControllerToPrimary(controller)
|
||||
}
|
||||
|
||||
private static func setGCControllerToPrimary(_ controller: GCController) {
|
||||
primaryGCController = controller
|
||||
_ = buildMapping(for: controller, swapABXY: false)
|
||||
disableSysGestures(controller)
|
||||
|
||||
@@ -536,6 +536,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"%.0f%% velocity" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%.0f%% velocity"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%.0f%% 移速缩放"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%.0f%% 移速縮放"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%.0f%% 移速縮放"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ is currently running" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -6269,6 +6298,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"controllerNavigationTip" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "VoidLink now supports global controller navigation! Go to [Settings Menu] → [Controller] section → turn [Controller Navigation] off and on again to customize radial menu buttons and controller mouse mode."
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "VoidLink已支持全局手柄导航!前往 [设置菜单] → [手柄] 分区 → 关闭再打开 [手柄导航],即可自定义环形菜单按键与手柄鼠标模式。"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "VoidLink 已支援全域手把導航!前往 [設定選單] → [手把] 分區 → 關閉再開啟 [手把導航],即可自訂環形選單按鍵與手把滑鼠模式。"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "VoidLink 已支援全域手把導航!前往 [設定選單] → [手把] 分區 → 關閉再開啟 [手把導航],即可自訂環形選單按鍵與手把滑鼠模式。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Create" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -6678,6 +6736,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"descendants" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "descendants"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "衍生版"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "衍生版"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "衍生版"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Detailed Tutorial" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -7750,6 +7837,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enhanced Touch Control for Genshin Game Streaming" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Enhanced Touch Control for Genshin Game Streaming"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "原神串流增强触控"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "原神串流增強觸控"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "原神串流增強觸控"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Enter a new command and alias" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -12922,6 +13038,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Originally designed & coded by True Zhuanjia with pride" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Originally designed & coded by True Zhuanjia with pride"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "由 True砖家@Bilibili 自豪地原创设计并编码"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "由 True砖家@Bilibili 自豪地原創設計並編碼"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "由 True砖家@Bilibili 自豪地原創設計並編碼"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Others" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -13083,6 +13228,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"passthrough" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "passthrough"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "坐标透传"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "座標透傳"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "座標透傳"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Pen Pressure Curve" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -17252,6 +17426,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tap to learn more" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "点击了解更多"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "點擊了解更多"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "點擊了解更多"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tap to toggle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -19046,4 +19243,4 @@
|
||||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +442,34 @@ import UIKit
|
||||
return false
|
||||
}
|
||||
|
||||
@objc public static func isFirstConnectingGamepad() -> Bool {
|
||||
let key = "isFirstConnectingGamepad20260730"
|
||||
let defaults = UserDefaults.standard
|
||||
let launchedBefore = defaults.bool(forKey: key)
|
||||
if !launchedBefore {
|
||||
defaults.set(true, forKey: key)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@objc public static func handleFirstGamepadConnection(in vc: UIViewController?, handler: @escaping () -> Void) {
|
||||
if isFirstConnectingGamepad() {
|
||||
AlertControllerUtil.showAlert(
|
||||
in: vc,
|
||||
title: "Tips".localized,
|
||||
message: "controllerNavigationTip".localized,
|
||||
withCancel: false,
|
||||
buttonTitle: "Got it!".localized,
|
||||
countdown: 6,
|
||||
completion: {
|
||||
handler()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@objc public static func isFirstStreamingOnMac() -> Bool {
|
||||
if !PublicUtils.isRunningOnMacAsiPadApp {return false}
|
||||
let key = "hasStreamedOnMac"
|
||||
|
||||
@@ -10,9 +10,9 @@ import UIKit
|
||||
|
||||
extension UIViewController {
|
||||
var hasNoPresentedVC: Bool {
|
||||
let hasPresentedVC = self.presentedViewController == nil && self.view.window != nil
|
||||
let hasNoPresentedVC = self.presentedViewController == nil && self.view.window != nil
|
||||
// let presentedVC = self.presentedViewController
|
||||
return hasPresentedVC
|
||||
return hasNoPresentedVC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -99,6 +99,9 @@ private final class ProfileCollectionViewCell: UICollectionViewCell, ControllerN
|
||||
}
|
||||
}
|
||||
|
||||
override var canBecomeFocused: Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@objc(FileOperation)
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
//
|
||||
// SettingsTouchVelocityPreview.swift
|
||||
// VoidLink
|
||||
//
|
||||
// Created by True砖家 on 2026/7/30.
|
||||
// Copyright © 2026 True砖家 on Bilibili. All rights reserved.
|
||||
//
|
||||
|
||||
import ObjectiveC
|
||||
import UIKit
|
||||
|
||||
private var touchVelocityPreviewViewKey: UInt8 = 0
|
||||
private var touchVelocityPreviewDismissWorkItemKey: UInt8 = 0
|
||||
private let touchVelocityPreviewScreenAspectRatio: CGFloat = 1.3333
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private final class TouchVelocityPreviewView: UIView {
|
||||
static let horizontalScreenInset: CGFloat = 16
|
||||
static let topPadding: CGFloat = 7
|
||||
static let titleHeight: CGFloat = 22
|
||||
static let titleScreenGap: CGFloat = 2
|
||||
static let footerGap: CGFloat = 6
|
||||
static let footerHeight: CGFloat = 40
|
||||
static let bottomPadding: CGFloat = 2
|
||||
static let heightExtra = topPadding
|
||||
+ titleHeight
|
||||
+ titleScreenGap
|
||||
+ footerGap
|
||||
+ footerHeight
|
||||
+ bottomPadding
|
||||
- horizontalScreenInset * 2 / touchVelocityPreviewScreenAspectRatio
|
||||
static let preferredWidth: CGFloat = PublicUtils.isIPhone ? 280 : 360
|
||||
static let preferredHeight = preferredWidth / touchVelocityPreviewScreenAspectRatio + heightExtra
|
||||
|
||||
var dividerPercent: CGFloat = 50 {
|
||||
didSet { setNeedsDisplay() }
|
||||
}
|
||||
|
||||
var velocityPercent: CGFloat = 100 {
|
||||
didSet { setNeedsDisplay() }
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
isOpaque = false
|
||||
isUserInteractionEnabled = true
|
||||
layer.cornerRadius = 14
|
||||
layer.cornerCurve = .continuous
|
||||
layer.masksToBounds = true
|
||||
layer.borderWidth = 1
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(openUrl))
|
||||
addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
@objc private func openUrl() {
|
||||
PublicUtils.openUrl("https://www.bilibili.com/video/BV1A1421r7qA")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
let isDark = ThemeManager.userInterfaceStyle() == .dark
|
||||
let backgroundColor = isDark
|
||||
? UIColor(white: 0.07, alpha: 0.9)
|
||||
: UIColor.white.withAlphaComponent(0.94)
|
||||
let borderColor = ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.34 : 0.24)
|
||||
let lineColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.32 : 0.22)
|
||||
let passthroughColor = UIColor.systemTeal.withAlphaComponent(isDark ? 0.22 : 0.16)
|
||||
let velocityColor = UIColor.systemIndigo.withAlphaComponent(isDark ? 0.20 : 0.13)
|
||||
let controlColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.82 : 0.66)
|
||||
let accentColor = ThemeManager.appPrimaryColor
|
||||
let secondaryAccentColor = UIColor(red: 0.18, green: 0.82, blue: 0.95, alpha: 1)
|
||||
layer.borderColor = borderColor.cgColor
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
let titleRect = CGRect(
|
||||
x: 12,
|
||||
y: Self.topPadding,
|
||||
width: bounds.width - 24,
|
||||
height: Self.titleHeight
|
||||
)
|
||||
drawPreviewTitle(in: titleRect, textColor: ThemeManager.textColor)
|
||||
|
||||
let screenTop = titleRect.maxY + Self.titleScreenGap
|
||||
let availableScreenHeight = max(bounds.height - screenTop - Self.footerGap - Self.footerHeight - Self.bottomPadding, 1)
|
||||
let availableScreenWidth = bounds.width - Self.horizontalScreenInset * 2
|
||||
let screenWidth = min(availableScreenWidth, availableScreenHeight * touchVelocityPreviewScreenAspectRatio)
|
||||
let screenHeight = screenWidth / touchVelocityPreviewScreenAspectRatio
|
||||
let screenRect = CGRect(
|
||||
x: bounds.midX - screenWidth / 2,
|
||||
y: screenTop,
|
||||
width: screenWidth,
|
||||
height: screenHeight
|
||||
)
|
||||
let screenPath = UIBezierPath(roundedRect: screenRect, cornerRadius: 10)
|
||||
context.saveGState()
|
||||
screenPath.addClip()
|
||||
|
||||
let dividerX = screenRect.minX + min(max(dividerPercent, 0), 100) / 100 * screenRect.width
|
||||
context.setFillColor(passthroughColor.cgColor)
|
||||
context.fill(CGRect(x: screenRect.minX, y: screenRect.minY, width: dividerX - screenRect.minX, height: screenRect.height))
|
||||
context.setFillColor(velocityColor.cgColor)
|
||||
context.fill(CGRect(x: dividerX, y: screenRect.minY, width: screenRect.maxX - dividerX, height: screenRect.height))
|
||||
|
||||
drawDirectionWheel(in: screenRect, color: controlColor, accentColor: secondaryAccentColor)
|
||||
drawCharacterSelector(in: screenRect, color: controlColor, accentColor: accentColor)
|
||||
drawActionButtons(in: screenRect, color: controlColor, accentColor: accentColor)
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
screenPath.lineWidth = 1
|
||||
lineColor.setStroke()
|
||||
screenPath.stroke()
|
||||
|
||||
context.saveGState()
|
||||
context.setLineDash(phase: 0, lengths: [6, 5])
|
||||
context.setStrokeColor(accentColor.withAlphaComponent(0.9).cgColor)
|
||||
context.setLineWidth(2)
|
||||
context.move(to: CGPoint(x: dividerX, y: screenRect.minY + 2))
|
||||
context.addLine(to: CGPoint(x: dividerX, y: screenRect.maxY - 2))
|
||||
context.strokePath()
|
||||
context.restoreGState()
|
||||
|
||||
drawRegionLabels(screenRect: screenRect, dividerX: dividerX, textColor: ThemeManager.textColor)
|
||||
drawPreviewFooter(in: CGRect(x: 12, y: screenRect.maxY + Self.footerGap, width: bounds.width - 24, height: Self.footerHeight), textColor: ThemeManager.textColor)
|
||||
}
|
||||
|
||||
private func drawPreviewTitle(in rect: CGRect, textColor: UIColor) {
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = .center
|
||||
paragraph.lineBreakMode = .byTruncatingTail
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.systemFont(ofSize: 14.5, weight: .semibold),
|
||||
.foregroundColor: textColor.withAlphaComponent(0.88),
|
||||
.paragraphStyle: paragraph
|
||||
]
|
||||
("Enhanced Touch Control for Genshin Game Streaming".localized as NSString).draw(in: rect, withAttributes: attributes)
|
||||
}
|
||||
|
||||
private func drawPreviewFooter(in rect: CGRect, textColor: UIColor) {
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = .center
|
||||
paragraph.lineBreakMode = .byTruncatingTail
|
||||
|
||||
let firstLineAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.systemFont(ofSize: 13, weight: .semibold),
|
||||
.foregroundColor: textColor.withAlphaComponent(0.82),
|
||||
.paragraphStyle: paragraph
|
||||
]
|
||||
("Originally designed & coded by True Zhuanjia with pride".localized as NSString).draw(
|
||||
in: CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: 20),
|
||||
withAttributes: firstLineAttributes
|
||||
)
|
||||
|
||||
let secondLine = "Tap to learn more".localized
|
||||
let secondLineAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.systemFont(ofSize: 13, weight: .semibold),
|
||||
.foregroundColor: textColor.withAlphaComponent(0.82),
|
||||
.paragraphStyle: paragraph
|
||||
]
|
||||
(secondLine as NSString).draw(
|
||||
in: CGRect(x: rect.minX, y: rect.minY + 20, width: rect.width, height: 20),
|
||||
withAttributes: secondLineAttributes
|
||||
)
|
||||
}
|
||||
|
||||
private func drawRegionLabels(screenRect: CGRect, dividerX: CGFloat, textColor: UIColor) {
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = .center
|
||||
paragraph.lineBreakMode = .byTruncatingTail
|
||||
let attributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.systemFont(ofSize: 13, weight: .semibold),
|
||||
.foregroundColor: textColor.withAlphaComponent(0.82),
|
||||
.paragraphStyle: paragraph
|
||||
]
|
||||
|
||||
let leftRect = CGRect(
|
||||
x: screenRect.minX + 8,
|
||||
y: screenRect.minY + 10,
|
||||
width: max(dividerX - screenRect.minX - 16, 0),
|
||||
height: 18
|
||||
)
|
||||
let rightRect = CGRect(
|
||||
x: dividerX + 8,
|
||||
y: screenRect.minY + 10,
|
||||
width: max(screenRect.maxX - dividerX - 16, 0),
|
||||
height: 18
|
||||
)
|
||||
|
||||
if leftRect.width >= 72 {
|
||||
("passthrough".localized as NSString).draw(in: leftRect, withAttributes: attributes)
|
||||
}
|
||||
if rightRect.width >= 80 {
|
||||
let velocityText = LocalizationHelper.localizedString(forKey: "%.0f%% velocity", velocityPercent)
|
||||
(velocityText as NSString).draw(in: rightRect, withAttributes: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private func drawDirectionWheel(in rect: CGRect, color: UIColor, accentColor: UIColor) {
|
||||
let base = min(rect.width, rect.height)
|
||||
let center = CGPoint(x: rect.minX + rect.width * 0.18, y: rect.maxY - base * 0.20)
|
||||
let radius = base * 0.13
|
||||
color.withAlphaComponent(0.18).setStroke()
|
||||
let outer = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
|
||||
outer.lineWidth = 2
|
||||
outer.stroke()
|
||||
|
||||
accentColor.withAlphaComponent(0.9).setStroke()
|
||||
let arc = UIBezierPath(arcCenter: center, radius: radius * 0.72, startAngle: -.pi * 0.8, endAngle: -.pi * 0.18, clockwise: true)
|
||||
arc.lineWidth = 5
|
||||
arc.lineCapStyle = .round
|
||||
arc.stroke()
|
||||
|
||||
color.withAlphaComponent(0.52).setStroke()
|
||||
let hub = UIBezierPath(ovalIn: CGRect(x: center.x - radius * 0.17, y: center.y - radius * 0.17, width: radius * 0.34, height: radius * 0.34))
|
||||
hub.lineWidth = 2
|
||||
hub.stroke()
|
||||
}
|
||||
|
||||
private func drawCharacterSelector(in rect: CGRect, color: UIColor, accentColor: UIColor) {
|
||||
let rowWidth = rect.width * 0.14
|
||||
let rowHeight = rect.height * 0.07
|
||||
let startX = rect.maxX - rowWidth - rect.width * 0.05
|
||||
let startY = rect.minY + rect.height * 0.22
|
||||
|
||||
for index in 0..<3 {
|
||||
let y = startY + CGFloat(index) * rowHeight * 1.42
|
||||
let lineRect = CGRect(x: startX, y: y + rowHeight * 0.36, width: rowWidth * 0.6, height: rowHeight * 0.18)
|
||||
let line = UIBezierPath(roundedRect: lineRect, cornerRadius: lineRect.height / 2)
|
||||
color.withAlphaComponent(0.34).setFill()
|
||||
line.fill()
|
||||
|
||||
let portraitRect = CGRect(x: startX + rowWidth * 0.68, y: y, width: rowHeight, height: rowHeight)
|
||||
color.withAlphaComponent(0.38).setStroke()
|
||||
let portrait = UIBezierPath(ovalIn: portraitRect)
|
||||
portrait.lineWidth = 2
|
||||
portrait.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
private func drawActionButtons(in rect: CGRect, color: UIColor, accentColor: UIColor) {
|
||||
let base = rect.width
|
||||
let center = CGPoint(x: rect.maxX - base * 0.19, y: rect.maxY - base * 0.16)
|
||||
let buttons: [(CGPoint, CGFloat)] = [
|
||||
(center, base * 0.055),
|
||||
(CGPoint(x: center.x + base * 0.11, y: center.y - base * 0.077), base * 0.05),
|
||||
(CGPoint(x: center.x + base * 0.11, y: center.y + base * 0.068), base * 0.05),
|
||||
(CGPoint(x: center.x - base * 0.11, y: center.y + base * 0.068), base * 0.05)
|
||||
]
|
||||
|
||||
for (index, button) in buttons.enumerated() {
|
||||
let rect = CGRect(x: button.0.x - button.1, y: button.0.y - button.1, width: button.1 * 2, height: button.1 * 2)
|
||||
let path = UIBezierPath(ovalIn: rect)
|
||||
color.withAlphaComponent(index == 0 ? 0.44 : 0.32).setStroke()
|
||||
path.lineWidth = index == 0 ? 3 : 2
|
||||
path.stroke()
|
||||
|
||||
let iconRect = rect.insetBy(dx: button.1 * 0.42, dy: button.1 * 0.42)
|
||||
let icon = UIBezierPath(ovalIn: iconRect)
|
||||
(index == 0 ? accentColor : color).withAlphaComponent(index == 0 ? 0.78 : 0.55).setFill()
|
||||
icon.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension SettingsViewController {
|
||||
private var touchVelocityPreviewView: TouchVelocityPreviewView? {
|
||||
get {
|
||||
objc_getAssociatedObject(self, &touchVelocityPreviewViewKey) as? TouchVelocityPreviewView
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &touchVelocityPreviewViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
private var touchVelocityPreviewDismissWorkItem: DispatchWorkItem? {
|
||||
get {
|
||||
objc_getAssociatedObject(self, &touchVelocityPreviewDismissWorkItemKey) as? DispatchWorkItem
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &touchVelocityPreviewDismissWorkItemKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
@objc(showTouchVelocityPreviewWithDividerPercent:velocityPercent:)
|
||||
func showTouchVelocityPreview(dividerPercent: CGFloat, velocityPercent: CGFloat) {
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
self?.showTouchVelocityPreviewOnMain(dividerPercent: dividerPercent, velocityPercent: velocityPercent)
|
||||
}
|
||||
}
|
||||
|
||||
private func showTouchVelocityPreviewOnMain(dividerPercent: CGFloat, velocityPercent: CGFloat) {
|
||||
let previewView = touchVelocityPreviewView ?? TouchVelocityPreviewView()
|
||||
touchVelocityPreviewView = previewView
|
||||
previewView.dividerPercent = dividerPercent
|
||||
previewView.velocityPercent = velocityPercent
|
||||
previewView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
if previewView.superview == nil {
|
||||
previewView.alpha = 0
|
||||
guard let containerView = view.window ?? view else { return }
|
||||
containerView.addSubview(previewView)
|
||||
let containerSafeArea = containerView.safeAreaLayoutGuide
|
||||
var contraints = [
|
||||
previewView.centerYAnchor.constraint(equalTo: containerSafeArea.centerYAnchor),
|
||||
previewView.widthAnchor.constraint(equalToConstant: TouchVelocityPreviewView.preferredWidth),
|
||||
previewView.heightAnchor.constraint(equalToConstant: TouchVelocityPreviewView.preferredHeight)
|
||||
]
|
||||
|
||||
if PublicUtils.isIPhone {
|
||||
contraints.append(previewView.trailingAnchor.constraint(equalTo: containerSafeArea.trailingAnchor, constant: previewView.bounds.width-50))
|
||||
}
|
||||
else {
|
||||
contraints.append(previewView.centerXAnchor.constraint(equalTo: containerSafeArea.centerXAnchor, constant: 80))
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate(contraints)
|
||||
}
|
||||
|
||||
previewView.superview?.bringSubviewToFront(previewView)
|
||||
UIView.animate(withDuration: 0.12) {
|
||||
previewView.alpha = 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc(cancelTouchVelocityPreviewDismiss)
|
||||
func cancelTouchVelocityPreviewDismiss() {
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
self?.touchVelocityPreviewDismissWorkItem?.cancel()
|
||||
self?.touchVelocityPreviewDismissWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
@objc(scheduleTouchVelocityPreviewDismiss)
|
||||
func scheduleTouchVelocityPreviewDismiss() {
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
self?.scheduleTouchVelocityPreviewDismissOnMain()
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleTouchVelocityPreviewDismissOnMain() {
|
||||
guard let previewView = touchVelocityPreviewView else { return }
|
||||
touchVelocityPreviewDismissWorkItem?.cancel()
|
||||
let workItem = DispatchWorkItem { [weak self, weak previewView] in
|
||||
UIView.animate(withDuration: 0.18, animations: {
|
||||
previewView?.alpha = 0
|
||||
}, completion: { _ in
|
||||
previewView?.removeFromSuperview()
|
||||
if self?.touchVelocityPreviewView === previewView {
|
||||
self?.touchVelocityPreviewView = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
touchVelocityPreviewDismissWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: workItem)
|
||||
}
|
||||
}
|
||||
@@ -2411,9 +2411,17 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
// pointer veloc setting, will be enable/disabled by touchMode
|
||||
[self.pointerVelocityModeDividerSlider addTarget:self action:@selector(pointerVelocityModeDividerSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self.pointerVelocityModeDividerSlider addTarget:self action:@selector(cancelTouchVelocityPreviewDismiss) forControlEvents:UIControlEventTouchDown];
|
||||
[self.pointerVelocityModeDividerSlider addTarget:self action:@selector(scheduleTouchVelocityPreviewDismiss) forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchUpOutside | UIControlEventTouchCancel)];
|
||||
}
|
||||
|
||||
// init pointer veloc setting, will be enable/disabled by touchMode
|
||||
[self.touchPointerVelocityFactorSlider addTarget:self action:@selector(touchPointerVelocityFactorSliderMoved:) forControlEvents:(UIControlEventValueChanged)]; // Update label display when slider is being moved.
|
||||
if (@available(iOS 13.0, *)) {
|
||||
[self.touchPointerVelocityFactorSlider addTarget:self action:@selector(cancelTouchVelocityPreviewDismiss) forControlEvents:UIControlEventTouchDown];
|
||||
[self.touchPointerVelocityFactorSlider addTarget:self action:@selector(scheduleTouchVelocityPreviewDismiss) forControlEvents:(UIControlEventTouchUpInside | UIControlEventTouchUpOutside | UIControlEventTouchCancel)];
|
||||
}
|
||||
|
||||
// async native touch event
|
||||
// self.asyncNativeTouchPrioritySelector.selectedSegmentIndex = currentSettings.asyncNativeTouchPriority.intValue; // load old setting of asyncNativeTouchPriority
|
||||
@@ -3264,10 +3272,20 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
- (void) pointerVelocityModeDividerSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" | %d%% | %d%% | ", (uint8_t)sender.value, 100-(uint8_t)sender.value];
|
||||
bool hasNoPresentedVC = self.presentedViewController == nil && self.view.window != nil;
|
||||
if (@available(iOS 13.0, *)) if(!settingsViewJustExpanded && hasNoPresentedVC) {
|
||||
[self showTouchVelocityPreviewWithDividerPercent:sender.value
|
||||
velocityPercent:[self map_velocFactorDisplay_fromSliderValue:self.touchPointerVelocityFactorSlider.value]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) touchPointerVelocityFactorSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %d%% ", (uint16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]]; // Update label display
|
||||
bool hasNoPresentedVC = self.presentedViewController == nil && self.view.window != nil;
|
||||
if (@available(iOS 13.0, *)) if(!settingsViewJustExpanded && hasNoPresentedVC) {
|
||||
[self showTouchVelocityPreviewWithDividerPercent:self.pointerVelocityModeDividerSlider.value
|
||||
velocityPercent:[self map_velocFactorDisplay_fromSliderValue:sender.value]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) gyroSensitivitySliderMoved:(UISlider* )sender {
|
||||
|
||||
@@ -93,6 +93,7 @@ import UIKit
|
||||
private var controllerNavigationExecuteDismissWindowOpen = false
|
||||
private var controllerNavigationExecuteDismissToken = 0
|
||||
private var controllerNavigationExecuteSuppressNextRelease = false
|
||||
private var pendingSpecialEntryToken = 0
|
||||
private var isEditingMode = false {
|
||||
didSet {
|
||||
if !isEditingMode {
|
||||
@@ -643,10 +644,18 @@ import UIKit
|
||||
|
||||
private func activateEntry(at indexPath: IndexPath, dismissAfterAction: Bool = true) {
|
||||
guard entries.indices.contains(indexPath.item) else { return }
|
||||
let entryIdentifier = entries[indexPath.item].orderIdentifier
|
||||
activateEntry(withIdentifier: entryIdentifier, fallbackIndexPath: indexPath, dismissAfterAction: dismissAfterAction)
|
||||
}
|
||||
|
||||
switch entries[indexPath.item] {
|
||||
private func activateEntry(withIdentifier identifier: String, fallbackIndexPath: IndexPath? = nil, dismissAfterAction: Bool = true) {
|
||||
guard let item = entries.firstIndex(where: { $0.orderIdentifier == identifier }) else { return }
|
||||
let indexPath = IndexPath(item: item, section: 0)
|
||||
pendingSpecialEntryToken += 1
|
||||
|
||||
switch entries[item] {
|
||||
case .special(let id, _):
|
||||
collectionView.deselectItem(at: indexPath, animated: false)
|
||||
collectionView.deselectItem(at: fallbackIndexPath ?? indexPath, animated: false)
|
||||
if !isEditingMode {
|
||||
flashCell(at: indexPath)
|
||||
handleSpecialEntry(id: id)
|
||||
@@ -670,6 +679,7 @@ import UIKit
|
||||
sendKeyboardCommand(command)
|
||||
if dismissAfterAction && !viewPinned {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
guard self.presentingViewController != nil else { return }
|
||||
self.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
@@ -848,7 +858,11 @@ import UIKit
|
||||
}
|
||||
|
||||
private func handleSpecialEntry(id: String) {
|
||||
pendingSpecialEntryToken += 1
|
||||
let token = pendingSpecialEntryToken
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
|
||||
guard self.pendingSpecialEntryToken == token else { return }
|
||||
self.dismiss(animated: false)
|
||||
|
||||
switch id {
|
||||
@@ -1074,7 +1088,7 @@ extension ToolboxViewController: ControllerCollectionNavigationDelegate {
|
||||
controllerNavigationDeleteDoublePressToken += 1
|
||||
let token = controllerNavigationDeleteDoublePressToken
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
guard let self,
|
||||
self.controllerNavigationDeleteDoublePressToken == token else {
|
||||
return
|
||||
@@ -1383,7 +1397,7 @@ private final class ToolboxCardCell: UICollectionViewCell, ControllerNavigationH
|
||||
}
|
||||
|
||||
override var canBecomeFocused: Bool {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
var controllerNavigationHighlightTargetView: UIView {
|
||||
|
||||
Reference in New Issue
Block a user