Skip to content

Commit 224fc73

Browse files
authored
feat: show filename in title bar (#58)
1 parent 49ff1be commit 224fc73

File tree

5 files changed

+108
-6
lines changed

5 files changed

+108
-6
lines changed

Front Row/FrontRowApp.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Joshua Park on 3/4/24.
66
//
77

8+
import AVKit
89
import Sparkle
910
import SwiftUI
1011

@@ -37,7 +38,12 @@ struct FrontRowApp: App {
3738
Window("Front Row", id: "main") {
3839
ContentView()
3940
.preferredColorScheme(.dark)
41+
.ignoresSafeArea()
4042
.environment(playEngine)
43+
.navigationTitle(playEngine.fileURL?.lastPathComponent ?? "Front Row")
44+
.if(playEngine.isLocalFile) { view in
45+
view.navigationDocument(playEngine.fileURL!)
46+
}
4147
.sheet(isPresented: $presentedViewManager.isPresentingOpenURLView) {
4248
OpenURLView()
4349
.frame(minWidth: 600)
@@ -68,7 +74,6 @@ struct FrontRowApp: App {
6874
windowController.setIsFullscreen(false)
6975
}
7076
}
71-
.windowStyle(.hiddenTitleBar)
7277
.restorationBehavior(.disabled)
7378
.commands {
7479
AppCommands(updater: updaterController.updater)

Front Row/Support/Extensions.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ import AVKit
99
import Foundation
1010
import SwiftUI
1111

12+
extension View {
13+
@ViewBuilder
14+
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
15+
if condition {
16+
transform(self)
17+
} else {
18+
self
19+
}
20+
}
21+
}
22+
1223
struct AnyDropDelegate: DropDelegate {
1324
var isTargeted: Binding<Bool>?
1425
var onValidate: ((DropInfo) -> Bool)?

Front Row/Support/PlayEngine.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import SwiftUI
3939

4040
private(set) var isLocalFile = false
4141

42+
private(set) var fileURL: URL?
43+
4244
private var _currentTime: TimeInterval = 0.0
4345

4446
var currentTime: Double {
@@ -229,6 +231,7 @@ import SwiftUI
229231
isLoaded = true
230232
isLocalFile = FileManager.default.fileExists(
231233
atPath: url.path(percentEncoded: false))
234+
fileURL = url
232235
NowPlayable.shared.setNowPlayingMetadata(
233236
NowPlayableStaticMetadata(
234237
assetURL: url, mediaType: videoSize == CGSize.zero ? .audio : .video,
@@ -237,6 +240,7 @@ import SwiftUI
237240
case .failed:
238241
isLoaded = false
239242
isLocalFile = false
243+
fileURL = nil
240244
NowPlayable.shared.sessionEnd()
241245
default:
242246
break

Front Row/Support/WindowController.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,54 @@ import SwiftUI
1111

1212
static let shared = WindowController()
1313

14+
private var mouseMovedMonitor: Any?
15+
16+
// MARK: - Mouse Tracking
17+
18+
private(set) var isMouseInTitleBar = false
19+
var isMouseInPlayerControls = false
20+
21+
init() {
22+
setupMouseTracking()
23+
}
24+
25+
deinit {
26+
if let monitor = mouseMovedMonitor {
27+
NSEvent.removeMonitor(monitor)
28+
}
29+
}
30+
31+
private func setupMouseTracking() {
32+
mouseMovedMonitor = NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
33+
[weak self] event in
34+
self?.updateMousePosition()
35+
return event
36+
}
37+
}
38+
39+
private func updateMousePosition() {
40+
guard let window = NSApp.mainWindow else {
41+
isMouseInTitleBar = false
42+
return
43+
}
44+
45+
let mouseLocation = NSEvent.mouseLocation
46+
let windowFrame = window.frame
47+
48+
guard windowFrame.contains(mouseLocation) else {
49+
isMouseInTitleBar = false
50+
return
51+
}
52+
53+
// Convert screen point to window coordinates
54+
let windowPoint = window.convertPoint(fromScreen: mouseLocation)
55+
// contentLayoutRect excludes the title bar area
56+
let contentRect = window.contentLayoutRect
57+
58+
// Mouse is in title bar if it's above the content rect
59+
isMouseInTitleBar = windowPoint.y > contentRect.maxY
60+
}
61+
1462
// MARK: - Fullscreen
1563

1664
private(set) var isFullscreen = false

Front Row/Views/ContentView.swift

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ struct ContentView: View {
5050
}
5151

5252
PlayerControlsView()
53+
.onContinuousHover { phase in
54+
switch phase {
55+
case .active:
56+
WindowController.shared.isMouseInPlayerControls = true
57+
resetMouseIdleTimer()
58+
showPlayerControls()
59+
WindowController.shared.showTitlebar()
60+
WindowController.shared.showCursor()
61+
case .ended:
62+
WindowController.shared.isMouseInPlayerControls = false
63+
}
64+
}
5365
.animation(.linear(duration: 0.4), value: playerControlsShown)
5466
.opacity(playerControlsShown ? 1.0 : 0.0)
5567
}
@@ -66,11 +78,25 @@ struct ContentView: View {
6678
WindowController.shared.showCursor()
6779
case .ended:
6880
mouseInsideWindow = false
69-
hidePlayerControls()
70-
WindowController.shared.hideTitlebar()
81+
// Only hide if mouse is not hovering over title bar or controls
82+
let isHoveringInteractiveArea =
83+
WindowController.shared.isMouseInTitleBar
84+
|| WindowController.shared.isMouseInPlayerControls
85+
if !isHoveringInteractiveArea {
86+
hidePlayerControls()
87+
WindowController.shared.hideTitlebar()
88+
}
7189
WindowController.shared.showCursor()
7290
}
7391
}
92+
.onChange(of: WindowController.shared.isMouseInTitleBar) { _, isInTitleBar in
93+
// When mouse enters title bar, show controls and reset idle timer
94+
if isInTitleBar {
95+
showPlayerControls()
96+
WindowController.shared.showTitlebar()
97+
resetMouseIdleTimer()
98+
}
99+
}
74100
}
75101

76102
private func hidePlayerControls() {
@@ -97,9 +123,17 @@ struct ContentView: View {
97123
}
98124

99125
private func mouseIdleTimerAction(_ sender: Timer) {
100-
hidePlayerControls()
101-
WindowController.shared.hideTitlebar()
102-
if mouseInsideWindow {
126+
let isHoveringInteractiveArea =
127+
WindowController.shared.isMouseInTitleBar
128+
|| WindowController.shared.isMouseInPlayerControls
129+
130+
// Only hide controls if mouse is not hovering over title bar or controls
131+
if !isHoveringInteractiveArea {
132+
hidePlayerControls()
133+
WindowController.shared.hideTitlebar()
134+
}
135+
// Only hide cursor if mouse is in content area (not title bar or controls)
136+
if mouseInsideWindow && !isHoveringInteractiveArea {
103137
WindowController.shared.hideCursor()
104138
}
105139
}

0 commit comments

Comments
 (0)