controller navigation improvements and fixes
This commit is contained in:
@@ -19,7 +19,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
private struct Hint {
|
||||
let control: ControllerElement
|
||||
let keyContent: KeyContent
|
||||
let title: String
|
||||
let titles: [String]
|
||||
let isInAction: Bool
|
||||
}
|
||||
|
||||
@@ -39,10 +39,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
private static let hudScale: CGFloat = PublicUtils.isIPhone ? 0.74 : 1
|
||||
private static let hudWidth: CGFloat = 235
|
||||
private static let edgeMargin: CGFloat = PublicUtils.isIPhone ? 5 : 24
|
||||
private var hints: [Hint] = [
|
||||
// Hint(control: .leftStick, keyContent: .text("L"), title: "浏览", isInAction: false),
|
||||
// Hint(control: .a, keyContent: .text("A"), title: "选择", isInAction: false)
|
||||
]
|
||||
private var hints: [Hint] = []
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@@ -83,10 +80,10 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
currentHud = hud
|
||||
hud.attach(to: window)
|
||||
|
||||
if let mainFrameVC = ControllerNavigator.radialMenuDelegate as? MainFrameViewController {
|
||||
if let mainFrameVC = ControllerNavigator.radialMenuDelegate as? MainFrameViewController, ControllerNavigator.radialMenuView == nil {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
|
||||
let streamFrameVC = StreamFrameViewController.sharedInstance()
|
||||
if ControllerNavigator.radialMenuView == nil, mainFrameVC.isStreaming(), !mainFrameVC.settingsExpandedInStreamView, streamFrameVC?.hasNoPresentedVC == true {
|
||||
if ControllerNavigator.radialMenuView == nil, mainFrameVC.isStreaming(), !mainFrameVC.settingsExpandedInStreamView, streamFrameVC?.hasNoPresentedVC == true, !ControllerNavigator.controllerMouseEnabled {
|
||||
requestHudDetachKeepingMinimumActionDuration()
|
||||
}
|
||||
}
|
||||
@@ -171,7 +168,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
guard hints.indices.contains(index),
|
||||
let rowStack = row as? UIStackView,
|
||||
let keyView = rowStack.arrangedSubviews.first,
|
||||
let titleLabel = rowStack.arrangedSubviews.last as? UILabel else {
|
||||
let titleView = rowStack.arrangedSubviews.last else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -192,7 +189,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
imageView.tintColor = isInAction ? ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 1 : 1) : ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.83 : 0.83)
|
||||
}
|
||||
}
|
||||
titleLabel.textColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.69 : 0.6)
|
||||
updateTitleColors(in: titleView, color: ThemeManager.textColor.withAlphaComponent(isDark ? 0.69 : 0.6))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +220,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
}
|
||||
|
||||
private func updateHints(with elements: [ControllerNavigationElement]) {
|
||||
let nextHints = elements.compactMap { hint(for: $0) }
|
||||
let nextHints = hints(for: elements)
|
||||
hints = nextHints.isEmpty ? [] : nextHints
|
||||
guard !hints.isEmpty else {return}
|
||||
reloadHintRows()
|
||||
@@ -236,7 +233,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
Hint(
|
||||
control: hint.control,
|
||||
keyContent: hint.keyContent,
|
||||
title: hint.title,
|
||||
titles: hint.titles,
|
||||
isInAction: Self.isControlActive(hint.control)
|
||||
)
|
||||
}
|
||||
@@ -252,12 +249,35 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
hints.forEach { contentStackView.addArrangedSubview(makeRow(for: $0)) }
|
||||
}
|
||||
|
||||
private func hint(for navigationElement: ControllerNavigationElement) -> Hint? {
|
||||
private func hints(for navigationElements: [ControllerNavigationElement]) -> [Hint] {
|
||||
var groupedElements: [ControllerElement: [ControllerNavigationElement]] = [:]
|
||||
var orderedControls: [ControllerElement] = []
|
||||
|
||||
for navigationElement in navigationElements {
|
||||
if groupedElements[navigationElement.control] == nil {
|
||||
orderedControls.append(navigationElement.control)
|
||||
groupedElements[navigationElement.control] = []
|
||||
}
|
||||
groupedElements[navigationElement.control]?.append(navigationElement)
|
||||
}
|
||||
|
||||
return orderedControls.flatMap { control -> [Hint] in
|
||||
guard let elements = groupedElements[control] else { return [] }
|
||||
if elements.count == 2 {
|
||||
return [hint(for: elements)]
|
||||
}
|
||||
|
||||
return elements.map { hint(for: [$0]) }
|
||||
}
|
||||
}
|
||||
|
||||
private func hint(for navigationElements: [ControllerNavigationElement]) -> Hint {
|
||||
let control = navigationElements.first?.control ?? .null
|
||||
return Hint(
|
||||
control: navigationElement.control,
|
||||
keyContent: keyContent(for: navigationElement.control),
|
||||
title: navigationElement.action.localized,
|
||||
isInAction: navigationElement.isInAction || Self.isControlActive(navigationElement.control)
|
||||
control: control,
|
||||
keyContent: keyContent(for: control),
|
||||
titles: navigationElements.map { $0.action.localized },
|
||||
isInAction: navigationElements.contains(where: { $0.isInAction }) || Self.isControlActive(control)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -309,22 +329,48 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
|
||||
private func makeRow(for hint: Hint) -> UIStackView {
|
||||
let keyView = makeKeyView(for: hint.keyContent)
|
||||
let titleView = makeTitleView(for: hint.titles)
|
||||
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = hint.title
|
||||
titleLabel.font = UIFont.roundedSystemFont(ofSize: 14, weight: .medium)
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
titleLabel.minimumScaleFactor = 0.2
|
||||
titleLabel.numberOfLines = 1
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
|
||||
let rowStackView = UIStackView(arrangedSubviews: [keyView, titleLabel])
|
||||
let rowStackView = UIStackView(arrangedSubviews: [keyView, titleView])
|
||||
rowStackView.axis = .horizontal
|
||||
rowStackView.alignment = .center
|
||||
rowStackView.spacing = 8
|
||||
return rowStackView
|
||||
}
|
||||
|
||||
private func makeTitleView(for titles: [String]) -> UIView {
|
||||
if titles.count == 2 {
|
||||
let stackView = UIStackView(arrangedSubviews: titles.map { makeTitleLabel(text: $0, isMergedTitle: true) })
|
||||
stackView.axis = .vertical
|
||||
stackView.alignment = .leading
|
||||
stackView.spacing = 1
|
||||
stackView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
return stackView
|
||||
}
|
||||
|
||||
return makeTitleLabel(text: titles.first ?? "", isMergedTitle: false)
|
||||
}
|
||||
|
||||
private func makeTitleLabel(text: String, isMergedTitle: Bool) -> UILabel {
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.text = text
|
||||
titleLabel.font = UIFont.roundedSystemFont(ofSize: isMergedTitle ? 12.5 : 14, weight: .medium)
|
||||
titleLabel.adjustsFontSizeToFitWidth = true
|
||||
titleLabel.minimumScaleFactor = isMergedTitle ? 0.55 : 0.2
|
||||
titleLabel.numberOfLines = 1
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
return titleLabel
|
||||
}
|
||||
|
||||
private func updateTitleColors(in view: UIView, color: UIColor) {
|
||||
if let label = view as? UILabel {
|
||||
label.textColor = color
|
||||
return
|
||||
}
|
||||
|
||||
view.subviews.forEach { updateTitleColors(in: $0, color: color) }
|
||||
}
|
||||
|
||||
private func makeKeyView(for content: KeyContent) -> UIView {
|
||||
let keyContainerView = UIView()
|
||||
keyContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
@@ -423,7 +469,7 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
}
|
||||
|
||||
private static func isControlActive(_ control: ControllerElement) -> Bool {
|
||||
!relatedControls(for: control).isDisjoint(with: activeControls)
|
||||
!activeControls(forHintControl: control).isDisjoint(with: activeControls)
|
||||
}
|
||||
|
||||
private static func requestActionStateEnd(for control: ControllerElement) {
|
||||
@@ -486,12 +532,16 @@ final class GamepadNavigationIllustrationHud: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
private static func relatedControls(for control: ControllerElement) -> Set<ControllerElement> {
|
||||
private static func activeControls(forHintControl control: ControllerElement) -> Set<ControllerElement> {
|
||||
switch control {
|
||||
case .leftStick, .leftStickX, .leftStickY:
|
||||
return [.leftStick, .leftStickX, .leftStickY]
|
||||
case .rightStick, .rightStickX, .rightStickY:
|
||||
return [.rightStick, .rightStickX, .rightStickY]
|
||||
case .dpad:
|
||||
return [.dpad, .dpadUp, .dpadDown, .dpadLeft, .dpadRight]
|
||||
case .abxy:
|
||||
return [.abxy, .a, .b, .x, .y]
|
||||
default:
|
||||
return [control]
|
||||
}
|
||||
|
||||
@@ -24,8 +24,14 @@ final class ControllerNavigationElement: NSObject {
|
||||
private var controllerNavigationSelectedIndexPathKey: UInt8 = 0
|
||||
private var controllerNavigationPersistTokenKey: UInt8 = 0
|
||||
private var controllerNavigationHighlightGenerationKey: UInt8 = 0
|
||||
private var controllerMouseCurvePreviewViewKey: UInt8 = 0
|
||||
private var controllerMouseCurvePreviewDismissWorkItemKey: UInt8 = 0
|
||||
private let settingsControllerNavigationHighlightedIdentifierKey = "SettingsControllerNavigationHighlightedIdentifier"
|
||||
private let hostControllerNavigationHighlightedUUIDKey = "HostControllerNavigationHighlightedUUID"
|
||||
private let settingsExcludedControllerNavigationSectionIdentifiers: Set<String> = [
|
||||
"SettingsSectionTouch&Controller",
|
||||
"SettingsSectionPencil"
|
||||
]
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private final class ControllerMouseDisplayLinkTarget: NSObject {
|
||||
@@ -34,6 +40,125 @@ private final class ControllerMouseDisplayLinkTarget: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
private final class ControllerMouseCurvePreviewView: UIView {
|
||||
var expo: CGFloat = 1.0 {
|
||||
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
|
||||
layer.cornerRadius = 14
|
||||
layer.cornerCurve = .continuous
|
||||
layer.masksToBounds = true
|
||||
layer.borderWidth = 1
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
let isDark = ThemeManager.userInterfaceStyle() == .dark
|
||||
let backgroundColor = isDark
|
||||
? UIColor(white: 0.08, alpha: 0.88)
|
||||
: UIColor.white.withAlphaComponent(0.92)
|
||||
let axisColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.62 : 0.48)
|
||||
let textColor = ThemeManager.textColor.withAlphaComponent(isDark ? 0.78 : 0.68)
|
||||
let curveColor = ThemeManager.appPrimaryColor
|
||||
layer.borderColor = ThemeManager.appPrimaryColor.withAlphaComponent(isDark ? 0.32 : 0.22).cgColor
|
||||
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
let footerHeight: CGFloat = 22
|
||||
let xLabelHeight: CGFloat = 24
|
||||
let plotHorizontalMargin: CGFloat = 34
|
||||
let chartRect = bounds.inset(by: UIEdgeInsets(top: 18, left: 0, bottom: footerHeight + xLabelHeight, right: 0))
|
||||
let plotSide = min(bounds.width - plotHorizontalMargin * 2, chartRect.height)
|
||||
let plotRect = CGRect(
|
||||
x: bounds.midX - plotSide / 2,
|
||||
y: chartRect.midY - plotSide / 2,
|
||||
width: plotSide,
|
||||
height: plotSide
|
||||
)
|
||||
guard plotRect.width > 1, plotRect.height > 1 else { return }
|
||||
|
||||
let textAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.roundedSystemFont(ofSize: 11, weight: .medium),
|
||||
.foregroundColor: textColor
|
||||
]
|
||||
|
||||
("Cursor Speed".localized as NSString).draw(at: CGPoint(x: plotRect.minX + 8, y: plotRect.minY + 8), withAttributes: textAttributes)
|
||||
let xAxisTitle = "Stick Offset".localized as NSString
|
||||
let xAxisTitleSize = xAxisTitle.size(withAttributes: textAttributes)
|
||||
xAxisTitle.draw(at: CGPoint(x: plotRect.midX - xAxisTitleSize.width / 2, y: plotRect.maxY + 10), withAttributes: textAttributes)
|
||||
|
||||
context.setStrokeColor(axisColor.cgColor)
|
||||
context.setLineWidth(1)
|
||||
context.move(to: CGPoint(x: plotRect.minX, y: plotRect.minY))
|
||||
context.addLine(to: CGPoint(x: plotRect.minX, y: plotRect.maxY))
|
||||
context.addLine(to: CGPoint(x: plotRect.maxX, y: plotRect.maxY))
|
||||
context.strokePath()
|
||||
|
||||
("0" as NSString).draw(at: CGPoint(x: plotRect.minX - 10, y: plotRect.maxY - 8), withAttributes: textAttributes)
|
||||
("1" as NSString).draw(at: CGPoint(x: plotRect.minX - 10, y: plotRect.minY - 7), withAttributes: textAttributes)
|
||||
("1" as NSString).draw(at: CGPoint(x: plotRect.maxX - 4, y: plotRect.maxY + 10), withAttributes: textAttributes)
|
||||
|
||||
let guidePath = UIBezierPath()
|
||||
guidePath.move(to: CGPoint(x: plotRect.minX, y: plotRect.maxY))
|
||||
guidePath.addLine(to: CGPoint(x: plotRect.maxX, y: plotRect.minY))
|
||||
context.saveGState()
|
||||
context.setLineDash(phase: 0, lengths: [4, 4])
|
||||
axisColor.withAlphaComponent(0.35).setStroke()
|
||||
guidePath.lineWidth = 1
|
||||
guidePath.stroke()
|
||||
context.restoreGState()
|
||||
|
||||
let curvePath = UIBezierPath()
|
||||
for step in 0...120 {
|
||||
let normalizedX = CGFloat(step) / 120
|
||||
let speed = abs(PublicUtils.controllerMouseExpoMappedOffset(normalizedX, expo: expo))
|
||||
let point = CGPoint(
|
||||
x: plotRect.minX + normalizedX * plotRect.width,
|
||||
y: plotRect.maxY - min(max(speed, 0), 1) * plotRect.height
|
||||
)
|
||||
if step == 0 {
|
||||
curvePath.move(to: point)
|
||||
} else {
|
||||
curvePath.addLine(to: point)
|
||||
}
|
||||
}
|
||||
|
||||
curveColor.setStroke()
|
||||
curvePath.lineWidth = 3
|
||||
curvePath.lineCapStyle = .round
|
||||
curvePath.lineJoinStyle = .round
|
||||
curvePath.stroke()
|
||||
|
||||
let footerParagraphStyle = NSMutableParagraphStyle()
|
||||
footerParagraphStyle.alignment = .center
|
||||
let footerAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: UIFont.roundedSystemFont(ofSize: 10, weight: .medium),
|
||||
.foregroundColor: textColor.withAlphaComponent(0.76),
|
||||
.paragraphStyle: footerParagraphStyle
|
||||
]
|
||||
("Inspired by RC hobby transmitters".localized as NSString).draw(
|
||||
in: CGRect(x: 12, y: bounds.maxY - footerHeight + 1, width: bounds.width - 24, height: footerHeight - 2),
|
||||
withAttributes: footerAttributes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
@objc protocol ControllerNavigatorRadialMenuDelegate: AnyObject {
|
||||
func controllerNavigatorDidSelect(item: RadialMenuItem)
|
||||
@@ -508,11 +633,11 @@ final class ControllerNavigator: NSObject {
|
||||
}
|
||||
case .exit:
|
||||
exit(0)
|
||||
case .shortcuts:
|
||||
case .toolbox:
|
||||
DispatchQueue.main.async {
|
||||
radialMenuView?.dismiss()
|
||||
radialMenuView = nil
|
||||
StreamFrameViewController.sharedInstance().bringUpToolboxMenu()
|
||||
StreamFrameViewController.sharedInstance().bringUpToolboxMenuWithoutWidgetLayoutTool()
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -544,7 +669,7 @@ final class ControllerNavigator: NSObject {
|
||||
if radialMenuState == .moreOptions {
|
||||
if mainFrameVC.isStreaming() {
|
||||
RadialMenuOverlayView.menuSectors.append(RadialMenuSector(title: "Game Profiles".localized, subtitle: "", symbol:PublicUtils.iOS18Available ? "gamecontroller.circle" : "gamecontroller.fill", item: .gameProfiles))
|
||||
RadialMenuOverlayView.menuSectors.append(RadialMenuSector(title: "=shortcuts".localized, subtitle: "", symbol: "apple.terminal", item: .shortcuts))
|
||||
RadialMenuOverlayView.menuSectors.append(RadialMenuSector(title: "=toolbox".localized, subtitle: "", symbol: "apple.terminal", item: .toolbox))
|
||||
}
|
||||
else {
|
||||
if !mainFrameVC.isInAppView(), !mainFrameVC.settingsViewExpanded {RadialMenuOverlayView.menuSectors.append(RadialMenuSector(title: "About".localized, subtitle: "", symbol: "questionmark.circle", item: .aboutView))}
|
||||
@@ -655,6 +780,94 @@ final class ControllerNavigator: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private static func listenToNavigationCluster() {
|
||||
guard let mainFrameVC = radialMenuDelegate as? MainFrameViewController else {return}
|
||||
|
||||
let upNavButton: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .dpadUp : .y
|
||||
let downNavButton: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .dpadDown : .a
|
||||
let leftNavButton: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .dpadLeft : .x
|
||||
let rightNavButton: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .dpadRight : .b
|
||||
|
||||
if let uiNavigationDelegate = uiNavigationDelegate, !mainFrameVC.isStreaming() || mainFrameVC.settingsExpandedInStreamView || uiNavigationDelegate is ToolboxViewController || uiNavigationDelegate is ProfileSelectorViewController {
|
||||
let verticalNavigationAxis: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .leftStickY : .rightStickY
|
||||
ControllerUtil.listenPrimaryControllerStickAxis(verticalNavigationAxis, threshold: 0.6) {state in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: verticalNavigationAxis, isInAction: state != .orderedSame)
|
||||
guard radialMenuView == nil else {return}
|
||||
switch state {
|
||||
case .orderedAscending:
|
||||
if navigationTimer?.isRunning() != true {
|
||||
if uiNavigationDelegate is SettingsViewController {
|
||||
navigateContinuously(downward: true)
|
||||
}
|
||||
if uiNavigationDelegate is ControllerCollectionNavigationDelegate {
|
||||
navigateContinuously(downward: true)
|
||||
}
|
||||
}
|
||||
case .orderedDescending:
|
||||
if navigationTimer?.isRunning() != true {
|
||||
if uiNavigationDelegate is SettingsViewController {
|
||||
navigateContinuously(downward: false)
|
||||
}
|
||||
if uiNavigationDelegate is ControllerCollectionNavigationDelegate {
|
||||
navigateContinuously(downward: false)
|
||||
}
|
||||
}
|
||||
case .orderedSame:
|
||||
navigationTimer?.clean()
|
||||
}
|
||||
}
|
||||
ControllerUtil.listenPrimaryControllerButton(upNavButton) {pressed in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: upNavButton, isInAction: pressed)
|
||||
if pressed {
|
||||
uiNavigationDelegate.navigateByController(downward: false)
|
||||
}
|
||||
}
|
||||
ControllerUtil.listenPrimaryControllerButton(downNavButton) {pressed in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: downNavButton, isInAction: pressed)
|
||||
if pressed {
|
||||
uiNavigationDelegate.navigateByController(downward: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let uiNavigationDelegate = uiNavigationDelegate, !mainFrameVC.isStreaming() || uiNavigationDelegate is ToolboxViewController || uiNavigationDelegate is ProfileSelectorViewController {
|
||||
let horizontalNavigationAxis: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .leftStickX : .rightStickX
|
||||
ControllerUtil.listenPrimaryControllerStickAxis(horizontalNavigationAxis, threshold: 0.6) {state in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: horizontalNavigationAxis, isInAction: state != .orderedSame)
|
||||
guard radialMenuView == nil else {return}
|
||||
switch state {
|
||||
case .orderedAscending:
|
||||
if navigationTimer?.isRunning() != true {
|
||||
if uiNavigationDelegate is ControllerCollectionNavigationDelegate {
|
||||
navigateContinuously(forward: false)
|
||||
}
|
||||
}
|
||||
case .orderedDescending:
|
||||
if navigationTimer?.isRunning() != true {
|
||||
if uiNavigationDelegate is ControllerCollectionNavigationDelegate {
|
||||
navigateContinuously(forward: true)
|
||||
}
|
||||
}
|
||||
case .orderedSame:
|
||||
navigationTimer?.clean()
|
||||
}
|
||||
}
|
||||
ControllerUtil.listenPrimaryControllerButton(leftNavButton) {pressed in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: leftNavButton, isInAction: pressed)
|
||||
if pressed {
|
||||
uiNavigationDelegate.navigateByController(forward: false)
|
||||
}
|
||||
}
|
||||
ControllerUtil.listenPrimaryControllerButton(rightNavButton) {pressed in
|
||||
GamepadNavigationIllustrationHud.updateActionState(for: rightNavButton, isInAction: pressed)
|
||||
if pressed {
|
||||
uiNavigationDelegate.navigateByController(forward: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc static func listenToControllerMouse() {
|
||||
guard radialMenuState == .mouseModeEnabled else { return }
|
||||
ControllerUtil.listenPrimaryControllerButton(controllerMouseLeftButton){ pressed in
|
||||
@@ -693,9 +906,9 @@ final class ControllerNavigator: NSObject {
|
||||
|
||||
listenToRadialMenuButton()
|
||||
listenToRadialMenuStick()
|
||||
|
||||
listenToNavigationCluster()
|
||||
|
||||
|
||||
/*
|
||||
if let uiNavigationDelegate = uiNavigationDelegate, !mainFrameVC.isStreaming() || mainFrameVC.settingsExpandedInStreamView || uiNavigationDelegate is ToolboxViewController || uiNavigationDelegate is ProfileSelectorViewController {
|
||||
let verticalNavigationAxis: ControllerElement = ControllerNavigator.radialMenuButtonPosition == .right ? .leftStickY : .rightStickY
|
||||
ControllerUtil.listenPrimaryControllerStickAxis(verticalNavigationAxis, threshold: 0.6) {state in
|
||||
@@ -749,6 +962,7 @@ final class ControllerNavigator: NSObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if let uiNavigationDelegate = uiNavigationDelegate, !mainFrameVC.isStreaming() || mainFrameVC.settingsExpandedInStreamView || uiNavigationDelegate is UIAlertController || uiNavigationDelegate is ProfileSelectorViewController || uiNavigationDelegate is ToolboxViewController {
|
||||
let navigations = uiNavigationDelegate.getNavigationElements()
|
||||
@@ -792,6 +1006,13 @@ final class ControllerNavigator: NSObject {
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension SettingsViewController: ControllerUINavigationDelegate {
|
||||
@objc(showControllerMouseCurvePreviewWithExpo:)
|
||||
func showControllerMouseCurvePreview(expo: CGFloat) {
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
self?.showControllerMouseCurvePreviewOnMain(expo: expo)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func persistControllerNavigationHighlight() {
|
||||
guard let highlightedView = ControllerNavigator.controllerNavigationHighlightedView,
|
||||
let identifier = controllerNavigationPersistenceIdentifier(for: highlightedView) else {
|
||||
@@ -801,6 +1022,64 @@ extension SettingsViewController: ControllerUINavigationDelegate {
|
||||
UserDefaults.standard.set(identifier, forKey: settingsControllerNavigationHighlightedIdentifierKey)
|
||||
}
|
||||
|
||||
private var controllerMouseCurvePreviewView: ControllerMouseCurvePreviewView? {
|
||||
get {
|
||||
objc_getAssociatedObject(self, &controllerMouseCurvePreviewViewKey) as? ControllerMouseCurvePreviewView
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &controllerMouseCurvePreviewViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
private var controllerMouseCurvePreviewDismissWorkItem: DispatchWorkItem? {
|
||||
get {
|
||||
objc_getAssociatedObject(self, &controllerMouseCurvePreviewDismissWorkItemKey) as? DispatchWorkItem
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &controllerMouseCurvePreviewDismissWorkItemKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
private func showControllerMouseCurvePreviewOnMain(expo: CGFloat) {
|
||||
let previewView = controllerMouseCurvePreviewView ?? ControllerMouseCurvePreviewView()
|
||||
controllerMouseCurvePreviewView = previewView
|
||||
previewView.expo = expo
|
||||
previewView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
if previewView.superview == nil {
|
||||
previewView.alpha = 0
|
||||
view.addSubview(previewView)
|
||||
let preferredWidth = previewView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, multiplier: 0.72)
|
||||
preferredWidth.priority = .defaultHigh
|
||||
NSLayoutConstraint.activate([
|
||||
previewView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
|
||||
previewView.widthAnchor.constraint(lessThanOrEqualToConstant: 360),
|
||||
preferredWidth,
|
||||
previewView.heightAnchor.constraint(equalToConstant: 190),
|
||||
previewView.bottomAnchor.constraint(equalTo: controllerMouseExpoStack.topAnchor, constant: -10)
|
||||
])
|
||||
}
|
||||
|
||||
view.bringSubviewToFront(previewView)
|
||||
UIView.animate(withDuration: 0.12) {
|
||||
previewView.alpha = 1
|
||||
}
|
||||
|
||||
controllerMouseCurvePreviewDismissWorkItem?.cancel()
|
||||
let workItem = DispatchWorkItem { [weak self, weak previewView] in
|
||||
UIView.animate(withDuration: 0.18, animations: {
|
||||
previewView?.alpha = 0
|
||||
}, completion: { _ in
|
||||
previewView?.removeFromSuperview()
|
||||
if self?.controllerMouseCurvePreviewView === previewView {
|
||||
self?.controllerMouseCurvePreviewView = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
controllerMouseCurvePreviewDismissWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: workItem)
|
||||
}
|
||||
|
||||
@objc func restoreControllerNavigationHighlight() {
|
||||
guard ControllerNavigator.enabled, ControllerUtil.primaryGCController != nil else {return}
|
||||
PublicUtils.runOnMain { [weak self] in
|
||||
@@ -853,23 +1132,25 @@ extension SettingsViewController: ControllerUINavigationDelegate {
|
||||
}
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButton, action: "radialMenu"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .rightStickY : .leftStickY, action: "menuNavigation"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .abxy : .dpad, action: "menuNavigation"))
|
||||
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadLeft : .x, action: "widgetOperationBackward"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .b, action: "widgetOperationForward"))
|
||||
return elements
|
||||
}
|
||||
|
||||
func navigateByController(forward:Bool) {
|
||||
func navigateByController(downward:Bool) {
|
||||
guard self.hasNoPresentedVC else {return}
|
||||
if currentSettingsMenuMode == .FavoriteSettings,
|
||||
ControllerNavigator.settingsFavoriteReorderActive {
|
||||
moveHighlightedFavoriteSettingStack(by: forward ? 1 : -1)
|
||||
moveHighlightedFavoriteSettingStack(by: downward ? 1 : -1)
|
||||
return
|
||||
}
|
||||
|
||||
performControllerNavigationSelectionMove(by: forward ? 1 : -1)
|
||||
performControllerNavigationSelectionMove(by: downward ? 1 : -1)
|
||||
}
|
||||
|
||||
func navigateByController(downward:Bool) {
|
||||
func navigateByController(forward:Bool) {
|
||||
}
|
||||
|
||||
func uiWidgetActionForControllerNavigator(forward: Bool, from navigation:ControllerNavigationElement) {
|
||||
@@ -1270,6 +1551,7 @@ extension SettingsViewController: ControllerUINavigationDelegate {
|
||||
targets.append(headerView)
|
||||
}
|
||||
|
||||
guard !shouldSkipControllerNavigationSection(sectionView) else { continue }
|
||||
guard let rootStackView = sectionView.rootStackView else { continue }
|
||||
guard isControllerNavigationVisible(rootStackView, within: sectionView) else { continue }
|
||||
|
||||
@@ -1282,6 +1564,14 @@ extension SettingsViewController: ControllerUINavigationDelegate {
|
||||
return targets
|
||||
}
|
||||
|
||||
private func shouldSkipControllerNavigationSection(_ sectionView: MenuSectionView) -> Bool {
|
||||
guard let identifier = sectionView.identifier else {
|
||||
return false
|
||||
}
|
||||
|
||||
return settingsExcludedControllerNavigationSectionIdentifiers.contains(identifier)
|
||||
}
|
||||
|
||||
private func favoriteSettingsControllerNavigationTargets(in parentStack: UIStackView, skippingDisabledStacks: Bool) -> [UIView] {
|
||||
var targets: [UIView] = []
|
||||
for arrangedSubview in parentStack.arrangedSubviews {
|
||||
@@ -1673,10 +1963,11 @@ extension ControllerCollectionNavigationDelegate {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButton, action: "radialMenu"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .rightStick : .leftStick, action: "focusNavigation"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .abxy : .dpad, action: "focusNavigation"))
|
||||
if self is HostCollectionViewController {elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: "applications"))}
|
||||
if self is HostCollectionViewController {elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .x, action: "launchPairWake"))}
|
||||
if self is MainFrameViewController {elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: "launch"))}
|
||||
if self is MainFrameViewController {elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .x, action: "quitApp"))}
|
||||
if self is MainFrameViewController {elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .b, action: "quitApp"))}
|
||||
return elements
|
||||
}
|
||||
|
||||
@@ -2055,13 +2346,28 @@ extension UIAlertController: ControllerUINavigationDelegate {
|
||||
|
||||
guard enabledActions.count > 1, let lastAction = enabledActions.last else {
|
||||
guard let title = firstAction.title, !title.isEmpty else { return [] }
|
||||
return [ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .x, action: title)]
|
||||
return [ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: title)]
|
||||
}
|
||||
|
||||
return [
|
||||
ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadLeft : .x, action: firstAction.title ?? ""),
|
||||
ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .b, action: lastAction.title ?? "")
|
||||
].filter { !$0.action.isEmpty }
|
||||
|
||||
|
||||
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
|
||||
if ControllerNavigator.radialMenuButtonPosition == .left {
|
||||
elements.append(ControllerNavigationElement(control: firstAction.style == .default ? .dpadRight : .dpadUp, action: firstAction.title ?? ""))
|
||||
if firstAction.style != lastAction.style {
|
||||
elements.append(ControllerNavigationElement(control: lastAction.style == .cancel ? .dpadUp : .dpadRight, action: lastAction.title ?? ""))
|
||||
}
|
||||
}
|
||||
else {
|
||||
elements.append(ControllerNavigationElement(control: firstAction.style == .default ? .a : .b, action: firstAction.title ?? ""))
|
||||
if firstAction.style != lastAction.style {
|
||||
elements.append(ControllerNavigationElement(control: lastAction.style == .cancel ? .b : .a, action: lastAction.title ?? ""))
|
||||
}
|
||||
}
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
func navigateByController(forward: Bool) {}
|
||||
|
||||
@@ -17,6 +17,7 @@ import Foundation
|
||||
case stick
|
||||
case stickAxis
|
||||
case touchpad
|
||||
case cluster
|
||||
case undefined
|
||||
}
|
||||
|
||||
@@ -70,6 +71,9 @@ import Foundation
|
||||
case rightStick = 0x800020
|
||||
case rightStickX = 0x800021
|
||||
case rightStickY = 0x800022
|
||||
|
||||
case dpad = 0x800013
|
||||
case abxy = 0x800023
|
||||
|
||||
case null = 0xFFFFFF
|
||||
|
||||
@@ -111,6 +115,9 @@ import Foundation
|
||||
case .rightStick: return "RStick".localized
|
||||
case .rightStickX: return "RStickX".localized
|
||||
case .rightStickY: return "RStickY".localized
|
||||
|
||||
case .dpad: return "DPad".localized
|
||||
case .abxy: return "ABXY".localized
|
||||
|
||||
case .null: return "Null".localized
|
||||
}
|
||||
@@ -130,6 +137,9 @@ import Foundation
|
||||
case .dpadRight:
|
||||
if #available(iOS 15.0, *) {return "dpad.right.filled"}
|
||||
else {return ""}
|
||||
case .dpad:
|
||||
if #available(iOS 14.0, *) {return "dpad"}
|
||||
else {return ""}
|
||||
case .a:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.down.filled"}
|
||||
else {return ""}
|
||||
@@ -142,6 +152,10 @@ import Foundation
|
||||
case .y:
|
||||
if #available(iOS 15.0, *) {return "circle.grid.cross.up.filled"}
|
||||
else {return ""}
|
||||
case .abxy:
|
||||
if #available(iOS 14.0, *) {return "circle.grid.cross"}
|
||||
else {return ""}
|
||||
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
@@ -184,6 +198,9 @@ import Foundation
|
||||
case .rightStick: return .stick
|
||||
case .rightStickX: return .stickAxis
|
||||
case .rightStickY: return .stickAxis
|
||||
|
||||
case .dpad: return .cluster
|
||||
case .abxy: return .cluster
|
||||
|
||||
case .null: return .undefined
|
||||
}
|
||||
@@ -227,6 +244,9 @@ import Foundation
|
||||
case .rightStick: return .right
|
||||
case .rightStickX: return .right
|
||||
case .rightStickY: return .right
|
||||
|
||||
case .abxy: return .right
|
||||
case .dpad: return .left
|
||||
|
||||
case .null: return .undefined
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<attribute name="controllerMouseExpo" optional="YES" attributeType="Float" defaultValueString="1.6" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseLeftButton" attributeType="Integer 32" defaultValueString="8" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMousePointerVelocity" optional="YES" attributeType="Float" defaultValueString="12" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseRightButton" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseRightButton" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="controllerMouseStick" attributeType="Integer 32" defaultValueString="8388640" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="ctrlDownForPinch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="customLocalRadialMenuButtonPosition" attributeType="Integer 32" defaultValueString="16777215" usesScalarValueType="NO" syncable="YES"/>
|
||||
|
||||
@@ -4569,29 +4569,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"All Settings" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "全部设置"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "所有設定"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "所有設定"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"All Profiles" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -4615,6 +4592,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"All Settings" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "全部设置"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "所有設定"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "所有設定"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Alpha" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -6390,6 +6390,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Current Profile" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "当前配置"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "目前設定檔"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "目前設定檔"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Cursor Speed" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "光标速度"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "游標速度"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "游標速度"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Custom Resolution Selected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -6534,29 +6580,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Current Profile" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "当前配置"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "目前設定檔"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "目前設定檔"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Del" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -6632,29 +6655,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Duplicate" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "复制"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "複製"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "複製"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Delete Buttons Here" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -7496,6 +7496,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Duplicate" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "复制"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "複製"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "複製"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"e.g. ctrl, lswheel, wasdpad..." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -8211,29 +8234,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Export" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "导出"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "匯出"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "匯出"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expands setting menu during streaming" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -8280,6 +8280,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Export" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "导出"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "匯出"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "匯出"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalDisplayModeStackDoc" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -10229,6 +10252,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Inspired by RC hobby transmitters" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "灵感来自航模遥控发射机"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "靈感來自航模遙控發射機"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "靈感來自航模遙控發射機"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Interval of Multi-Touch Move Events: %d μs" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -16814,6 +16860,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Stick Offset" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "摇杆偏移量"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "搖桿偏移量"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "搖桿偏移量"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"stickWheelLink" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -18977,4 +19046,4 @@
|
||||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -1201,9 +1201,10 @@ final class ProfileSelectorViewController: UIViewController, UICollectionViewDat
|
||||
func getNavigationElements() -> [ControllerNavigationElement] {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .rightStick : .leftStick, action: "focusNavigation"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .abxy : .dpad, action: "focusNavigation"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .y, action: "holdToReorder"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .y, action: "doublePressToDelete"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: "exit"))
|
||||
// elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .y, action: "doublePressToDelete"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .b, action: "exit"))
|
||||
return elements
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
@class LayoutOnScreenControlsViewController;
|
||||
@class MainFrameViewController;
|
||||
@class MenuSectionView;
|
||||
@class MicHandler;
|
||||
@class WidgetPickerViewController;
|
||||
|
||||
@@ -105,6 +106,7 @@
|
||||
@property (weak, nonatomic) LayoutOnScreenControlsViewController *layoutOnScreenControlsVC;
|
||||
@property (nonatomic, weak) MainFrameViewController *mainFrameViewController;
|
||||
@property (strong, nonatomic) UIView *controllerNavigationHighlightOverlayView;
|
||||
@property (nonatomic, strong, readonly) MenuSectionView *touchControlSection;
|
||||
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *externalDisplayModeSelector;
|
||||
@property (strong, nonatomic) IBOutlet UISegmentedControl *localMousePointerModeSelector;
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
@dynamic overrideUserInterfaceStyle;
|
||||
@synthesize controllerNavigationHighlightOverlayView = _controllerNavigationHighlightOverlayView;
|
||||
|
||||
- (MenuSectionView *)touchControlSection {
|
||||
return touchControlSection;
|
||||
}
|
||||
|
||||
|
||||
//static NSString* bitrateFormat;
|
||||
static const int bitrateTable[] = {
|
||||
@@ -1839,8 +1843,8 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
[tipsAlertController setValue:attributedMessage forKey:@"attributedMessage"];
|
||||
*/
|
||||
|
||||
UIAlertAction *readInstruction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Online Documentation"]
|
||||
style:UIAlertActionStyleDefault
|
||||
UIAlertAction *readInstruction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Learn More"]
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:^(UIAlertAction *action){
|
||||
[PublicUtils openUrl:onlineDocLink];
|
||||
}];
|
||||
@@ -2540,7 +2544,7 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
*/
|
||||
|
||||
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"Cancel"]
|
||||
style:UIAlertActionStyleDefault
|
||||
style:UIAlertActionStyleCancel
|
||||
handler:nil];
|
||||
|
||||
UIAlertAction *okAction = [UIAlertAction actionWithTitle:[LocalizationHelper localizedStringForKey:@"OK"]
|
||||
@@ -3342,6 +3346,9 @@ BOOL isCustomResolution(int resolutionSelected) {
|
||||
|
||||
- (void) controllerMouseExpoSliderMoved:(UISlider* )sender {
|
||||
[self findDynamicLabelFromStack:(UIStackView*)sender.superview].text = [NSString stringWithFormat:@" %.1f ", sender.value];
|
||||
if (@available(iOS 13.0, *)) if(!settingsViewJustLoaded) {
|
||||
[self showControllerMouseCurvePreviewWithExpo:sender.value];
|
||||
}
|
||||
}
|
||||
|
||||
- (uint32_t) getScreenEdgeFromSelector {
|
||||
|
||||
@@ -63,5 +63,6 @@
|
||||
- (void)resetMagnifierStreamViewWithAnimated:(BOOL)animated;
|
||||
- (void)returnToMainFrame;
|
||||
- (void)bringUpToolboxMenu;
|
||||
- (void)bringUpToolboxMenuWithoutWidgetLayoutTool;
|
||||
|
||||
@end
|
||||
|
||||
@@ -307,11 +307,20 @@ static __weak StreamFrameViewController *VLSharedStreamFrameViewController = nil
|
||||
}
|
||||
|
||||
- (void)bringUpToolboxMenu{
|
||||
[self bringUpToolboxMenuWithoutWidgetLayoutTool:NO];
|
||||
}
|
||||
|
||||
- (void)bringUpToolboxMenuWithoutWidgetLayoutTool{
|
||||
[self bringUpToolboxMenuWithoutWidgetLayoutTool:YES];
|
||||
}
|
||||
|
||||
- (void)bringUpToolboxMenuWithoutWidgetLayoutTool:(BOOL)hideWidgetLayoutTool{
|
||||
[self prepareGameProfileSelector];
|
||||
ToolboxViewController* oldToolboxVC = toolBoxViewController;
|
||||
toolBoxViewController = [[ToolboxViewController alloc] init];
|
||||
toolBoxViewController.specialEntryDelegate = self;
|
||||
toolBoxViewController.specialEntries = oldToolboxVC.specialEntries;
|
||||
toolBoxViewController.specialEntries = [oldToolboxVC.specialEntries mutableCopy];
|
||||
if(hideWidgetLayoutTool) [toolBoxViewController.specialEntries removeObject:@"widgetLayoutTool"];
|
||||
toolBoxViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
[self presentViewController:toolBoxViewController animated:YES completion:^{
|
||||
//[self->toolBoxViewController setupConstraints];
|
||||
|
||||
@@ -946,6 +946,7 @@ extension ToolboxViewController: ControllerCollectionNavigationDelegate {
|
||||
@objc func getNavigationElements() -> [ControllerNavigationElement] {
|
||||
var elements: [ControllerNavigationElement] = []
|
||||
elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .rightStick : .leftStick, action: "focusNavigation"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .abxy : .dpad, action: "focusNavigation"))
|
||||
elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: "execute"))
|
||||
elements.append(ControllerNavigationElement(control: ControllerNavigator.radialMenuButtonPosition == .left ? .dpadRight : .a, action: "doublePressPinExecute"))
|
||||
elements.append(ControllerNavigationElement(control:ControllerNavigator.radialMenuButtonPosition == .left ? .dpadUp : .y, action: "holdToReorder"))
|
||||
|
||||
@@ -25,7 +25,7 @@ import UIKit
|
||||
case more
|
||||
case navigationSettings
|
||||
case exit
|
||||
case shortcuts
|
||||
case toolbox
|
||||
}
|
||||
|
||||
@objc public enum RadialMenuState:Int {
|
||||
|
||||
Reference in New Issue
Block a user