Refactor and Rename to CommandNotch
This commit is contained in:
109
Downterm/CommandNotch/Components/NotchShape.swift
Normal file
109
Downterm/CommandNotch/Components/NotchShape.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Custom SwiftUI Shape that draws the characteristic MacBook notch outline.
|
||||
/// Both top and bottom corner radii are animatable, enabling smooth transitions
|
||||
/// between the compact closed state and the expanded open state.
|
||||
///
|
||||
/// The shape uses quadratic Bezier curves to produce the distinctive
|
||||
/// top-edge cut-ins of the closed notch, and a clean rounded-bottom
|
||||
/// rectangle when open (topCornerRadius approaches 0).
|
||||
struct NotchShape: Shape {
|
||||
|
||||
/// Radius applied to the top-left and top-right transitions where the notch
|
||||
/// curves away from the screen edge. When close to 0, the top corners become
|
||||
/// sharp and the shape is a rectangle with rounded bottom corners.
|
||||
var topCornerRadius: CGFloat
|
||||
|
||||
/// Radius applied to the bottom-left and bottom-right inner corners.
|
||||
var bottomCornerRadius: CGFloat
|
||||
|
||||
// MARK: - Animatable conformance
|
||||
|
||||
var animatableData: AnimatablePair<CGFloat, CGFloat> {
|
||||
get { AnimatablePair(topCornerRadius, bottomCornerRadius) }
|
||||
set {
|
||||
topCornerRadius = newValue.first
|
||||
bottomCornerRadius = newValue.second
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Path
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
|
||||
let minX = rect.minX
|
||||
let maxX = rect.maxX
|
||||
let minY = rect.minY
|
||||
let maxY = rect.maxY
|
||||
let width = rect.width
|
||||
let height = rect.height
|
||||
|
||||
let topR = min(topCornerRadius, width / 4, height / 2)
|
||||
let botR = min(bottomCornerRadius, width / 4, height / 2)
|
||||
|
||||
// Start at the top-left corner of the rect
|
||||
path.move(to: CGPoint(x: minX, y: minY))
|
||||
|
||||
if topR > 0.5 {
|
||||
// Leave the screen edge horizontally, then turn into the side wall.
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: minX + topR, y: minY + topR),
|
||||
control: CGPoint(x: minX + topR, y: minY)
|
||||
)
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: minX, y: minY))
|
||||
}
|
||||
|
||||
// Left edge down to bottom-left corner area
|
||||
path.addLine(to: CGPoint(x: minX + topR, y: maxY - botR))
|
||||
|
||||
// Bottom-left inner corner
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: minX + topR + botR, y: maxY),
|
||||
control: CGPoint(x: minX + topR, y: maxY)
|
||||
)
|
||||
|
||||
// Bottom edge across
|
||||
path.addLine(to: CGPoint(x: maxX - topR - botR, y: maxY))
|
||||
|
||||
// Bottom-right inner corner
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: maxX - topR, y: maxY - botR),
|
||||
control: CGPoint(x: maxX - topR, y: maxY)
|
||||
)
|
||||
|
||||
// Right edge up to the top-right transition
|
||||
path.addLine(to: CGPoint(x: maxX - topR, y: minY + topR))
|
||||
|
||||
if topR > 0.5 {
|
||||
// Mirror the top-left transition.
|
||||
path.addQuadCurve(
|
||||
to: CGPoint(x: maxX, y: minY),
|
||||
control: CGPoint(x: maxX - topR, y: minY)
|
||||
)
|
||||
} else {
|
||||
path.addLine(to: CGPoint(x: maxX, y: minY))
|
||||
}
|
||||
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience initializers
|
||||
|
||||
extension NotchShape {
|
||||
|
||||
/// Closed-state shape with tight corner radii that mimic the physical notch.
|
||||
static var closed: NotchShape {
|
||||
NotchShape(topCornerRadius: 6, bottomCornerRadius: 14)
|
||||
}
|
||||
|
||||
/// Open-state shape: no top-edge cut-ins, just rounded bottom corners.
|
||||
/// topCornerRadius is near-zero so the top becomes effectively flat and the panel
|
||||
/// extends flush to the top edge of the screen.
|
||||
static var opened: NotchShape {
|
||||
NotchShape(topCornerRadius: 0, bottomCornerRadius: 24)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user