Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Controllers/EmulationControllers/CherryController.swift
2 views
//
//  CherryController.swift
//  Folium
//
//  Created by Jarrod Norwell on 20/1/2026.
//

import Cherry
import GameController
import UIKit

class CherryController : UIViewController {
    var imageView: UIImageView? = nil,
        secondaryImageView: UIImageView? = nil
    
    var visualEffectView: UIVisualEffectView? = nil,
        glassVisualEffectView: UIVisualEffectView? = nil
    
    var settingsButton: UIButton? = nil,
        selectButton: UIButton? = nil,
        startButton: UIButton? = nil
    
    var asterixButton: UIButton? = nil,
        zeroButton: UIButton? = nil,
        hashtagButton: UIButton? = nil,
        sevenButton: UIButton? = nil,
        eightButton: UIButton? = nil,
        nineButton: UIButton? = nil,
        fourButton: UIButton? = nil,
        fiveButton: UIButton? = nil,
        sixButton: UIButton? = nil,
        oneButton: UIButton? = nil,
        twoButton: UIButton? = nil,
        threeButton: UIButton? = nil
    
    var thumbstick: LatestControllerThumbstick? = nil
    
    var portraitBottomConstraint: NSLayoutConstraint? = nil
    var landscapeLeftConstraint: NSLayoutConstraint? = nil,
        landscapeRightConstraint: NSLayoutConstraint? = nil
    var constraints: (portrait: [NSLayoutConstraint], landscape: [NSLayoutConstraint]) = ([], [])
    
    var game: NewCherryGame
    var cherry: Cherry
    init(_ game: NewCherryGame, _ cherry: Cherry) {
        self.game = game
        self.cherry = cherry
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        secondaryImageView = UIImageView()
        guard let secondaryImageView else {
            return
        }
        secondaryImageView.translatesAutoresizingMaskIntoConstraints = false
        secondaryImageView.backgroundColor = .label
        view.addSubview(secondaryImageView)
        
        visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
        guard let visualEffectView else {
            return
        }
        visualEffectView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(visualEffectView)
        
        if #available(iOS 26, *) {
            let effect: UIGlassContainerEffect = UIGlassContainerEffect()
            effect.spacing = 20
            
            glassVisualEffectView = UIVisualEffectView(effect: effect)
            guard let glassVisualEffectView else {
                return
            }
            glassVisualEffectView.translatesAutoresizingMaskIntoConstraints = false
            visualEffectView.contentView.addSubview(glassVisualEffectView)
        }
        
        let viewToAddSubviews: UIView = glassVisualEffectView?.contentView ?? visualEffectView.contentView
        
