diff --git a/RollkofferSimulator/AppDelegate.swift b/RollkofferSimulator/AppDelegate.swift index 5a9cbe4..85a1aea 100644 --- a/RollkofferSimulator/AppDelegate.swift +++ b/RollkofferSimulator/AppDelegate.swift @@ -14,6 +14,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + #if targetEnvironment(macCatalyst) + // Configure for macOS + configureMacOS() + #endif return true } @@ -33,10 +37,60 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationDidBecomeActive(_ application: UIApplication) { // Resume game if needed } + + #if targetEnvironment(macCatalyst) + // MARK: - macOS Configuration + private func configureMacOS() { + // Set minimum window size for macOS + UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.forEach { windowScene in + windowScene.sizeRestrictions?.minimumSize = CGSize(width: 400, height: 600) + windowScene.sizeRestrictions?.maximumSize = CGSize(width: 600, height: 900) + } + } + + override func buildMenu(with builder: UIMenuBuilder) { + super.buildMenu(with: builder) + + // Remove unnecessary menus for a game + builder.remove(menu: .format) + builder.remove(menu: .edit) + + // Add Game menu + let pauseCommand = UIKeyCommand( + title: "Pause", + action: #selector(handlePauseCommand), + input: "p", + modifierFlags: .command + ) + + let restartCommand = UIKeyCommand( + title: "Neustart", + action: #selector(handleRestartCommand), + input: "r", + modifierFlags: .command + ) + + let gameMenu = UIMenu( + title: "Spiel", + children: [pauseCommand, restartCommand] + ) + + builder.insertSibling(gameMenu, afterMenu: .file) + } + + @objc private func handlePauseCommand() { + NotificationCenter.default.post(name: .pauseGame, object: nil) + } + + @objc private func handleRestartCommand() { + NotificationCenter.default.post(name: .restartGame, object: nil) + } + #endif } // MARK: - Notification Names extension Notification.Name { static let pauseGame = Notification.Name("pauseGame") static let resumeGame = Notification.Name("resumeGame") + static let restartGame = Notification.Name("restartGame") } diff --git a/RollkofferSimulator/GameViewController.swift b/RollkofferSimulator/GameViewController.swift index 9d0e5a3..f755f34 100644 --- a/RollkofferSimulator/GameViewController.swift +++ b/RollkofferSimulator/GameViewController.swift @@ -36,6 +36,10 @@ class GameViewController: UIViewController { // Setup notification observers setupNotificationObservers() + + #if targetEnvironment(macCatalyst) + setupMacCatalyst() + #endif } private func setupNotificationObservers() { @@ -45,6 +49,13 @@ class GameViewController: UIViewController { name: .pauseGame, object: nil ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handleRestartNotification), + name: .restartGame, + object: nil + ) } @objc private func handlePauseNotification() { @@ -57,12 +68,46 @@ class GameViewController: UIViewController { // This is just a notification that the app is going to background } + @objc private func handleRestartNotification() { + guard let skView = self.view as? SKView else { return } + + let menuScene = MenuScene(size: skView.bounds.size) + menuScene.scaleMode = .aspectFill + + let transition = SKTransition.fade(withDuration: 0.5) + skView.presentScene(menuScene, transition: transition) + } + + #if targetEnvironment(macCatalyst) + private func setupMacCatalyst() { + // Configure window appearance for macOS + if let windowScene = view.window?.windowScene { + windowScene.title = "Rollkoffer Simulator" + + // Set window style + if let titlebar = windowScene.titlebar { + titlebar.titleVisibility = .visible + titlebar.toolbarStyle = .unified + } + } + } + + // Enable keyboard input + override var canBecomeFirstResponder: Bool { + return true + } + #endif + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + #if targetEnvironment(macCatalyst) + return .all + #else if UIDevice.current.userInterfaceIdiom == .phone { return .portrait } else { return .all } + #endif } override var prefersStatusBarHidden: Bool { diff --git a/RollkofferSimulator/Info.plist b/RollkofferSimulator/Info.plist index c05f1e7..8f5d56d 100644 --- a/RollkofferSimulator/Info.plist +++ b/RollkofferSimulator/Info.plist @@ -50,5 +50,9 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + LSMinimumSystemVersion + 13.0 + NSHumanReadableCopyright + Copyright 2024 Ingo K. All rights reserved. diff --git a/RollkofferSimulator/RollkofferSimulator.xcodeproj/project.pbxproj b/RollkofferSimulator/RollkofferSimulator.xcodeproj/project.pbxproj index 0531fdd..2069378 100644 --- a/RollkofferSimulator/RollkofferSimulator.xcodeproj/project.pbxproj +++ b/RollkofferSimulator/RollkofferSimulator.xcodeproj/project.pbxproj @@ -381,6 +381,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -393,9 +394,12 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -409,6 +413,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -421,9 +426,12 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.ingok.RollkofferSimulator; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/RollkofferSimulator/Scenes/GameOverScene.swift b/RollkofferSimulator/Scenes/GameOverScene.swift index 6d66b8f..4d8b0c6 100644 --- a/RollkofferSimulator/Scenes/GameOverScene.swift +++ b/RollkofferSimulator/Scenes/GameOverScene.swift @@ -223,6 +223,27 @@ class GameOverScene: SKScene { } } + // MARK: - Keyboard Handling (macOS) + #if targetEnvironment(macCatalyst) + override var canBecomeFirstResponder: Bool { true } + + override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard let key = presses.first?.key else { + super.pressesBegan(presses, with: event) + return + } + + switch key.keyCode { + case .keyboardSpacebar, .keyboardReturnOrEnter: + retryGame() + case .keyboardEscape: + returnToMenu() + default: + super.pressesBegan(presses, with: event) + } + } + #endif + private func retryGame() { let pressDown = SKAction.scale(to: 0.9, duration: 0.1) let pressUp = SKAction.scale(to: 1.0, duration: 0.1) diff --git a/RollkofferSimulator/Scenes/GameScene.swift b/RollkofferSimulator/Scenes/GameScene.swift index 1df64d7..7db94cf 100644 --- a/RollkofferSimulator/Scenes/GameScene.swift +++ b/RollkofferSimulator/Scenes/GameScene.swift @@ -318,6 +318,29 @@ class GameScene: SKScene { isDragging = false } + // MARK: - Keyboard Handling (macOS) + #if targetEnvironment(macCatalyst) + override var canBecomeFirstResponder: Bool { true } + + override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard let key = presses.first?.key else { + super.pressesBegan(presses, with: event) + return + } + + switch key.keyCode { + case .keyboardEscape: + togglePause() + case .keyboardSpacebar: + if gameState.currentState == .paused { + resumeGame() + } + default: + super.pressesBegan(presses, with: event) + } + } + #endif + // MARK: - Pause Handling private func togglePause() { if gameState.currentState == .playing { diff --git a/RollkofferSimulator/Scenes/MenuScene.swift b/RollkofferSimulator/Scenes/MenuScene.swift index 7a650ce..074984e 100644 --- a/RollkofferSimulator/Scenes/MenuScene.swift +++ b/RollkofferSimulator/Scenes/MenuScene.swift @@ -245,6 +245,25 @@ class MenuScene: SKScene { } } + // MARK: - Keyboard Handling (macOS) + #if targetEnvironment(macCatalyst) + override var canBecomeFirstResponder: Bool { true } + + override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard let key = presses.first?.key else { + super.pressesBegan(presses, with: event) + return + } + + switch key.keyCode { + case .keyboardSpacebar, .keyboardReturnOrEnter: + startGame() + default: + super.pressesBegan(presses, with: event) + } + } + #endif + private func startGame() { // Button press effect let pressDown = SKAction.scale(to: 0.9, duration: 0.1) diff --git a/RollkofferSimulator/Scenes/VictoryScene.swift b/RollkofferSimulator/Scenes/VictoryScene.swift index c6f4fa4..82910d9 100644 --- a/RollkofferSimulator/Scenes/VictoryScene.swift +++ b/RollkofferSimulator/Scenes/VictoryScene.swift @@ -280,6 +280,27 @@ class VictoryScene: SKScene { } } + // MARK: - Keyboard Handling (macOS) + #if targetEnvironment(macCatalyst) + override var canBecomeFirstResponder: Bool { true } + + override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { + guard let key = presses.first?.key else { + super.pressesBegan(presses, with: event) + return + } + + switch key.keyCode { + case .keyboardSpacebar, .keyboardReturnOrEnter: + playAgain() + case .keyboardEscape: + returnToMenu() + default: + super.pressesBegan(presses, with: event) + } + } + #endif + private func playAgain() { let pressDown = SKAction.scale(to: 0.9, duration: 0.1) let pressUp = SKAction.scale(to: 1.0, duration: 0.1)