mirror of
https://github.com/libretro/RetroArch
synced 2025-02-01 00:32:46 +00:00
01cb10d8b3
* Fetch translations from Crowdin * Support for showing and hiding indicator and navigation bar * Refactored to use a view model * Support defining helper bar items and support showing/hiding keyboard * reorganized source files into separate logical files * Moved mouse support to swift (except for delegate implementation), added support for enabling touch mouse in helper bar; reorganized swift source files * Reorganized keyboard files; added the touch mouse messages to the RA localization files; use the RA notification system * change keyboard letters to uppercase for clarity Co-authored-by: github-actions <github-actions@github.com>
284 lines
11 KiB
Swift
284 lines
11 KiB
Swift
//
|
|
// EmulatorKeyboard.swift
|
|
//
|
|
// Created by Yoshi Sugawara on 7/30/20.
|
|
//
|
|
|
|
// TODO: shift key should change the label of the keys to uppercase (need callback mechanism?)
|
|
// pan gesture to outer edges of keyboard view for better dragging
|
|
|
|
@objc protocol EmulatorKeyboardKeyPressedDelegate: AnyObject {
|
|
func keyDown(_ key: KeyCoded)
|
|
func keyUp(_ key: KeyCoded)
|
|
}
|
|
|
|
@objc protocol EmulatorKeyboardModifierPressedDelegate: AnyObject {
|
|
func modifierPressedWithKey(_ key: KeyCoded, enable: Bool)
|
|
func isModifierEnabled(key: KeyCoded) -> Bool
|
|
}
|
|
|
|
protocol EmulatorKeyboardViewDelegate: AnyObject {
|
|
func toggleAlternateKeys()
|
|
func refreshModifierStates()
|
|
func updateTransparency(toAlpha alpha: Float)
|
|
}
|
|
|
|
class EmulatorKeyboardView: UIView {
|
|
|
|
static var keyboardBackgroundColor = UIColor.systemGray6.withAlphaComponent(0.5)
|
|
static var keyboardCornerRadius = 6.0
|
|
static var keyboardDragColor = UIColor.systemGray
|
|
|
|
static var keyCornerRadius = 6.0
|
|
static var keyBorderWidth = 1.0
|
|
|
|
static var rowSpacing = 12.0
|
|
static var keySpacing = 8.0
|
|
|
|
static var keyNormalFont = UIFont.systemFont(ofSize: 12)
|
|
static var keyPressedFont = UIFont.boldSystemFont(ofSize: 24)
|
|
|
|
static var keyNormalBackgroundColor = UIColor.systemGray4.withAlphaComponent(0.5)
|
|
static var keyNormalBorderColor = keyNormalBackgroundColor
|
|
static var keyNormalTextColor = UIColor.label
|
|
|
|
static var keyPressedBackgroundColor = UIColor.systemGray2
|
|
static var keyPressedBorderColor = keyPressedBackgroundColor
|
|
static var keyPressedTextColor = UIColor.label
|
|
|
|
static var keySelectedBackgroundColor = UIColor.systemGray2.withAlphaComponent(0.8)
|
|
static var keySelectedBorderColor = keySelectedBackgroundColor
|
|
static var keySelectedTextColor = UIColor.label
|
|
|
|
var viewModel = EmulatorKeyboardViewModel(keys: [[KeyCoded]]()) {
|
|
didSet {
|
|
setupWithModel(viewModel)
|
|
}
|
|
}
|
|
var modifierButtons = Set<EmulatorKeyboardButton>()
|
|
|
|
weak var delegate: EmulatorKeyboardViewDelegate?
|
|
|
|
private lazy var keyRowsStackView: UIStackView = {
|
|
let stackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.distribution = .equalCentering
|
|
stackView.spacing = Self.rowSpacing
|
|
return stackView
|
|
}()
|
|
|
|
private lazy var alternateKeyRowsStackView: UIStackView = {
|
|
let stackView = UIStackView()
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
|
stackView.axis = .vertical
|
|
stackView.distribution = .equalCentering
|
|
stackView.spacing = Self.rowSpacing
|
|
stackView.isHidden = true
|
|
return stackView
|
|
}()
|
|
|
|
let dragMeView: UIView = {
|
|
let view = UIView(frame: .zero)
|
|
view.backgroundColor = EmulatorKeyboardView.keyboardDragColor
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
view.widthAnchor.constraint(equalToConstant: 80).isActive = true
|
|
view.heightAnchor.constraint(equalToConstant: 2).isActive = true
|
|
let outerView = UIView(frame: .zero)
|
|
outerView.backgroundColor = .clear
|
|
outerView.translatesAutoresizingMaskIntoConstraints = false
|
|
outerView.addSubview(view)
|
|
view.centerXAnchor.constraint(equalTo: outerView.centerXAnchor).isActive = true
|
|
view.centerYAnchor.constraint(equalTo: outerView.centerYAnchor).isActive = true
|
|
outerView.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
|
outerView.widthAnchor.constraint(equalToConstant: 100).isActive = true
|
|
return outerView
|
|
}()
|
|
|
|
private var pressedKeyViews = [UIControl: UIView]()
|
|
|
|
convenience init() {
|
|
self.init(frame: CGRect.zero)
|
|
}
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
commonInit()
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
commonInit()
|
|
}
|
|
|
|
private func commonInit() {
|
|
backgroundColor = Self.keyboardBackgroundColor
|
|
layer.cornerRadius = Self.keyboardCornerRadius
|
|
layoutMargins = UIEdgeInsets(top: 16, left: 4, bottom: 16, right: 4)
|
|
insetsLayoutMarginsFromSafeArea = false
|
|
addSubview(keyRowsStackView)
|
|
keyRowsStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
|
keyRowsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 4.0).isActive = true
|
|
keyRowsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -4.0).isActive = true
|
|
addSubview(alternateKeyRowsStackView)
|
|
alternateKeyRowsStackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
|
|
alternateKeyRowsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 4.0).isActive = true
|
|
alternateKeyRowsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor, constant: -4.0).isActive = true
|
|
addSubview(dragMeView)
|
|
dragMeView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
|
|
dragMeView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
|
}
|
|
|
|
|
|
@objc private func keyPressed(_ sender: EmulatorKeyboardButton) {
|
|
if sender.key.keyCode == 9000 { // hack for now
|
|
return
|
|
}
|
|
if !sender.key.isModifier {
|
|
// make a "stand-in" for our key, and scale up key
|
|
let view = UIView()
|
|
view.backgroundColor = EmulatorKeyboardView.keyPressedBackgroundColor
|
|
view.layer.cornerRadius = EmulatorKeyboardView.keyCornerRadius
|
|
view.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
|
|
view.frame = sender.convert(sender.bounds, to: self)
|
|
addSubview(view)
|
|
|
|
var tx = 0.0
|
|
let ty = sender.bounds.height * -1.20
|
|
|
|
if let window = self.window {
|
|
let rect = sender.convert(sender.bounds, to:window)
|
|
|
|
if rect.maxX > window.bounds.width * 0.9 {
|
|
tx = sender.bounds.width * -0.5
|
|
}
|
|
if rect.minX < window.bounds.width * 0.1 {
|
|
tx = sender.bounds.width * 0.5
|
|
}
|
|
}
|
|
|
|
sender.superview!.bringSubviewToFront(sender)
|
|
sender.transform = CGAffineTransform(translationX:tx, y:ty).scaledBy(x:2, y:2)
|
|
|
|
pressedKeyViews[sender] = view
|
|
}
|
|
viewModel.keyPressed(sender.key)
|
|
}
|
|
|
|
@objc private func keyCancelled(_ sender: EmulatorKeyboardButton) {
|
|
sender.transform = .identity
|
|
if let view = pressedKeyViews[sender] {
|
|
view.removeFromSuperview()
|
|
pressedKeyViews.removeValue(forKey: sender)
|
|
}
|
|
}
|
|
|
|
@objc private func keyReleased(_ sender: EmulatorKeyboardButton) {
|
|
sender.transform = .identity
|
|
if sender.key.keyCode == 9000 {
|
|
delegate?.toggleAlternateKeys()
|
|
return
|
|
}
|
|
if let view = pressedKeyViews[sender] {
|
|
view.removeFromSuperview()
|
|
pressedKeyViews.removeValue(forKey: sender)
|
|
}
|
|
sender.isSelected = viewModel.modifierKeyToggleStateForKey(sender.key)
|
|
viewModel.keyReleased(sender.key)
|
|
self.delegate?.refreshModifierStates()
|
|
}
|
|
|
|
func setupWithModel(_ model: EmulatorKeyboardViewModel) {
|
|
for row in model.keys {
|
|
let keysInRow = createKeyRow(keys: row)
|
|
keyRowsStackView.addArrangedSubview(keysInRow)
|
|
}
|
|
if let altKeys = model.alternateKeys {
|
|
for row in altKeys {
|
|
let keysInRow = createKeyRow(keys: row)
|
|
alternateKeyRowsStackView.addArrangedSubview(keysInRow)
|
|
}
|
|
}
|
|
if !model.isDraggable {
|
|
dragMeView.isHidden = true
|
|
}
|
|
}
|
|
|
|
func toggleKeysStackView() {
|
|
if viewModel.alternateKeys != nil {
|
|
keyRowsStackView.isHidden.toggle()
|
|
alternateKeyRowsStackView.isHidden.toggle()
|
|
refreshModifierStates()
|
|
}
|
|
}
|
|
|
|
func refreshModifierStates() {
|
|
modifierButtons.forEach{ button in
|
|
button.isSelected = viewModel.modifierKeyToggleStateForKey(button.key)
|
|
}
|
|
}
|
|
|
|
private func createKey(_ keyCoded: KeyCoded) -> UIButton {
|
|
let key = EmulatorKeyboardButton(key: keyCoded)
|
|
if let imageName = keyCoded.keyImageName {
|
|
key.tintColor = EmulatorKeyboardView.keyNormalTextColor
|
|
key.setImage(UIImage(systemName: imageName), for: .normal)
|
|
if let highlightedImageName = keyCoded.keyImageNameHighlighted {
|
|
key.setImage(UIImage(systemName: highlightedImageName), for: .highlighted)
|
|
key.setImage(UIImage(systemName: highlightedImageName), for: .selected)
|
|
}
|
|
} else {
|
|
key.setTitle(keyCoded.keyLabel, for: .normal)
|
|
key.titleLabel?.font = EmulatorKeyboardView.keyNormalFont
|
|
key.setTitleColor(EmulatorKeyboardView.keyNormalTextColor, for: .normal)
|
|
key.setTitleColor(EmulatorKeyboardView.keySelectedTextColor, for: .selected)
|
|
key.setTitleColor(EmulatorKeyboardView.keyPressedTextColor, for: .highlighted)
|
|
}
|
|
|
|
key.translatesAutoresizingMaskIntoConstraints = false
|
|
key.widthAnchor.constraint(equalToConstant: (25 * CGFloat(keyCoded.keySize.rawValue))).isActive = true
|
|
key.heightAnchor.constraint(equalToConstant: 35).isActive = true
|
|
key.backgroundColor = EmulatorKeyboardView.keyNormalBackgroundColor
|
|
key.layer.borderWidth = EmulatorKeyboardView.keyBorderWidth
|
|
key.layer.borderColor = EmulatorKeyboardView.keyNormalBorderColor.cgColor
|
|
key.layer.cornerRadius = EmulatorKeyboardView.keyCornerRadius
|
|
key.addTarget(self, action: #selector(keyPressed(_:)), for: .touchDown)
|
|
key.addTarget(self, action: #selector(keyReleased(_:)), for: .touchUpInside)
|
|
key.addTarget(self, action: #selector(keyReleased(_:)), for: .touchUpOutside)
|
|
key.addTarget(self, action: #selector(keyCancelled(_:)), for: .touchCancel)
|
|
if keyCoded.isModifier {
|
|
modifierButtons.update(with: key)
|
|
}
|
|
return key
|
|
}
|
|
|
|
private func createKeyRow(keys: [KeyCoded]) -> UIStackView {
|
|
let subviews: [UIView] = keys.enumerated().map { index, keyCoded -> UIView in
|
|
if keyCoded is SpacerKey {
|
|
let spacer = UIView()
|
|
spacer.widthAnchor.constraint(equalToConstant: 25.0 * CGFloat(keyCoded.keySize.rawValue)).isActive = true
|
|
spacer.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
|
|
return spacer
|
|
} else if let sliderKey = keyCoded as? SliderKey {
|
|
sliderKey.keyboardView = self
|
|
return sliderKey.createView()
|
|
}
|
|
return createKey(keyCoded)
|
|
}
|
|
let stack = UIStackView(arrangedSubviews: subviews)
|
|
stack.axis = .horizontal
|
|
stack.distribution = .fill
|
|
stack.spacing = 8
|
|
return stack
|
|
}
|
|
}
|
|
|
|
extension UIImage {
|
|
static func dot(size:CGSize, color:UIColor) -> UIImage {
|
|
return UIGraphicsImageRenderer(size: size).image { context in
|
|
context.cgContext.setFillColor(color.cgColor)
|
|
context.cgContext.fillEllipse(in: CGRect(origin:.zero, size:size))
|
|
}
|
|
}
|
|
}
|