        imageView = UIImageView()
        guard let imageView else {
            return
        }
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .label
        if #available(iOS 26, *) {
            imageView.clipsToBounds = true
            imageView.cornerConfiguration = .corners(radius: .fixed(15.0))
        } else {
            imageView.clipsToBounds = true
            imageView.layer.cornerCurve = .continuous
            imageView.layer.cornerRadius = 15.0
        }
        viewToAddSubviews.addSubview(imageView)
        
        let settingsConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "ellipsis"), .medium)
        settingsButton = .button(with: settingsConfiguration,
                                 actions: ({ _ in }, { _ in }), UIMenu(children: [
                                    UIMenu(options: .displayInline, children: [
                                        UIDeferredMenuElement.uncached { completion in
                                            Task {
                                                completion([
                                                    UIAction(title: "Stop & Exit", image: UIImage(systemName: "stop"), attributes: .destructive) { _ in
                                                        let alertController: UIAlertController = UIAlertController(title: "Stop & Exit",
                                                                                                                   message: "Are you sure you want to stop & exit? Unsaved progress will be lost",
                                                                                                                   preferredStyle: .alert)
                                                        alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
                                                        alertController.addAction(UIAlertAction(title: "Stop & Exit", style: .destructive, handler: { _ in
                                                            Task {
                                                                await self.cherry.stop()
                                                                self.dismiss(animated: true)
                                                            }
                                                        }))
                                                        self.present(alertController, animated: true)
                                                    },
                                                    UIAction(title: await self.cherry.isPaused ? "Resume" : "Pause",
                                                             image: await self.cherry.isPaused ? UIImage(systemName: "play") : UIImage(systemName: "pause")) { _ in
                                                                 Task {
                                                                     await self.cherry.pause(await !self.cherry.isPaused)
                                                                 }
                                                             }
                                                ])
                                            }
                                        }
                                    ]),
                                    UIMenu(options: .displayInline, children: [
                                        UIMenu(title: "Delete State", image: UIImage(systemName: "minus.circle"), options: .destructive, preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Cherry").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? .destructive : .disabled) { _ in
                                                        Task {
                                                            try FileManager.default.removeItem(at: url)
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(title: "Save State", image: .init(systemName: "arrow.down.circle"), preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Cherry").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle")) { _ in
                                                        if fileExists {
                                                            let alertController: UIAlertController = UIAlertController(title: "Overwrite?",
                                                                                                                       message: "Are you sure you want to overwrite? This action is irreversible",
                                                                                                                       preferredStyle: .alert)
                                                            alertController.addAction(UIAlertAction(title: .localized(for: .dismiss), style: .cancel))
                                                            alertController.addAction(UIAlertAction(title: "Overwrite", style: .destructive, handler: { _ in
                                                                Task {
                                                                    // await self.cherry.save(state: url)
                                                                }
                                                            }))
                                                            self.present(alertController, animated: true)
                                                        } else {
                                                            Task {
                                                                // await self.cherry.save(state: url)
                                                            }
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(title: "Load State", image: .init(systemName: "arrow.up.circle"), preferredElementSize: .small, children: [
                                            UIDeferredMenuElement.uncached { completion in
                                                guard let documentDirectory: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
                                                    completion([])
                                                    return
                                                }
                                                
                                                let statesDirectory: URL = documentDirectory.appending(component: "Cherry").appending(component: "states")
                                                
                                                let stateNumbers: [Int] = [0, 1, 2]
                                                
                                                let elements: [UIAction] = stateNumbers.map { state in
                                                    let url: URL = statesDirectory.appending(component: "\(self.game.details.name)_\(state).state")
                                                    let fileExists: Bool = FileManager.default.fileExists(atPath: url.path)
                                                    
                                                    return .init(image: UIImage(systemName: "\(state + 1).circle"), attributes: fileExists ? [] : .disabled) { _ in
                                                        Task {
                                                            // await self.cherry.load(state: url)
                                                        }
                                                    }
                                                }
                                                
                                                completion(elements)
                                            }
                                        ]),
                                        UIMenu(options: .displayInline, children: [
                                            UIAction(title: "Capture Still Image", image: UIImage(systemName: "photo.badge.arrow.down")) { _ in
                                                Task {
                                                    await self.cherry.pause(true)
                                                    
                                                    guard let imageView: UIImageView = self.imageView, let image: UIImage = imageView.image else {
                                                        return
                                                    }
                                                    
                                                    UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.image(_:didFinishSavingWithError:contextInfo:)), nil)
                                                    
                                                    await self.cherry.pause(false)
                                                }
                                            }
                                        ])
                                    ])
                                 ]))
        guard let settingsButton else {
            return
        }
        viewToAddSubviews.addSubview(settingsButton)
        
        let selectConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "minus"), .medium)
        selectButton = .button(with: selectConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_left_button, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_left_button, player: 0, pressed: false)
        }))
        guard let selectButton else {
            return
        }
        viewToAddSubviews.addSubview(selectButton)
        
        let startConfiguration: UIButton.Configuration = .configuration(.medium, .capsule, UIImage(systemName: "plus"), .medium)
        startButton = .button(with: startConfiguration,
                              actions: ({ _ in
            await self.cherry.button(button: .k_right_button, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_right_button, player: 0, pressed: false)
        }))
        guard let startButton else {
            return
        }
        viewToAddSubviews.addSubview(startButton)
        
        // TODO: buttons
        let asterixConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "circle"), .large)
        asterixButton = .button(with: asterixConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_asterisk, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_asterisk, player: 0, pressed: false)
        }))
        guard let asterixButton else {
            return
        }
        viewToAddSubviews.addSubview(asterixButton)
        
        let zeroConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "0.circle.fill"), .large)
        zeroButton = .button(with: zeroConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_0, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_0, player: 0, pressed: false)
        }))
        guard let zeroButton else {
            return
        }
        viewToAddSubviews.addSubview(zeroButton)
        
        let hashtagConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "number"), .large)
        hashtagButton = .button(with: hashtagConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_hash, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_hash, player: 0, pressed: false)
        }))
        guard let hashtagButton else {
            return
        }
        viewToAddSubviews.addSubview(hashtagButton)
        
        let sevenConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "7.circle.fill"), .large)
        sevenButton = .button(with: sevenConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_7, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_7, player: 0, pressed: false)
        }))
        guard let sevenButton else {
            return
        }
        viewToAddSubviews.addSubview(sevenButton)
        
        let eightConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "8.circle.fill"), .large)
        eightButton = .button(with: eightConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_8, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_8, player: 0, pressed: false)
        }))
        guard let eightButton else {
            return
        }
        viewToAddSubviews.addSubview(eightButton)
        
        let nineConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "9.circle.fill"), .large)
        nineButton = .button(with: nineConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_9, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_9, player: 0, pressed: false)
        }))
        guard let nineButton else {
            return
        }
        viewToAddSubviews.addSubview(nineButton)
        
        let fourConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "4.circle.fill"), .large)
        fourButton = .button(with: fourConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_4, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_4, player: 0, pressed: false)
        }))
        guard let fourButton else {
            return
        }
        viewToAddSubviews.addSubview(fourButton)
        
        let fiveConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "5.circle.fill"), .large)
        fiveButton = .button(with: fiveConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_5, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_5, player: 0, pressed: false)
        }))
        guard let fiveButton else {
            return
        }
        viewToAddSubviews.addSubview(fiveButton)
        
        let sixConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "6.circle.fill"), .large)
        sixButton = .button(with: sixConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_6, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_6, player: 0, pressed: false)
        }))
        guard let sixButton else {
            return
        }
        viewToAddSubviews.addSubview(sixButton)
        
        let oneConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "1.circle.fill"), .large)
        oneButton = .button(with: oneConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_1, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_1, player: 0, pressed: false)
        }))
        guard let oneButton else {
            return
        }
        viewToAddSubviews.addSubview(oneButton)
        
        let twoConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "2.circle.fill"), .large)
        twoButton = .button(with: twoConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_2, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_2, player: 0, pressed: false)
        }))
        guard let twoButton else {
            return
        }
        viewToAddSubviews.addSubview(twoButton)
        
        let threeConfiguration: UIButton.Configuration = .configuration(.large, .capsule, UIImage(systemName: "3.circle.fill"), .large)
        threeButton = .button(with: threeConfiguration,
                               actions: ({ _ in
            await self.cherry.button(button: .k_3, player: 0, pressed: true)
        }, { _ in
            await self.cherry.button(button: .k_3, player: 0, pressed: false)
        }))
        guard let threeButton else {
            return
        }
        viewToAddSubviews.addSubview(threeButton)
        // TODO: buttons
        
        thumbstick = LatestControllerThumbstick(.left, self, 1 / 2)
        guard let thumbstick else {
            return
        }
        thumbstick.translatesAutoresizingMaskIntoConstraints = false
        viewToAddSubviews.addSubview(thumbstick)
        
        if iPhone {
            let padding: CGFloat = 50 * 3
            let divider: CGFloat = 2
            
            portraitBottomConstraint = secondaryImageView.bottomAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor)
            guard let portraitBottomConstraint else {
                return
            }
            
            constraints.portrait.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                secondaryImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                portraitBottomConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                    constant: -20.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                zeroButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                zeroButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                zeroButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                zeroButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                asterixButton.trailingAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                asterixButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                asterixButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                asterixButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                hashtagButton.leadingAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                hashtagButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                hashtagButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                hashtagButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                eightButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                eightButton.bottomAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                eightButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                eightButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                sevenButton.trailingAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                sevenButton.bottomAnchor.constraint(equalTo: asterixButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                sevenButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                sevenButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                nineButton.leadingAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                nineButton.bottomAnchor.constraint(equalTo: hashtagButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                nineButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                nineButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                fiveButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                fiveButton.bottomAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                fiveButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                fiveButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                fourButton.trailingAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                fourButton.bottomAnchor.constraint(equalTo: sevenButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                fourButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                fourButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                sixButton.leadingAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                sixButton.bottomAnchor.constraint(equalTo: nineButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                sixButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                sixButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                twoButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                twoButton.bottomAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                twoButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                twoButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                oneButton.trailingAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                oneButton.bottomAnchor.constraint(equalTo: fourButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                oneButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                oneButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                threeButton.leadingAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                threeButton.bottomAnchor.constraint(equalTo: sixButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                threeButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                threeButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                thumbstick.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
                                                constant: 20.0),
                thumbstick.bottomAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                thumbstick.widthAnchor.constraint(equalTo: thumbstick.safeAreaLayoutGuide.heightAnchor),
                thumbstick.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor)
            ])
            // 40 • 20 • 20 • 40 = 120
            
            if #available(iOS 26, *), let glassVisualEffectView {
                constraints.portrait.append(contentsOf: [
                    glassVisualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                    glassVisualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                    glassVisualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                    glassVisualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
                ])
            }
            
            landscapeLeftConstraint = secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor)
            landscapeRightConstraint = secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor)
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            constraints.landscape.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                landscapeLeftConstraint,
                landscapeRightConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                startButton.topAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.bottomAnchor,
                                                 constant: 20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                selectButton.bottomAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.topAnchor,
                                                     constant: -20.0),
                selectButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                      constant: 20.0),
                
                settingsButton.centerYAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerYAnchor),
                settingsButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.bottomAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.bottomAnchor,
                                                  constant: -20.0),
                imageView.widthAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.heightAnchor,
                                                 multiplier: 4.0 / 3.0)
            ])
            
            if #available(iOS 26, *), let glassVisualEffectView {
                constraints.landscape.append(contentsOf: [
                    glassVisualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                    glassVisualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                    glassVisualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                    glassVisualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
                ])
            }
        } else {
            let padding: CGFloat = 180 * 3
            let divider: CGFloat = 2
            
            portraitBottomConstraint = secondaryImageView.bottomAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor)
            guard let portraitBottomConstraint else {
                return
            }
            
            constraints.portrait.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                secondaryImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                portraitBottomConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.leadingAnchor,
                                                   constant: 20.0),
                imageView.trailingAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.trailingAnchor,
                                                    constant: -20.0),
                imageView.heightAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.widthAnchor,
                                                  multiplier: 3.0 / 4.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: settingsButton.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20.0),
                
                zeroButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                zeroButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                zeroButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                zeroButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                asterixButton.trailingAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                asterixButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                asterixButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                asterixButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                hashtagButton.leadingAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                hashtagButton.bottomAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                hashtagButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                hashtagButton.heightAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.widthAnchor),
                
                eightButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                eightButton.bottomAnchor.constraint(equalTo: zeroButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                eightButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                eightButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                sevenButton.trailingAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                sevenButton.bottomAnchor.constraint(equalTo: asterixButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                sevenButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                sevenButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                nineButton.leadingAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                nineButton.bottomAnchor.constraint(equalTo: hashtagButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                nineButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                nineButton.heightAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.widthAnchor),
                
                fiveButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                fiveButton.bottomAnchor.constraint(equalTo: eightButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                fiveButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                fiveButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                fourButton.trailingAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                fourButton.bottomAnchor.constraint(equalTo: sevenButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                fourButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                fourButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                sixButton.leadingAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                sixButton.bottomAnchor.constraint(equalTo: nineButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                sixButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                sixButton.heightAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.widthAnchor),
                
                twoButton.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                twoButton.bottomAnchor.constraint(equalTo: fiveButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                twoButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                twoButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                oneButton.trailingAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.leadingAnchor,
                                                        constant: -20.0),
                oneButton.bottomAnchor.constraint(equalTo: fourButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                oneButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                oneButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                threeButton.leadingAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                threeButton.bottomAnchor.constraint(equalTo: sixButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                threeButton.widthAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.widthAnchor, multiplier: 1 / 3, constant: -(padding / divider)),
                threeButton.heightAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.widthAnchor),
                
                thumbstick.topAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.bottomAnchor,
                                                constant: 20.0),
                thumbstick.bottomAnchor.constraint(equalTo: twoButton.safeAreaLayoutGuide.topAnchor,
                                                   constant: -20.0),
                thumbstick.widthAnchor.constraint(equalTo: thumbstick.safeAreaLayoutGuide.heightAnchor),
                thumbstick.centerXAnchor.constraint(equalTo: viewToAddSubviews.safeAreaLayoutGuide.centerXAnchor)
            ])
            
            if #available(iOS 26, *), let glassVisualEffectView {
                constraints.portrait.append(contentsOf: [
                    glassVisualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                    glassVisualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                    glassVisualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                    glassVisualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
                ])
            }
            
            landscapeLeftConstraint = secondaryImageView.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor)
            landscapeRightConstraint = secondaryImageView.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor)
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            constraints.landscape.append(contentsOf: [
                secondaryImageView.topAnchor.constraint(equalTo: view.topAnchor),
                secondaryImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                landscapeLeftConstraint,
                landscapeRightConstraint,
                
                visualEffectView.topAnchor.constraint(equalTo: view.topAnchor),
                visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                
                startButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                    constant: -20.0),
                startButton.leadingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.trailingAnchor,
                                                     constant: 20),
                
                selectButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                     constant: -20.0),
                selectButton.trailingAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.leadingAnchor,
                                                       constant: -20.0),
                
                settingsButton.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                       constant: -20.0),
                settingsButton.leadingAnchor.constraint(equalTo: startButton.safeAreaLayoutGuide.trailingAnchor,
                                                        constant: 20.0),
                
                imageView.topAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.topAnchor,
                                               constant: 20.0),
                imageView.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor,
                                                  constant: -20.0),
                imageView.centerXAnchor.constraint(equalTo: visualEffectView.contentView.safeAreaLayoutGuide.centerXAnchor),
                imageView.widthAnchor.constraint(equalTo: imageView.safeAreaLayoutGuide.heightAnchor,
                                                 multiplier: 4.0 / 3.0)
            ])
        }
        
        switch interfaceOrientation() {
        case .portrait:
            view.addConstraints(constraints.portrait)
        case .landscapeLeft, .landscapeRight:
            view.addConstraints(constraints.landscape)
        default:
            break
        }
        
        Task {
            _ = await cherry.insert(game.details.url)
            
            await cherry.framebuffer { framebuffer in
                guard let cgImage = CGImage.genericRGB888(framebuffer, 256, 192) else {
                    return
                }
                
                imageView.image = .init(cgImage: cgImage)
                secondaryImageView.image = .init(cgImage: cgImage)
            }
            
            await cherry.start()
        }
        
        Task {
            await GCController.startWirelessControllerDiscovery()
        }
        
        NotificationCenter.default.addObserver(forName: .applicationStateDidChange, object: nil, queue: .current) { notification in
            guard let applicationState: ApplicationState = notification.object as? ApplicationState else {
                return
            }
            
            Task {
                await self.cherry.pause(applicationState.shouldPause)
            }
        }
        
