diff --git a/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayContent.swift b/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayContent.swift index b8ba87e..ec1ab55 100644 --- a/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayContent.swift +++ b/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayContent.swift @@ -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,30 +39,30 @@ 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( + wings( + step: step, + elapsed: elapsed, + remaining: remaining, + overshoot: overshoot, + isPaused: isPaused, + menuBarH: menuBarH, + notchW: notchW + ) + + if isExpanded { + expandedBody( + active: active, step: step, elapsed: elapsed, remaining: remaining, overshoot: overshoot, - isPaused: isPaused, - menuBarH: menuBarH, - notchW: notchW + isPaused: isPaused ) - if isExpanded { - expandedBody( - active: active, - step: step, - elapsed: elapsed, - remaining: remaining, - overshoot: overshoot, - isPaused: isPaused - ) - .transition(.opacity.combined(with: .move(edge: .top))) - } + .padding(.top, menuBarH) + .transition(.opacity) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .animation(.spring(response: 0.32, dampingFraction: 0.82), value: isExpanded) .onHover { hover in state.isExpanded = hover diff --git a/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayController.swift b/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayController.swift index 455a3c1..d22a091 100644 --- a/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayController.swift +++ b/macos/Sources/FailWell/Views/NotchOverlay/NotchOverlayController.swift @@ -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