From a87fea63ade2ba393084fea46ca28bc94441e31d Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 16 Nov 2023 17:44:55 -0600 Subject: [PATCH] Shows AirPlay icon + message when AirPlaying --- Delta.xcodeproj/project.pbxproj | 4 ++ Delta/Emulation/GameViewController.swift | 24 +++++++- Delta/Extensions/GameView+AirPlay.swift | 77 ++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 Delta/Extensions/GameView+AirPlay.swift diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index e751885..b93f649 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -209,6 +209,7 @@ D5CDCCF12A859E7500E22131 /* ReviewSaveStatesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A1375A2A7D8F2600AB1372 /* ReviewSaveStatesViewController.swift */; }; D5CDCCF22A859E7500E22131 /* RepairDatabaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A137442A7D814000AB1372 /* RepairDatabaseViewController.swift */; }; D5CDCCF32A859E7500E22131 /* GamePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A137662A7DB37200AB1372 /* GamePickerViewController.swift */; }; + D5D39E792AF2D624004BE3F7 /* GameView+AirPlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D39E782AF2D624004BE3F7 /* GameView+AirPlay.swift */; }; D5D78AE529F9BC3700E064F0 /* DSAirPlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D78AE429F9BC3700E064F0 /* DSAirPlay.swift */; }; D5D78AE729F9D40A00E064F0 /* EnvironmentValues+FeatureOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D78AE629F9D40A00E064F0 /* EnvironmentValues+FeatureOption.swift */; }; D5D797E6298D946200738869 /* Contributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D797E5298D946200738869 /* Contributor.swift */; }; @@ -484,6 +485,7 @@ D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSLog+Delta.swift"; sourceTree = ""; }; D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Delta.swift"; sourceTree = ""; }; D5CDCCEC2A859B2B00E22131 /* SyncValidationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncValidationError.swift; sourceTree = ""; }; + D5D39E782AF2D624004BE3F7 /* GameView+AirPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GameView+AirPlay.swift"; sourceTree = ""; }; D5D78AE429F9BC3700E064F0 /* DSAirPlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSAirPlay.swift; sourceTree = ""; }; D5D78AE629F9D40A00E064F0 /* EnvironmentValues+FeatureOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+FeatureOption.swift"; sourceTree = ""; }; D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = ""; }; @@ -566,6 +568,7 @@ D5CDCCC32A85765900E22131 /* OSLog+Delta.swift */, D5CDCCEA2A8593FC00E22131 /* UserDefaults+Delta.swift */, AC1AE30B2A6A068F00956EB9 /* Bundle+AppIconImage.swift */, + D5D39E782AF2D624004BE3F7 /* GameView+AirPlay.swift */, ); path = Extensions; sourceTree = ""; @@ -1669,6 +1672,7 @@ BF5942861E09BC8B0051894B /* _Cheat.swift in Sources */, BF5E7F441B9A650B00AE44F8 /* SettingsViewController.swift in Sources */, BF8CA9361F5F651900499FDD /* PopoverMenuController.swift in Sources */, + D5D39E792AF2D624004BE3F7 /* GameView+AirPlay.swift in Sources */, BFEF24F31F7DD4FD00454C62 /* SaveStateMigrationPolicy.swift in Sources */, BF5942931E09BD1A0051894B /* NSFetchedResultsController+Conveniences.m in Sources */, D5CDCCEE2A859DC200E22131 /* SyncValidationError.swift in Sources */, diff --git a/Delta/Emulation/GameViewController.swift b/Delta/Emulation/GameViewController.swift index 39ed472..92310e0 100644 --- a/Delta/Emulation/GameViewController.swift +++ b/Delta/Emulation/GameViewController.swift @@ -711,12 +711,24 @@ private extension GameViewController { // AirPlaying, hide all (non-touch) screens. - if let traits = self.controllerView.controllerSkinTraits, let screens = self.controllerView.controllerSkin?.screens(for: traits) + if let traits = self.controllerView.controllerSkinTraits, + let supportedTraits = self.controllerView.controllerSkin?.supportedTraits(for: traits), + let screens = self.controllerView.controllerSkin?.screens(for: supportedTraits) { for (screen, gameView) in zip(screens, self.gameViews) { gameView.isEnabled = screen.isTouchScreen - gameView.isHidden = !screen.isTouchScreen + + if gameView == self.gameView + { + // Always show AirPlay indicator on self.gameView + gameView.isAirPlaying = true + gameView.isHidden = false + } + else + { + gameView.isHidden = !screen.isTouchScreen + } } } else @@ -725,7 +737,8 @@ private extension GameViewController // Most likely this system only has 1 screen, so just hide self.gameView. self.gameView.isEnabled = false - self.gameView.isHidden = true + self.gameView.isHidden = false + self.gameView.isAirPlaying = true } } else @@ -736,6 +749,7 @@ private extension GameViewController { gameView.isEnabled = true gameView.isHidden = false + gameView.isAirPlaying = false } } } @@ -1252,6 +1266,8 @@ private extension GameViewController // Implicitly called from updateControllerSkin() // self.updateExternalDisplay() + + self.gameView?.isAirPlaying = true } func updateExternalDisplay() @@ -1322,6 +1338,8 @@ private extension GameViewController } self.updateControllerSkin() // Reset TouchControllerSkin + GameViews + + self.gameView?.isAirPlaying = false } } diff --git a/Delta/Extensions/GameView+AirPlay.swift b/Delta/Extensions/GameView+AirPlay.swift new file mode 100644 index 0000000..0e10fbd --- /dev/null +++ b/Delta/Extensions/GameView+AirPlay.swift @@ -0,0 +1,77 @@ +// +// GameView+AirPlay.swift +// Delta +// +// Created by Riley Testut on 11/1/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation +import ObjectiveC.runtime + +import DeltaCore +import Roxas + +private var airPlayViewKey = 0 + +extension GameView +{ + var isAirPlaying: Bool { + get { self.airPlayView != nil } + set { + guard newValue != self.isAirPlaying else { return } + + if newValue + { + self.showAirPlayView() + } + else + { + self.hideAirPlayView() + } + } + } +} + +private extension GameView +{ + weak var airPlayView: UIView? { + get { objc_getAssociatedObject(self, &airPlayViewKey) as? UIView } + set { objc_setAssociatedObject(self, &airPlayViewKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN) } + } + + func showAirPlayView() + { + guard self.airPlayView == nil else { return } + + let placeholderView = RSTPlaceholderView(frame: .zero) + placeholderView.backgroundColor = .black + + placeholderView.textLabel.font = UIFont.preferredFont(forTextStyle: .headline) + placeholderView.textLabel.text = NSLocalizedString("AirPlay", comment: "") + placeholderView.textLabel.textColor = .systemGray + + placeholderView.detailTextLabel.font = UIFont.preferredFont(forTextStyle: .subheadline) + placeholderView.detailTextLabel.text = NSLocalizedString("This game is playing on a second screen.", comment: "") + placeholderView.detailTextLabel.textColor = .systemGray + + let config = UIImage.SymbolConfiguration(pointSize: 100) + let airPlayIcon = UIImage(systemName: "tv", withConfiguration: config) + placeholderView.imageView.image = airPlayIcon + placeholderView.imageView.isHidden = false + placeholderView.imageView.tintColor = .systemGray + + self.addSubview(placeholderView, pinningEdgesWith: .zero) + + self.airPlayView = placeholderView + } + + func hideAirPlayView() + { + guard let airPlayView else { return } + + airPlayView.removeFromSuperview() + + self.airPlayView = nil + } +}