#if !targetEnvironment(simulator)
        NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .current) { notification in
            guard let controller: GCController = notification.object as? GCController,
                let _: GCExtendedGamepad = controller.extendedGamepad else {
                return
            }
            
            visualEffectView.contentView.subviews.filter { subview in
                subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
            }.forEach { button in
                UIView.animate(withDuration: 1 / 3) {
                    button.alpha = 1 / 3
                }
            }
            
            /*
            extendedGamepad.buttonA.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .a, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.buttonB.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .b, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.dpad.up.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .up, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.dpad.down.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .down, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.dpad.left.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .left, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.dpad.right.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .right, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.leftShoulder.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .l, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            extendedGamepad.rightShoulder.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .r, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
            
            if let buttonOptions = extendedGamepad.buttonOptions {
                buttonOptions.pressedChangedHandler = { element, value, pressed in
                    Task {
                        await self.cherry.button(button: .select, player: controller.playerIndex.rawValue, pressed: pressed)
                    }
                }
            }
            
            extendedGamepad.buttonMenu.pressedChangedHandler = { element, value, pressed in
                Task {
                    await self.cherry.button(button: .start, player: controller.playerIndex.rawValue, pressed: pressed)
                }
            }
             */
        }
        
        NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .current) { notification in
            visualEffectView.contentView.subviews.filter { subview in
                subview.isKind(of: UIButton.classForCoder()) && subview != settingsButton
            }.forEach { button in
                UIView.animate(withDuration: 1 / 3) {
                    button.alpha = 1
                }
            }
        }
