From e8470020c5772c533c2cc5bded5af51185c1f0e1 Mon Sep 17 00:00:00 2001 From: godly-devotion Date: Thu, 28 Mar 2024 15:10:48 -0400 Subject: [PATCH] refactor: autohide controls feat: double click to toggle fullscreen feat: secondary click to toggle play state --- Front Row/Support/PlayEngine.swift | 11 ++++++ Front Row/Support/WindowController.swift | 44 +++++++++++++----------- Front Row/Views/ContentView.swift | 41 +++++++++++++--------- Front Row/Views/PlayerView.swift | 14 ++++++++ 4 files changed, 72 insertions(+), 38 deletions(-) diff --git a/Front Row/Support/PlayEngine.swift b/Front Row/Support/PlayEngine.swift index 8a2b7a9..7f0cef0 100644 --- a/Front Row/Support/PlayEngine.swift +++ b/Front Row/Support/PlayEngine.swift @@ -174,18 +174,25 @@ import SwiftUI func cancelLoading() { guard let asset else { return } + asset.cancelLoading() } func play() { + guard isLoaded else { return } + player.play() } func pause() { + guard isLoaded else { return } + player.pause() } func playPause() { + guard isLoaded else { return } + if timeControlStatus == .playing { pause() } else { @@ -195,6 +202,7 @@ import SwiftUI func goForwards(_ duration: Double = 5.0) async { guard isLoaded else { return } + let time = CMTimeAdd( player.currentTime(), CMTimeMakeWithSeconds(duration, preferredTimescale: 1) @@ -204,6 +212,7 @@ import SwiftUI func goBackwards(_ duration: Double = 5.0) async { guard isLoaded else { return } + let time = CMTimeSubtract( player.currentTime(), CMTimeMakeWithSeconds(duration, preferredTimescale: 1) @@ -213,6 +222,7 @@ import SwiftUI func goToTime(_ timecode: Double) async { guard isLoaded else { return } + let time = CMTimeMakeWithSeconds(timecode, preferredTimescale: 1) await player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero) } @@ -244,6 +254,7 @@ import SwiftUI func fitToVideoSize() { guard videoSize != CGSize.zero else { return } guard let window = NSApp.windows.first else { return } + let screenFrame = (window.screen ?? NSScreen.main!).visibleFrame let newFrame: NSRect diff --git a/Front Row/Support/WindowController.swift b/Front Row/Support/WindowController.swift index c2fdcd1..38fbddc 100644 --- a/Front Row/Support/WindowController.swift +++ b/Front Row/Support/WindowController.swift @@ -11,8 +11,16 @@ import SwiftUI static let shared = WindowController() + // MARK: - Fullscreen + private(set) var isFullscreen = false + func setIsFullscreen(_ isFullscreen: Bool) { + self.isFullscreen = isFullscreen + } + + // MARK: - Float on Top + private var _isOnTop = false var isOnTop: Bool { @@ -27,10 +35,18 @@ import SwiftUI } } - func setIsFullscreen(_ isFullscreen: Bool) { - self.isFullscreen = isFullscreen + // MARK: - Autohide Cursor + + func hideCursor() { + CGDisplayHideCursor(CGMainDisplayID()) + } + + func showCursor() { + CGDisplayShowCursor(CGMainDisplayID()) } + // MARK: - Autohide Titlebar + private var _titlebarView: NSView? var titlebarView: NSView? { @@ -42,23 +58,13 @@ import SwiftUI .first(where: { $0.isKind(of: containerClass) }) else { return nil } - _titlebarView = containerView - - return _titlebarView - } + guard let titlebarClass = NSClassFromString("NSTitlebarView") else { return nil } + guard let titlebar = containerView.subviews.first(where: { $0.isKind(of: titlebarClass) }) + else { return nil } - private var mouseIdleTimer: Timer! + _titlebarView = titlebar - func resetMouseIdleTimer() { - if mouseIdleTimer != nil { - mouseIdleTimer.invalidate() - mouseIdleTimer = nil - } - - mouseIdleTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { - [weak self] in - self?.mouseIdleTimerAction($0) - } + return _titlebarView } func hideTitlebar() { @@ -69,10 +75,6 @@ import SwiftUI setTitlebarOpacity(1.0) } - private func mouseIdleTimerAction(_ sender: Timer) { - hideTitlebar() - } - private func setTitlebarOpacity(_ opacity: CGFloat) { /// when the window is in full screen, the titlebar view is in another window (the "toolbar window") guard titlebarView?.window == NSApp.windows.first else { return } diff --git a/Front Row/Views/ContentView.swift b/Front Row/Views/ContentView.swift index 61112ce..ddebc5b 100644 --- a/Front Row/Views/ContentView.swift +++ b/Front Row/Views/ContentView.swift @@ -9,8 +9,9 @@ import SwiftUI struct ContentView: View { @Environment(PlayEngine.self) var playEngine: PlayEngine - @State private var playerControlsShown = true @State private var mouseIdleTimer: Timer! + @State private var mouseInsideWindow = false + @State private var playerControlsShown = true var body: some View { @Bindable var playEngine = playEngine @@ -37,13 +38,12 @@ struct ContentView: View { } ) ) - .onTapGesture(count: 2) { - NSApplication.shared.mainWindow?.toggleFullScreen(nil) - } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) .ignoresSafeArea() - if playEngine.timeControlStatus == .waitingToPlayAtSpecifiedRate { + if !playEngine.isLocalFile + && playEngine.timeControlStatus == .waitingToPlayAtSpecifiedRate + { ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) } @@ -59,28 +59,20 @@ struct ContentView: View { .onContinuousHover { phase in switch phase { case .active: + mouseInsideWindow = true resetMouseIdleTimer() showPlayerControls() - WindowController.shared.resetMouseIdleTimer() WindowController.shared.showTitlebar() + WindowController.shared.showCursor() case .ended: + mouseInsideWindow = false hidePlayerControls() WindowController.shared.hideTitlebar() + WindowController.shared.showCursor() } } } - private func resetMouseIdleTimer() { - if mouseIdleTimer != nil { - mouseIdleTimer.invalidate() - mouseIdleTimer = nil - } - - mouseIdleTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { - mouseIdleTimerAction($0) - } - } - private func hidePlayerControls() { withAnimation { playerControlsShown = false @@ -93,8 +85,23 @@ struct ContentView: View { } } + private func resetMouseIdleTimer() { + if mouseIdleTimer != nil { + mouseIdleTimer.invalidate() + mouseIdleTimer = nil + } + + mouseIdleTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { + mouseIdleTimerAction($0) + } + } + private func mouseIdleTimerAction(_ sender: Timer) { hidePlayerControls() + WindowController.shared.hideTitlebar() + if mouseInsideWindow { + WindowController.shared.hideCursor() + } } } diff --git a/Front Row/Views/PlayerView.swift b/Front Row/Views/PlayerView.swift index c7187ea..eda0d71 100644 --- a/Front Row/Views/PlayerView.swift +++ b/Front Row/Views/PlayerView.swift @@ -12,12 +12,26 @@ struct PlayerView: NSViewRepresentable { let player: AVPlayer class PlayerNSView: NSView, CALayerDelegate { + private let playerLayer = AVPlayerLayer() override func makeBackingLayer() -> CALayer { playerLayer } + override func mouseDown(with event: NSEvent) { + if event.type == .leftMouseDown && event.clickCount == 2 { + NSApplication.shared.mainWindow?.toggleFullScreen(nil) + } else { + super.mouseDown(with: event) + } + } + + override func rightMouseUp(with event: NSEvent) { + PlayEngine.shared.playPause() + super.rightMouseUp(with: event) + } + init(player: AVPlayer) { super.init(frame: .zero) playerLayer.player = player