mirror of
https://github.com/yattee/yattee.git
synced 2026-10-31 17:07:18 +00:00
The corner layout now always renders the info page as a portrait-style panel below the video (collapsible description, drag to hide/expand), while the side column holds tabs for the remaining pages. Key pieces: - Below-video panel reuses PortraitDetailsPanel (no comments pill, square corners, chapters/related stay in the side column tabs) - Drag down hides the panel with the video recentering in lockstep; swipe up on the video reveals it following the finger - Fullscreen button toggles the panels in corner layout (controls hide first so their resize never plays visibly); status bar and home indicator hide only while the panels are hidden - Panel visibility and column width are seeded at init from a warmed PlayerControlsLayout cache, so the first frame resolves corner mode without flashing the default layout or animating the panels in - Dismiss pan is blocked inside the below-video panel frame (cornerBelowPanelFrame in NavigationCoordinator) - Resize grabber slimmed to match the panel drag handle look
114 lines
3.6 KiB
Swift
114 lines
3.6 KiB
Swift
//
|
|
// PanelResizeGrabber.swift
|
|
// Yattee
|
|
//
|
|
// A draggable handle for resizing a details panel's width.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
#if os(iOS) || os(macOS)
|
|
|
|
/// A draggable handle for resizing the panel width.
|
|
struct PanelResizeGrabber: View {
|
|
@Binding var panelWidth: CGFloat
|
|
let minWidth: CGFloat
|
|
let maxWidth: CGFloat
|
|
let panelSide: FloatingPanelSide
|
|
let isPinned: Bool
|
|
@Binding var isDragActive: Bool
|
|
|
|
/// Width of the grabber hit area (hosts can narrow it, e.g. corner layout)
|
|
var hitWidth: CGFloat = PanelResizeGrabber.grabberHitWidth
|
|
|
|
@State private var isDragging = false
|
|
@State private var dragStartWidth: CGFloat? = nil
|
|
@State private var dragStartLocation: CGFloat? = nil
|
|
#if os(macOS)
|
|
@State private var isHovering = false
|
|
#endif
|
|
|
|
/// Width of the grabber hit area
|
|
static let grabberHitWidth: CGFloat = 20
|
|
|
|
/// Grabber fill color - needs to be visible against both light (pinned) and dark (floating) backgrounds
|
|
private var grabberColor: Color {
|
|
// Pinned matches the panel drag handle (secondary 0.4);
|
|
// floating (dark background) stays a bit lighter
|
|
let isActive = isDragging
|
|
return Color.secondary.opacity(isActive ? 1.0 : (isPinned ? 0.4 : 0.6))
|
|
}
|
|
|
|
var body: some View {
|
|
// Full-height container for expanded hit area
|
|
Rectangle()
|
|
.fill(Color.clear)
|
|
.frame(width: hitWidth)
|
|
.frame(maxHeight: .infinity)
|
|
.contentShape(Rectangle())
|
|
.overlay {
|
|
// Visual pill indicator (centered) — sized like the panel
|
|
// drag handle capsule (36x4), rotated vertical
|
|
Capsule()
|
|
.fill(grabberColor)
|
|
.frame(width: 4, height: 36)
|
|
}
|
|
.gesture(dragGesture)
|
|
#if os(macOS)
|
|
.onHover { hovering in
|
|
isHovering = hovering
|
|
isDragActive = hovering || isDragging
|
|
if hovering {
|
|
NSCursor.resizeLeftRight.push()
|
|
} else {
|
|
NSCursor.pop()
|
|
}
|
|
}
|
|
#endif
|
|
.animation(.easeInOut(duration: 0.15), value: isDragging)
|
|
}
|
|
|
|
private var dragGesture: some Gesture {
|
|
DragGesture(minimumDistance: 0, coordinateSpace: .global)
|
|
.onChanged { value in
|
|
// Capture initial state on first drag event
|
|
if dragStartWidth == nil {
|
|
dragStartWidth = panelWidth
|
|
dragStartLocation = value.location.x
|
|
}
|
|
|
|
isDragging = true
|
|
isDragActive = true
|
|
|
|
guard let startWidth = dragStartWidth,
|
|
let startLocation = dragStartLocation else { return }
|
|
|
|
// Calculate delta from absolute finger position (not translation)
|
|
let locationDelta = value.location.x - startLocation
|
|
|
|
// Apply delta based on panel side
|
|
let delta: CGFloat
|
|
if panelSide == .right {
|
|
delta = -locationDelta
|
|
} else {
|
|
delta = locationDelta
|
|
}
|
|
|
|
let newWidth = startWidth + delta
|
|
panelWidth = min(max(newWidth, minWidth), maxWidth)
|
|
}
|
|
.onEnded { _ in
|
|
dragStartWidth = nil
|
|
dragStartLocation = nil
|
|
isDragging = false
|
|
#if os(macOS)
|
|
isDragActive = isHovering
|
|
#else
|
|
isDragActive = false
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|