#endif
    }
    
    override var prefersHomeIndicatorAutoHidden: Bool { true }
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .portrait }
    
    override func viewSafeAreaInsetsDidChange() {
        super.viewSafeAreaInsetsDidChange()
        switch interfaceOrientation() {
        case .portrait:
            guard let portraitBottomConstraint else {
                return
            }
            
            portraitBottomConstraint.constant = view.safeAreaInsets.top
        case .landscapeLeft:
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            landscapeLeftConstraint.constant = -view.safeAreaInsets.right
            landscapeRightConstraint.constant = view.safeAreaInsets.right
        case .landscapeRight:
            guard let landscapeLeftConstraint, let landscapeRightConstraint else {
                return
            }
            
            landscapeLeftConstraint.constant = -view.safeAreaInsets.left
            landscapeRightConstraint.constant = view.safeAreaInsets.left
        default:
            break
        }
        
        Task {
            view.setNeedsUpdateConstraints()
        }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        GCController.stopWirelessControllerDiscovery()
        NotificationCenter.default.removeObserver(self)
    }
    
    override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate { context in } completion: { context in
            switch self.interfaceOrientation() {
            case .portrait:
                self.view.removeConstraints(self.constraints.landscape)
                self.view.addConstraints(self.constraints.portrait)
            case .landscapeLeft, .landscapeRight:
                self.view.removeConstraints(self.constraints.portrait)
                self.view.addConstraints(self.constraints.landscape)
            default:
                break
            }
            
            self.view.setNeedsUpdateConstraints()
        }
    }
}

