Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,11 @@ struct ContentView: View {
updateSidebarResizerBandState()
syncTrafficLightInset()
}
// The portal stops following SwiftUI frames when the slide ends; sync it to the final one.
.onReceive(NotificationCenter.default.publisher(for: .programaSidebarToggleDidSettle, object: sidebarState)) { _ in
guard let observedWindow else { return }
TerminalWindowPortalRegistry.scheduleExternalGeometrySynchronize(for: observedWindow)
}
.onChange(of: sidebarMatchTerminalBackground) {
guard sidebarState.isVisible,
sidebarBlendMode == SidebarBlendModeOption.withinWindow.rawValue else { return }
Expand Down
10 changes: 10 additions & 0 deletions Sources/Update/UpdateTitlebarAccessory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,14 @@ private final class TrafficLightsTrailingEdgeView: NSView {
self?.scheduleMeasure()
}
}
// Measurements are skipped while the sidebar slides, so measure once it settles.
windowObservers.append(
NotificationCenter.default.addObserver(
forName: .programaSidebarToggleDidSettle, object: nil, queue: .main
) { [weak self] _ in
self?.scheduleMeasure()
}
)
scheduleMeasure()
}

Expand All @@ -764,6 +772,8 @@ private final class TrafficLightsTrailingEdgeView: NSView {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.measurePending = false
// Mid-slide frames carry the transition offset; the settle notification re-measures.
guard !TerminalWindowPortalRegistry.isInteractiveGeometryResizeActive else { return }
self.onChange?(self.measureZoomButtonMaxX())
}
}
Expand Down
27 changes: 25 additions & 2 deletions Sources/VerticalTabsSidebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import SwiftUI
extension Notification.Name {
static let programaSidebarVisibilityDidChange =
Notification.Name("programaSidebarVisibilityDidChange")
/// Posted with the `SidebarState` as object when its show/hide animation finishes.
static let programaSidebarToggleDidSettle =
Notification.Name("programaSidebarToggleDidSettle")
}

final class SidebarState: ObservableObject {
Expand All @@ -16,6 +19,17 @@ final class SidebarState: ObservableObject {
}
}
@Published var persistedWidth: CGFloat
/// Slides whose completion has not run; released in deinit so a window closed
/// mid-slide cannot leave the global portal resize counter raised.
private var pendingSlides = 0

deinit {
let pending = pendingSlides
guard pending > 0 else { return }
Task { @MainActor in
for _ in 0..<pending { TerminalWindowPortalRegistry.endInteractiveGeometryResize() }
}
}

init(isVisible: Bool = true, persistedWidth: CGFloat = CGFloat(SessionPersistencePolicy.defaultSidebarWidth)) {
self.isVisible = isVisible
Expand All @@ -28,10 +42,19 @@ final class SidebarState: ObservableObject {
// The terminal is an AppKit portal; marking the toggle as an interactive resize makes
// it follow the animated SwiftUI frame every tick, as it does for a sidebar drag.
TerminalWindowPortalRegistry.beginInteractiveGeometryResize()
withAnimation(reduceMotion ? .easeOut(duration: 0.15) : .snappy(duration: 0.24)) {
pendingSlides += 1
// `.removed` waits for the visual end: the default (logically complete) fires as soon as
// another toggle retargets the slide, which ended the portal resize mid-motion.
withAnimation(
reduceMotion ? .easeOut(duration: 0.15) : .snappy(duration: 0.24),
completionCriteria: .removed
) {
isVisible.toggle()
} completion: {
} completion: { [weak self] in
guard let self else { return }
self.pendingSlides -= 1
TerminalWindowPortalRegistry.endInteractiveGeometryResize()
NotificationCenter.default.post(name: .programaSidebarToggleDidSettle, object: self)
}
}
}
Expand Down
Loading