feat: animate compact window transitions
This commit is contained in:
+33
-14
@@ -410,7 +410,7 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
transition.closeMedia()
|
||||
compactIntent = nil
|
||||
compactWindow.restore(resumeFullScreen: false)
|
||||
compactWindow.restore(resumeFullScreen: false, animated: false)
|
||||
fileName = nil
|
||||
fileIcon = nil
|
||||
youtubeHeights = []
|
||||
@@ -509,7 +509,7 @@ final class PlayerModel: ObservableObject {
|
||||
return window != nil && fullScreenIntent == nil && !compactWindow.isTransitioning &&
|
||||
(action == .enterFullscreen ? !isFullScreen : isFullScreen)
|
||||
case .enterCompact:
|
||||
return window != nil && !(compactIntent ?? isCompact) && !compactWindow.isTransitioning &&
|
||||
return window != nil && !(compactIntent ?? isCompact) &&
|
||||
canTogglePlayback && state.has_video &&
|
||||
state.video_aspect_ratio.isFinite && state.video_aspect_ratio > 0
|
||||
case .exitCompact: return window != nil && (compactIntent ?? isCompact)
|
||||
@@ -553,7 +553,7 @@ final class PlayerModel: ObservableObject {
|
||||
case .enterFullscreen, .exitFullscreen:
|
||||
if isCompact {
|
||||
compactIntent = false
|
||||
compactWindow.restore(resumeFullScreen: false)
|
||||
compactWindow.restore(resumeFullScreen: false, animated: false)
|
||||
}
|
||||
fullScreenIntent = action == .enterFullscreen
|
||||
window?.toggleFullScreen(nil)
|
||||
@@ -707,7 +707,7 @@ final class PlayerModel: ObservableObject {
|
||||
if let playing = observation.playback { setPlayback(playing: playing) }
|
||||
if next.status == PLAYER_EMPTY || next.status == PLAYER_ERROR ||
|
||||
(!isBusy && !next.has_video) {
|
||||
compactWindow.restore(resumeFullScreen: false)
|
||||
compactWindow.restore(resumeFullScreen: false, animated: false)
|
||||
}
|
||||
if !isSeeking && !transition.seekPending {
|
||||
seekPosition = min(max(0, next.position.isFinite ? next.position : 0), duration)
|
||||
@@ -755,7 +755,7 @@ final class PlayerModel: ObservableObject {
|
||||
cancelYouTubeResolution()
|
||||
systemPlayback?.shutdown()
|
||||
systemPlayback = nil
|
||||
compactWindow.restore(resumeFullScreen: false)
|
||||
compactWindow.restore(resumeFullScreen: false, animated: false)
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
hideDeadline = nil
|
||||
@@ -1166,6 +1166,7 @@ private struct CompactDragSurface: NSViewRepresentable {
|
||||
@MainActor
|
||||
private struct PlayerContent: View {
|
||||
@ObservedObject var model: PlayerModel
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@State private var showsRemainingTime = false
|
||||
@State private var dropTargeted = false
|
||||
@State private var controlSize = CGSize(width: 440, height: 68)
|
||||
@@ -1177,6 +1178,11 @@ private struct PlayerContent: View {
|
||||
private var controlHeight: CGFloat { showsBufferReadout ? 88 : 68 }
|
||||
private var compactControlHeight: CGFloat { showsBufferReadout ? 52 : 40 }
|
||||
|
||||
private var compactChromeAnimation: Animation? {
|
||||
reduceMotion || !model.compactWindow.isTransitioning
|
||||
? nil : .easeInOut(duration: CompactWindowController.transitionDuration)
|
||||
}
|
||||
|
||||
private var bufferedStatus: String? {
|
||||
model.bufferedRange.map {
|
||||
let behind = max(0, model.state.position - $0.lowerBound)
|
||||
@@ -1282,7 +1288,7 @@ private struct PlayerContent: View {
|
||||
model.effectivePlaying ? "Pause" : "Play", action: model.togglePlayback)
|
||||
.disabled(!model.canTogglePlayback)
|
||||
iconButton("pip.exit", "Exit Compact Mode", action: model.toggleCompact)
|
||||
.disabled(model.compactWindow.isTransitioning)
|
||||
.disabled(!model.canToggleCompact)
|
||||
actionsMenu
|
||||
}
|
||||
if showsBufferReadout { bufferReadout }
|
||||
@@ -1446,7 +1452,7 @@ private struct PlayerContent: View {
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
|
||||
if model.isCompact {
|
||||
ZStack(alignment: .top) {
|
||||
Text(model.fileName ?? "terebi")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.lineLimit(1)
|
||||
@@ -1459,11 +1465,21 @@ private struct PlayerContent: View {
|
||||
.accessibilityHidden(!model.controlsVisible)
|
||||
compactControls
|
||||
.position(x: geometry.size.width / 2, y: geometry.size.height - compactControlHeight / 2 - 8)
|
||||
} else {
|
||||
titlebar
|
||||
}
|
||||
.animation(compactChromeAnimation) { content in
|
||||
content.opacity(model.isCompact ? 1 : 0)
|
||||
}
|
||||
.allowsHitTesting(model.isCompact)
|
||||
.accessibilityHidden(!model.isCompact)
|
||||
|
||||
if model.canTogglePlayback && !model.isCompact {
|
||||
titlebar
|
||||
.animation(compactChromeAnimation) { content in
|
||||
content.opacity(model.isCompact ? 0 : 1)
|
||||
}
|
||||
.allowsHitTesting(!model.isCompact && model.controlsVisible)
|
||||
.accessibilityHidden(model.isCompact || !model.controlsVisible)
|
||||
|
||||
if model.canTogglePlayback {
|
||||
playbackControls
|
||||
.frame(width: 440, height: controlHeight)
|
||||
.background {
|
||||
@@ -1487,8 +1503,11 @@ private struct PlayerContent: View {
|
||||
.onGeometryChange(for: CGSize.self) { $0.size } action: { controlSize = $0 }
|
||||
.position(controllerPosition(in: geometry.size))
|
||||
.opacity(model.controlsVisible ? 1 : 0)
|
||||
.allowsHitTesting(model.controlsVisible)
|
||||
.accessibilityHidden(!model.controlsVisible)
|
||||
.animation(compactChromeAnimation) { content in
|
||||
content.opacity(model.isCompact ? 0 : 1)
|
||||
}
|
||||
.allowsHitTesting(!model.isCompact && model.controlsVisible)
|
||||
.accessibilityHidden(model.isCompact || !model.controlsVisible)
|
||||
}
|
||||
if dropTargeted {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
@@ -1514,8 +1533,8 @@ private struct PlayerContent: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minWidth: model.isCompact ? 160 : 400,
|
||||
minHeight: model.isCompact ? model.compactContentMinimumHeight : 240)
|
||||
.frame(minWidth: model.isCompact || model.compactWindow.isTransitioning ? 160 : 400,
|
||||
minHeight: model.isCompact || model.compactWindow.isTransitioning ? model.compactContentMinimumHeight : 240)
|
||||
.ignoresSafeArea()
|
||||
.preferredColorScheme(.dark)
|
||||
.background(WindowChrome(model: model))
|
||||
|
||||
Reference in New Issue
Block a user