extension CherryController {
    @objc func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
        if #available(iOS 17.5, *), let settingsButton {
            UINotificationFeedbackGenerator(view: settingsButton).notificationOccurred(error == nil ? .success : .error)
        } else {
            UINotificationFeedbackGenerator().notificationOccurred(error == nil ? .success : .error)
        }
    }
}

extension CherryController : LatestControllerThumbstickDelegate {
    func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {}
    func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {}
    func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) {}
    
    func touchBegan(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        
    }
    
    func touchEnded(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        await cherry.button(button: .k_up, player: 0, pressed: false)
        await cherry.button(button: .k_down, player: 0, pressed: false)
        await cherry.button(button: .k_left, player: 0, pressed: false)
        await cherry.button(button: .k_right, player: 0, pressed: false)
    }
    
    func touchMoved(with thumbstick: LatestThumbstick, position: (x: Float, y: Float), playerIndex: GCControllerPlayerIndex) async {
        let deadZone: Float = 0.2
        let x = position.x
        let y = position.y
        
        // LEFT / RIGHT
        if x <= -deadZone {
            await cherry.button(button: .k_left,  player: 0, pressed: true)
        } else {
            await cherry.button(button: .k_left,  player: 0, pressed: false)
        }
        
        if x >= deadZone {
            await cherry.button(button: .k_right, player: 0, pressed: true)
        } else {
            await cherry.button(button: .k_right, player: 0, pressed: false)
        }
        
        // UP / DOWN
        if y >= deadZone {
            await cherry.button(button: .k_up,    player: 0, pressed: true)
        } else {
            await cherry.button(button: .k_up,    player: 0, pressed: false)
        }
        
        if y <= -deadZone {
            await cherry.button(button: .k_down,  player: 0, pressed: true)
        } else {
            await cherry.button(button: .k_down,  player: 0, pressed: false)
        }
    }
}