refactor(macos): route notch geometry through overlay state
Store NotchGeometry on NotchOverlayState so the SwiftUI content can read it reactively instead of receiving it via initializer. Lets the controller drive frame updates when geometry changes and simplifies the panel resize to NSPanel's built-in animate flag.
This commit is contained in:
@@ -5,15 +5,15 @@ struct NotchOverlayContent: View {
|
||||
@Environment(Clock.self) private var clock
|
||||
|
||||
let state: NotchOverlayState
|
||||
let geometry: NotchGeometry
|
||||
|
||||
var body: some View {
|
||||
if let active = store.activeRecord,
|
||||
if let geometry = state.geometry,
|
||||
let active = store.activeRecord,
|
||||
let stepId = active.record.currentStepId,
|
||||
let step = active.task.step(id: stepId),
|
||||
let range = active.record.stepRecords[stepId]
|
||||
{
|
||||
island(active: active, step: step, range: range)
|
||||
island(active: active, step: step, range: range, geometry: geometry)
|
||||
} else {
|
||||
Color.clear
|
||||
}
|
||||
@@ -22,7 +22,8 @@ struct NotchOverlayContent: View {
|
||||
private func island(
|
||||
active: (task: TaskPlan, record: TaskRecord),
|
||||
step: Step,
|
||||
range: TimeRange
|
||||
range: TimeRange,
|
||||
geometry: NotchGeometry
|
||||
) -> some View {
|
||||
let elapsed = clock.now.timeIntervalSince(range.start)
|
||||
let estimationSec = Double(step.estimation) * 60
|
||||
@@ -38,7 +39,6 @@ struct NotchOverlayContent: View {
|
||||
.fill(Color.black, style: FillStyle(eoFill: true))
|
||||
.shadow(color: .black.opacity(isExpanded ? 0.35 : 0), radius: 12, x: 0, y: 6)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
wings(
|
||||
step: step,
|
||||
elapsed: elapsed,
|
||||
@@ -48,6 +48,7 @@ struct NotchOverlayContent: View {
|
||||
menuBarH: menuBarH,
|
||||
notchW: notchW
|
||||
)
|
||||
|
||||
if isExpanded {
|
||||
expandedBody(
|
||||
active: active,
|
||||
@@ -57,11 +58,11 @@ struct NotchOverlayContent: View {
|
||||
overshoot: overshoot,
|
||||
isPaused: isPaused
|
||||
)
|
||||
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||
.padding(.top, menuBarH)
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||
}
|
||||
.animation(.spring(response: 0.32, dampingFraction: 0.82), value: isExpanded)
|
||||
.onHover { hover in
|
||||
state.isExpanded = hover
|
||||
|
||||
@@ -6,6 +6,7 @@ import Observation
|
||||
@Observable
|
||||
final class NotchOverlayState {
|
||||
var isExpanded = false
|
||||
var geometry: NotchGeometry?
|
||||
}
|
||||
|
||||
struct NotchGeometry: Equatable {
|
||||
@@ -66,9 +67,15 @@ final class NotchOverlayController {
|
||||
}
|
||||
|
||||
private func sync() {
|
||||
let shouldShow = store.activeRecord != nil && notchGeometry() != nil
|
||||
if shouldShow {
|
||||
let geometry = notchGeometry()
|
||||
let shouldShow = store.activeRecord != nil && geometry != nil
|
||||
if shouldShow, let geometry {
|
||||
let geometryChanged = state.geometry != geometry
|
||||
state.geometry = geometry
|
||||
ensurePanel()
|
||||
if geometryChanged {
|
||||
updateFrame(animated: false)
|
||||
}
|
||||
} else {
|
||||
tearDown()
|
||||
}
|
||||
@@ -99,7 +106,7 @@ final class NotchOverlayController {
|
||||
}
|
||||
|
||||
private func ensurePanel() {
|
||||
guard let geometry = notchGeometry() else { return }
|
||||
guard let geometry = state.geometry else { return }
|
||||
if panel != nil { return }
|
||||
|
||||
let panel = NSPanel(
|
||||
@@ -116,12 +123,14 @@ final class NotchOverlayController {
|
||||
panel.ignoresMouseEvents = false
|
||||
panel.isMovable = false
|
||||
|
||||
let content = NotchOverlayContent(state: state, geometry: geometry)
|
||||
let content = NotchOverlayContent(state: state)
|
||||
.environment(store)
|
||||
.environment(clock)
|
||||
let hosting = NSHostingController(rootView: content)
|
||||
hosting.view.wantsLayer = true
|
||||
hosting.view.layer?.backgroundColor = .clear
|
||||
hosting.view.layerContentsPlacement = .topLeft
|
||||
hosting.view.layerContentsRedrawPolicy = .duringViewResize
|
||||
panel.contentViewController = hosting
|
||||
panel.orderFront(nil)
|
||||
|
||||
@@ -129,22 +138,15 @@ final class NotchOverlayController {
|
||||
}
|
||||
|
||||
private func updateFrame(animated: Bool) {
|
||||
guard let panel, let geometry = notchGeometry() else { return }
|
||||
guard let panel, let geometry = state.geometry else { return }
|
||||
let newFrame = panelFrame(geometry: geometry, expanded: state.isExpanded)
|
||||
if animated {
|
||||
NSAnimationContext.runAnimationGroup { ctx in
|
||||
ctx.duration = 0.32
|
||||
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
||||
ctx.allowsImplicitAnimation = true
|
||||
panel.animator().setFrame(newFrame, display: true)
|
||||
}
|
||||
} else {
|
||||
panel.setFrame(newFrame, display: true)
|
||||
}
|
||||
if panel.frame == newFrame { return }
|
||||
panel.setFrame(newFrame, display: true, animate: animated)
|
||||
}
|
||||
|
||||
private func tearDown() {
|
||||
state.isExpanded = false
|
||||
state.geometry = nil
|
||||
panel?.orderOut(nil)
|
||||
panel?.close()
|
||||
panel = nil
|
||||
|
||||
Reference in New Issue
Block a user