Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Front Row/FrontRowApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Joshua Park on 3/4/24.
//

import AVKit
import Sparkle
import SwiftUI

Expand Down Expand Up @@ -37,7 +38,12 @@ struct FrontRowApp: App {
Window("Front Row", id: "main") {
ContentView()
.preferredColorScheme(.dark)
.ignoresSafeArea()
.environment(playEngine)
.navigationTitle(playEngine.fileURL?.lastPathComponent ?? "Front Row")
.if(playEngine.isLocalFile) { view in
view.navigationDocument(playEngine.fileURL!)
}
.sheet(isPresented: $presentedViewManager.isPresentingOpenURLView) {
OpenURLView()
.frame(minWidth: 600)
Expand Down Expand Up @@ -68,7 +74,6 @@ struct FrontRowApp: App {
windowController.setIsFullscreen(false)
}
}
.windowStyle(.hiddenTitleBar)
.restorationBehavior(.disabled)
.commands {
AppCommands(updater: updaterController.updater)
Expand Down
11 changes: 11 additions & 0 deletions Front Row/Support/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ import AVKit
import Foundation
import SwiftUI

extension View {
@ViewBuilder
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}
}

struct AnyDropDelegate: DropDelegate {
var isTargeted: Binding<Bool>?
var onValidate: ((DropInfo) -> Bool)?
Expand Down
4 changes: 4 additions & 0 deletions Front Row/Support/PlayEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import SwiftUI

private(set) var isLocalFile = false

private(set) var fileURL: URL?

private var _currentTime: TimeInterval = 0.0

var currentTime: Double {
Expand Down Expand Up @@ -229,6 +231,7 @@ import SwiftUI
isLoaded = true
isLocalFile = FileManager.default.fileExists(
atPath: url.path(percentEncoded: false))
fileURL = url
NowPlayable.shared.setNowPlayingMetadata(
NowPlayableStaticMetadata(
assetURL: url, mediaType: videoSize == CGSize.zero ? .audio : .video,
Expand All @@ -237,6 +240,7 @@ import SwiftUI
case .failed:
isLoaded = false
isLocalFile = false
fileURL = nil
NowPlayable.shared.sessionEnd()
default:
break
Expand Down
48 changes: 48 additions & 0 deletions Front Row/Support/WindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,54 @@ import SwiftUI

static let shared = WindowController()

private var mouseMovedMonitor: Any?

// MARK: - Mouse Tracking

private(set) var isMouseInTitleBar = false
var isMouseInPlayerControls = false

init() {
setupMouseTracking()
}

deinit {
if let monitor = mouseMovedMonitor {
NSEvent.removeMonitor(monitor)
}
}

private func setupMouseTracking() {
mouseMovedMonitor = NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
[weak self] event in
self?.updateMousePosition()
return event
}
}

private func updateMousePosition() {
guard let window = NSApp.mainWindow else {
isMouseInTitleBar = false
return
}

let mouseLocation = NSEvent.mouseLocation
let windowFrame = window.frame

guard windowFrame.contains(mouseLocation) else {
isMouseInTitleBar = false
return
}

// Convert screen point to window coordinates
let windowPoint = window.convertPoint(fromScreen: mouseLocation)
// contentLayoutRect excludes the title bar area
let contentRect = window.contentLayoutRect

// Mouse is in title bar if it's above the content rect
isMouseInTitleBar = windowPoint.y > contentRect.maxY
}

// MARK: - Fullscreen

private(set) var isFullscreen = false
Expand Down
44 changes: 39 additions & 5 deletions Front Row/Views/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ struct ContentView: View {
}

PlayerControlsView()
.onContinuousHover { phase in
switch phase {
case .active:
WindowController.shared.isMouseInPlayerControls = true
resetMouseIdleTimer()
showPlayerControls()
WindowController.shared.showTitlebar()
WindowController.shared.showCursor()
case .ended:
WindowController.shared.isMouseInPlayerControls = false
}
}
.animation(.linear(duration: 0.4), value: playerControlsShown)
.opacity(playerControlsShown ? 1.0 : 0.0)
}
Expand All @@ -66,11 +78,25 @@ struct ContentView: View {
WindowController.shared.showCursor()
case .ended:
mouseInsideWindow = false
hidePlayerControls()
WindowController.shared.hideTitlebar()
// Only hide if mouse is not hovering over title bar or controls
let isHoveringInteractiveArea =
WindowController.shared.isMouseInTitleBar
|| WindowController.shared.isMouseInPlayerControls
if !isHoveringInteractiveArea {
hidePlayerControls()
WindowController.shared.hideTitlebar()
}
WindowController.shared.showCursor()
}
}
.onChange(of: WindowController.shared.isMouseInTitleBar) { _, isInTitleBar in
// When mouse enters title bar, show controls and reset idle timer
if isInTitleBar {
showPlayerControls()
WindowController.shared.showTitlebar()
resetMouseIdleTimer()
}
}
}

private func hidePlayerControls() {
Expand All @@ -97,9 +123,17 @@ struct ContentView: View {
}

private func mouseIdleTimerAction(_ sender: Timer) {
hidePlayerControls()
WindowController.shared.hideTitlebar()
if mouseInsideWindow {
let isHoveringInteractiveArea =
WindowController.shared.isMouseInTitleBar
|| WindowController.shared.isMouseInPlayerControls

// Only hide controls if mouse is not hovering over title bar or controls
if !isHoveringInteractiveArea {
hidePlayerControls()
WindowController.shared.hideTitlebar()
}
// Only hide cursor if mouse is in content area (not title bar or controls)
if mouseInsideWindow && !isHoveringInteractiveArea {
WindowController.shared.hideCursor()
}
}
Expand Down