Show sidebar/notification/new-tab controls in fullscreen without hovering titlebar (#55)

In fullscreen mode, the NSTitlebarAccessoryViewController buttons are hidden
with the system titlebar. This adds SwiftUI-based fullscreen controls that
appear in the sidebar area (when visible) or inline in the custom titlebar
(when sidebar is hidden), reusing the existing TitlebarControlsView component.

- Track fullscreen state via window notifications and toggle controls visibility
- Hide original titlebar accessory (isHidden + alphaValue=0) in fullscreen
- Route notification popover anchoring through fullscreen controls view model
  so both button clicks and keyboard shortcuts (Cmd+Shift+I) position correctly
- Add debug titlebar spacing slider for fine-tuning leading inset
This commit is contained in:
Lawrence Chen 2026-02-17 20:24:01 -08:00 committed by GitHub
parent 4220c3808f
commit f0e4ccdc1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 117 additions and 14 deletions

View file

@ -119,7 +119,7 @@ final class TitlebarControlsViewModel: ObservableObject {
weak var notificationsAnchorView: NSView?
}
private struct NotificationsAnchorView: NSViewRepresentable {
struct NotificationsAnchorView: NSViewRepresentable {
let onResolve: (NSView) -> Void
func makeNSView(context: Context) -> NSView {
@ -134,7 +134,7 @@ private struct NotificationsAnchorView: NSViewRepresentable {
func updateNSView(_ nsView: NSView, context: Context) {}
}
private final class AnchorNSView: NSView {
final class AnchorNSView: NSView {
var onLayout: (() -> Void)?
override func layout() {
@ -193,7 +193,7 @@ struct ShortcutHintHorizontalPlanner {
}
}
private struct TitlebarControlButton<Content: View>: View {
struct TitlebarControlButton<Content: View>: View {
let config: TitlebarControlsStyleConfig
let action: () -> Void
@ViewBuilder let content: () -> Content
@ -221,7 +221,7 @@ private struct TitlebarControlButton<Content: View>: View {
}
}
private struct TitlebarControlsView: View {
struct TitlebarControlsView: View {
@ObservedObject var notificationStore: TerminalNotificationStore
@ObservedObject var viewModel: TitlebarControlsViewModel
let onToggleSidebar: () -> Void
@ -666,7 +666,7 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
hostingView.frame = NSRect(x: 0, y: yOffset, width: contentSize.width, height: contentSize.height)
}
func toggleNotificationsPopover(animated: Bool = true) {
func toggleNotificationsPopover(animated: Bool = true, externalAnchor: NSView? = nil) {
if notificationsPopover.isShown {
notificationsPopover.performClose(nil)
return
@ -684,7 +684,7 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
hostingController.view.layer?.backgroundColor = .clear
notificationsPopover.contentViewController = hostingController
guard let window = view.window ?? hostingView.window ?? NSApp.keyWindow,
guard let window = externalAnchor?.window ?? view.window ?? hostingView.window ?? NSApp.keyWindow,
let contentView = window.contentView else {
return
}
@ -692,7 +692,18 @@ final class TitlebarControlsAccessoryViewController: NSTitlebarAccessoryViewCont
// Force layout to ensure geometry is current.
contentView.layoutSubtreeIfNeeded()
if let anchorView = viewModel.notificationsAnchorView, anchorView.window != nil {
// Use external anchor (e.g. fullscreen sidebar controls) if provided.
if let externalAnchor, externalAnchor.window != nil {
externalAnchor.superview?.layoutSubtreeIfNeeded()
let anchorRect = externalAnchor.convert(externalAnchor.bounds, to: contentView)
if !anchorRect.isEmpty {
notificationsPopover.animates = animated
notificationsPopover.show(relativeTo: anchorRect, of: contentView, preferredEdge: .maxY)
return
}
}
if let anchorView = viewModel.notificationsAnchorView, anchorView.window != nil, !isHidden {
anchorView.superview?.layoutSubtreeIfNeeded()
let anchorRect = anchorView.convert(anchorView.bounds, to: contentView)
if !anchorRect.isEmpty {
@ -1034,10 +1045,21 @@ final class UpdateTitlebarAccessoryController {
return controllers.first
}
func toggleNotificationsPopover(animated: Bool = true) {
func toggleNotificationsPopover(animated: Bool = true, anchorView: NSView? = nil) {
let controllers = controlsControllers.allObjects
guard !controllers.isEmpty else { return }
// If an external anchor is provided (e.g. fullscreen sidebar controls),
// use it for popover positioning instead of the hidden titlebar accessory.
if let anchorView, anchorView.window != nil {
let target = preferredNotificationsController(from: controllers, preferShownPopover: true)
for controller in controllers where controller !== target {
controller.dismissNotificationsPopover()
}
target?.toggleNotificationsPopover(animated: animated, externalAnchor: anchorView)
return
}
let target = preferredNotificationsController(from: controllers, preferShownPopover: true)
for controller in controllers {
if controller !== target {