Created
October 18, 2023 08:19
-
-
Save jarrodnorwell/9ffe674e68c5b3b7c5b355fbbb67bcc3 to your computer and use it in GitHub Desktop.
LMVirtualControllerView is a custom on-screen (virtual) controller replacement for GCVirualController used in emuThreeDS/Limón
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// LMVirtualControllerView.swift | |
// Limon | |
// | |
// Created by Jarrod Norwell on 10/9/23. | |
// | |
import Foundation | |
import UIKit | |
class LMVirtualControllerView : UIView { | |
var abxyButtonDelegate: ABXYButtonDelegate? | |
var ldurButtonDelegate: LDURButtonDelegate? | |
var selectStartButtonDelegate: SelectStartButtonDelegate? | |
var bumperTriggerButtonDelegate: BumperTriggerButtonDelegate? | |
var aButton, bButton, xButton, yButton: ABXYButton! | |
var leftButton, downButton, upButton, rightButton: LDURButton! | |
var selectButton, startButton: SelectStartButton! | |
var lButton, zlButton, rButton, zrButton: BumperTriggerButton! | |
enum State { | |
case activated, deactivated | |
} | |
var state: State = .activated | |
func addAButton() { | |
aButton = .init(.a) | |
aButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(aButton) | |
addConstraints([ | |
aButton.widthAnchor.constraint(equalToConstant: 50), | |
aButton.heightAnchor.constraint(equalToConstant: 50), | |
aButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -88), | |
aButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -20) | |
]) | |
} | |
func addBButton() { | |
bButton = .init(.b) | |
bButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(bButton) | |
addConstraints([ | |
bButton.widthAnchor.constraint(equalToConstant: 50), | |
bButton.heightAnchor.constraint(equalToConstant: 50), | |
bButton.topAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.bottomAnchor), | |
bButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func addXButton() { | |
xButton = .init(.x) | |
xButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(xButton) | |
addConstraints([ | |
xButton.widthAnchor.constraint(equalToConstant: 50), | |
xButton.heightAnchor.constraint(equalToConstant: 50), | |
xButton.bottomAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor), | |
xButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func addYButton() { | |
yButton = .init(.y) | |
yButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(yButton) | |
addConstraints([ | |
yButton.widthAnchor.constraint(equalToConstant: 50), | |
yButton.heightAnchor.constraint(equalToConstant: 50), | |
yButton.topAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.topAnchor), | |
yButton.trailingAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func addLeftButton() { | |
leftButton = .init(.left) | |
leftButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(leftButton) | |
addConstraints([ | |
leftButton.widthAnchor.constraint(equalToConstant: 50), | |
leftButton.heightAnchor.constraint(equalToConstant: 50), | |
leftButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -88), | |
leftButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 20) | |
]) | |
} | |
func addDownButton() { | |
downButton = .init(.down) | |
downButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(downButton) | |
addConstraints([ | |
downButton.widthAnchor.constraint(equalToConstant: 50), | |
downButton.heightAnchor.constraint(equalToConstant: 50), | |
downButton.topAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.bottomAnchor), | |
downButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor) | |
]) | |
} | |
func addUpButton() { | |
upButton = .init(.up) | |
upButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(upButton) | |
addConstraints([ | |
upButton.widthAnchor.constraint(equalToConstant: 50), | |
upButton.heightAnchor.constraint(equalToConstant: 50), | |
upButton.bottomAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor), | |
upButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.trailingAnchor) | |
]) | |
} | |
func addRightButton() { | |
rightButton = .init(.right) | |
rightButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(rightButton) | |
addConstraints([ | |
rightButton.widthAnchor.constraint(equalToConstant: 50), | |
rightButton.heightAnchor.constraint(equalToConstant: 50), | |
rightButton.topAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.topAnchor), | |
rightButton.leadingAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.trailingAnchor) | |
]) | |
} | |
func addSelectButton() { | |
selectButton = .init(.select) | |
selectButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(selectButton) | |
addConstraints([ | |
selectButton.widthAnchor.constraint(equalToConstant: 40), | |
selectButton.heightAnchor.constraint(equalToConstant: 40), | |
selectButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -20), | |
selectButton.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: -10) | |
]) | |
} | |
func addStartButton() { | |
startButton = .init(.start) | |
startButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(startButton) | |
addConstraints([ | |
startButton.widthAnchor.constraint(equalToConstant: 40), | |
startButton.heightAnchor.constraint(equalToConstant: 40), | |
startButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -20), | |
startButton.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor, constant: 10) | |
]) | |
} | |
func addLButton() { | |
lButton = .init(.l) | |
lButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(lButton) | |
addConstraints([ | |
lButton.widthAnchor.constraint(equalToConstant: 56), | |
lButton.heightAnchor.constraint(equalToConstant: 48), | |
lButton.bottomAnchor.constraint(equalTo: upButton.safeAreaLayoutGuide.topAnchor, constant: -20), | |
lButton.leadingAnchor.constraint(equalTo: leftButton.safeAreaLayoutGuide.leadingAnchor) | |
]) | |
} | |
func addZLButton() { | |
zlButton = .init(.zl) | |
zlButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(zlButton) | |
addConstraints([ | |
zlButton.widthAnchor.constraint(equalToConstant: 56), | |
zlButton.heightAnchor.constraint(equalToConstant: 48), | |
zlButton.topAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.topAnchor), | |
zlButton.leadingAnchor.constraint(equalTo: lButton.safeAreaLayoutGuide.trailingAnchor, constant: 20) | |
]) | |
} | |
func addRButton() { | |
rButton = .init(.r) | |
rButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(rButton) | |
addConstraints([ | |
rButton.widthAnchor.constraint(equalToConstant: 56), | |
rButton.heightAnchor.constraint(equalToConstant: 48), | |
rButton.bottomAnchor.constraint(equalTo: xButton.safeAreaLayoutGuide.topAnchor, constant: -20), | |
rButton.trailingAnchor.constraint(equalTo: aButton.safeAreaLayoutGuide.trailingAnchor) | |
]) | |
} | |
func addZRButton() { | |
zrButton = .init(.zr) | |
zrButton.translatesAutoresizingMaskIntoConstraints = false | |
addSubview(zrButton) | |
addConstraints([ | |
zrButton.widthAnchor.constraint(equalToConstant: 56), | |
zrButton.heightAnchor.constraint(equalToConstant: 48), | |
zrButton.topAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.topAnchor), | |
zrButton.trailingAnchor.constraint(equalTo: rButton.safeAreaLayoutGuide.leadingAnchor, constant: -20) | |
]) | |
} | |
func hide() { | |
state = .deactivated | |
aButton.letterImageView.addSymbolEffect(.disappear) | |
bButton.letterImageView.addSymbolEffect(.disappear) | |
xButton.letterImageView.addSymbolEffect(.disappear) | |
yButton.letterImageView.addSymbolEffect(.disappear) | |
leftButton.letterImageView.addSymbolEffect(.disappear) | |
downButton.letterImageView.addSymbolEffect(.disappear) | |
upButton.letterImageView.addSymbolEffect(.disappear) | |
rightButton.letterImageView.addSymbolEffect(.disappear) | |
selectButton.letterImageView.addSymbolEffect(.disappear) | |
startButton.letterImageView.addSymbolEffect(.disappear) | |
lButton.letterImageView.addSymbolEffect(.disappear) | |
zlButton.letterImageView.addSymbolEffect(.disappear) | |
rButton.letterImageView.addSymbolEffect(.disappear) | |
zrButton.letterImageView.addSymbolEffect(.disappear) | |
} | |
func show() { | |
state = .activated | |
aButton.letterImageView.addSymbolEffect(.appear) | |
bButton.letterImageView.addSymbolEffect(.appear) | |
xButton.letterImageView.addSymbolEffect(.appear) | |
yButton.letterImageView.addSymbolEffect(.appear) | |
leftButton.letterImageView.addSymbolEffect(.appear) | |
downButton.letterImageView.addSymbolEffect(.appear) | |
upButton.letterImageView.addSymbolEffect(.appear) | |
rightButton.letterImageView.addSymbolEffect(.appear) | |
selectButton.letterImageView.addSymbolEffect(.appear) | |
startButton.letterImageView.addSymbolEffect(.appear) | |
lButton.letterImageView.addSymbolEffect(.appear) | |
zlButton.letterImageView.addSymbolEffect(.appear) | |
rButton.letterImageView.addSymbolEffect(.appear) | |
zrButton.letterImageView.addSymbolEffect(.appear) | |
} | |
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { | |
state == .activated ? super.hitTest(point, with: event) == self ? nil : super.hitTest(point, with: event) : nil | |
} | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesBegan(touches, with: event) | |
guard state == .activated, let abxyButtonDelegate, let ldurButtonDelegate, let selectStartButtonDelegate, let bumperTriggerButtonDelegate, let touch = touches.first else { | |
return | |
} | |
UIImpactFeedbackGenerator(style: .soft).impactOccurred() | |
switch touch.view { | |
case aButton: | |
abxyButtonDelegate.touchDown(.a) | |
case bButton: | |
abxyButtonDelegate.touchDown(.b) | |
case xButton: | |
abxyButtonDelegate.touchDown(.x) | |
case yButton: | |
abxyButtonDelegate.touchDown(.y) | |
case leftButton: | |
ldurButtonDelegate.touchDown(.left) | |
case downButton: | |
ldurButtonDelegate.touchDown(.down) | |
case upButton: | |
ldurButtonDelegate.touchDown(.up) | |
case rightButton: | |
ldurButtonDelegate.touchDown(.right) | |
case selectButton: | |
selectStartButtonDelegate.touchDown(.select) | |
case startButton: | |
selectStartButtonDelegate.touchDown(.start) | |
case lButton: | |
bumperTriggerButtonDelegate.touchDown(.l) | |
case zlButton: | |
bumperTriggerButtonDelegate.touchDown(.zl) | |
case rButton: | |
bumperTriggerButtonDelegate.touchDown(.r) | |
case zrButton: | |
bumperTriggerButtonDelegate.touchDown(.zr) | |
default: | |
break | |
} | |
} | |
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesEnded(touches, with: event) | |
guard state == .activated, let abxyButtonDelegate, let ldurButtonDelegate, let selectStartButtonDelegate, let bumperTriggerButtonDelegate, let touch = touches.first else { | |
return | |
} | |
switch touch.view { | |
case aButton: | |
abxyButtonDelegate.touchUpInside(.a) | |
case bButton: | |
abxyButtonDelegate.touchUpInside(.b) | |
case xButton: | |
abxyButtonDelegate.touchUpInside(.x) | |
case yButton: | |
abxyButtonDelegate.touchUpInside(.y) | |
case leftButton: | |
ldurButtonDelegate.touchUpInside(.left) | |
case downButton: | |
ldurButtonDelegate.touchUpInside(.down) | |
case upButton: | |
ldurButtonDelegate.touchUpInside(.up) | |
case rightButton: | |
ldurButtonDelegate.touchUpInside(.right) | |
case selectButton: | |
selectStartButtonDelegate.touchUpInside(.select) | |
case startButton: | |
selectStartButtonDelegate.touchUpInside(.start) | |
case lButton: | |
bumperTriggerButtonDelegate.touchUpInside(.l) | |
case zlButton: | |
bumperTriggerButtonDelegate.touchUpInside(.zl) | |
case rButton: | |
bumperTriggerButtonDelegate.touchUpInside(.r) | |
case zrButton: | |
bumperTriggerButtonDelegate.touchUpInside(.zr) | |
default: | |
break | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment