Path: blob/a-new-beginning/Folium-iOS/Classes/ControllerView/ControllerButton.swift
2 views
//
// ControllerButton.swift
// Folium-iOS
//
// Created by Jarrod Norwell on 9/6/2024.
//
import Foundation
import GameController
import UIKit
protocol ControllerButtonDelegate {
func touchBegan(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex)
func touchEnded(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex)
func touchMoved(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex)
func touchBegan(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex) async
func touchEnded(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex) async
func touchMoved(with type: Button.`Type`, playerIndex: GCControllerPlayerIndex) async
}
enum ButtonClass : String {
case blurredButton = "blurredButton"
case borderedButton = "borderedButton"
case defaultButton = "defaultButton"
}
class ControllerButton : UIView {
var button: Button
var skin: Skin
var delegate: ControllerButtonDelegate? = nil
init(button: Button, skin: Skin, delegate: ControllerButtonDelegate? = nil) {
self.button = button
self.skin = skin
self.delegate = delegate
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func touchUpInside() {
guard let delegate else {
return
}
if [.grape, .guava].contains(skin.core) {
delegate.touchEnded(with: button.type, playerIndex: .index1)
} else {
Task {
await delegate.touchEnded(with: button.type, playerIndex: .index1)
}
}
}
@objc func touchDown() {
guard let delegate else {
return
}
if let vibrateOnTap = button.vibrateOnTap, vibrateOnTap {
UIImpactFeedbackGenerator().impactOccurred()
}
if [.grape, .guava].contains(skin.core) {
delegate.touchBegan(with: button.type, playerIndex: .index1)
} else {
Task {
await delegate.touchBegan(with: button.type, playerIndex: .index1)
}
}
}
}
class BlurredButton : ControllerButton {
fileprivate var visualEffectView: UIVisualEffectView? = nil
fileprivate var imageView: UIImageView? = nil
fileprivate var label: UILabel? = nil
override init(button: Button, skin: Skin, delegate: (any ControllerButtonDelegate)? = nil) {
super.init(button: button, skin: skin, delegate: delegate)
if #available(iOS 26, *) {
var configuration: UIButton.Configuration = .glass()
configuration.buttonSize = .medium
configuration.cornerStyle = .capsule
if [.l, .r, .zl, .zr].contains(button.type) || skin.core.nintendo && [.a, .b, .x, .y].contains(button.type) {
configuration.attributedTitle = .init(button.letter(for: skin.core) ?? "", attributes: .init([
.font : UIFont.systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2)
.pointSize, weight: .medium)
]))
} else {
configuration.image = if [.minus, .home, .plus, .settings, .loadState, .saveState].contains(button.type) {
button.image(for: skin.core)?
.applyingSymbolConfiguration(.init(scale: .small))?
.applyingSymbolConfiguration(.init(weight: .bold))
} else {
button.image(for: skin.core)?
.applyingSymbolConfiguration(.init(scale: .medium))?
.applyingSymbolConfiguration(.init(weight: .bold))
}
}
let button: UIButton = .init(configuration: configuration)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
addSubview(button)
button.topAnchor.constraint(equalTo: topAnchor).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
} else {
let effect: UIBlurEffect = .init(style: .systemMaterial)
var configuration: UIButton.Configuration = .plain()
configuration.baseForegroundColor = .label
configuration.buttonSize = .medium
configuration.cornerStyle = .capsule
if [.l, .r, .zl, .zr].contains(button.type) || skin.core.nintendo && [.a, .b, .x, .y].contains(button.type) {
configuration.attributedTitle = .init(button.letter(for: skin.core) ?? "", attributes: .init([
.font : UIFont.systemFont(ofSize: UIFont.preferredFont(forTextStyle: .title2)
.pointSize, weight: .medium)
]))
} else {
configuration.image = if [.minus, .home, .plus, .settings, .loadState, .saveState].contains(button.type) {
button.image(for: skin.core)?
.applyingSymbolConfiguration(.init(scale: .small))?
.applyingSymbolConfiguration(.init(weight: .bold))
} else {
button.image(for: skin.core)?
.applyingSymbolConfiguration(.init(scale: .medium))?
.applyingSymbolConfiguration(.init(weight: .bold))
}
}
configuration.background.visualEffect = effect
let button: UIButton = .init(configuration: configuration)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
button.addTarget(self, action: #selector(touchDown), for: .touchDown)
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowOpacity = 1 / 5
button.layer.shadowRadius = 20
button.layer.shadowOffset = .init(width: 0, height: 10)
addSubview(button)
button.topAnchor.constraint(equalTo: topAnchor).isActive = true
button.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
button.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class BorderedButton : ControllerButton {
override init(button: Button, skin: Skin, delegate: (any ControllerButtonDelegate)? = nil) {
super.init(button: button, skin: skin, delegate: delegate)
layer.borderColor = UIColor.white.cgColor
layer.borderWidth = 2
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
switch button.type {
case .l, .zl, .r, .zr:
layer.cornerRadius = frame.height / 3
default:
layer.cornerRadius = frame.height / 2
}
}
}
class DefaultButton : ControllerButton {
fileprivate var imageView: UIImageView? = nil
override init(button: Button, skin: Skin, delegate: (any ControllerButtonDelegate)? = nil) {
super.init(button: button, skin: skin, delegate: delegate)
imageView = .init()
guard let imageView else {
return
}
if let debugging = skin.debugging, debugging {
imageView.backgroundColor = .systemRed.withAlphaComponent(1 / 3)
}
if let backgroundImageName = button.backgroundImageName, let url = skin.url {
imageView.image = .init(contentsOfFile: url
.appendingPathComponent("buttons", conformingTo: .folder)
.appendingPathComponent(backgroundImageName, conformingTo: .fileURL)
.path
)
} else {
if let transparent = button.transparent, transparent {
imageView.image = nil
} else {
imageView.image = button.image(for: skin.core)?
.applyingSymbolConfiguration(.init(paletteColors: [.black, .white]))
}
}
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFill
addSubview(imageView)
imageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}