@@ -84,36 +84,53 @@ private func windowDragHandleShouldResolveActiveHitCapture(
8484
8585/// Runs the same action macOS titlebars use for double-click:
8686/// zoom by default, or minimize when the user preference is set.
87- @discardableResult
88- func performStandardTitlebarDoubleClick( window: NSWindow ? ) -> Bool {
89- guard let window else { return false }
87+ enum StandardTitlebarDoubleClickAction : Equatable {
88+ case miniaturize
89+ case zoom
90+ case none
91+ }
9092
91- let globalDefaults = UserDefaults . standard . persistentDomain ( forName : UserDefaults . globalDomain ) ?? [ : ]
93+ func resolvedStandardTitlebarDoubleClickAction ( globalDefaults: [ String : Any ] ) -> StandardTitlebarDoubleClickAction {
9294 if let action = ( globalDefaults [ " AppleActionOnDoubleClick " ] as? String ) ?
9395 . trimmingCharacters ( in: . whitespacesAndNewlines)
9496 . lowercased ( ) {
9597 switch action {
96- case " minimize " :
97- window. miniaturize ( nil )
98- return true
99- case " none " :
100- return false
101- case " maximize " , " zoom " :
102- window. zoom ( nil )
103- return true
98+ case " minimize " , " miniaturize " :
99+ return . miniaturize
100+ case " maximize " , " zoom " , " fill " :
101+ return . zoom
102+ case " none " , " no action " :
103+ return . none
104104 default :
105105 break
106106 }
107107 }
108108
109109 if let miniaturizeOnDoubleClick = globalDefaults [ " AppleMiniaturizeOnDoubleClick " ] as? Bool ,
110110 miniaturizeOnDoubleClick {
111- window. miniaturize ( nil )
112- return true
111+ return . miniaturize
113112 }
114113
115- window. zoom ( nil )
116- return true
114+ return . zoom
115+ }
116+
117+ /// Runs the same action macOS titlebars use for double-click:
118+ /// zoom by default, or minimize when the user preference is set.
119+ @discardableResult
120+ func performStandardTitlebarDoubleClick( window: NSWindow ? ) -> StandardTitlebarDoubleClickAction ? {
121+ guard let window else { return nil }
122+
123+ let globalDefaults = UserDefaults . standard. persistentDomain ( forName: UserDefaults . globalDomain) ?? [ : ]
124+ let action = resolvedStandardTitlebarDoubleClickAction ( globalDefaults: globalDefaults)
125+ switch action {
126+ case . miniaturize:
127+ window. miniaturize ( nil )
128+ case . zoom:
129+ window. zoom ( nil )
130+ case . none:
131+ break
132+ }
133+ return action
117134}
118135
119136private enum WindowDragHandleAssociatedObjectKeys {
@@ -410,11 +427,11 @@ struct WindowDragHandleView: NSViewRepresentable {
410427 #endif
411428
412429 if event. clickCount >= 2 {
413- let handled = performStandardTitlebarDoubleClick ( window: window)
430+ let action = performStandardTitlebarDoubleClick ( window: window)
414431 #if DEBUG
415- dlog ( " titlebar.dragHandle.mouseDownDoubleClick handled = \( handled ? 1 : 0 ) " )
432+ dlog ( " titlebar.dragHandle.mouseDownDoubleClick action = \( String ( describing : action ) ) " )
416433 #endif
417- if handled {
434+ if action != nil {
418435 return
419436 }
420437 }
@@ -440,3 +457,48 @@ struct WindowDragHandleView: NSViewRepresentable {
440457 }
441458 }
442459}
460+
461+ /// Local monitor that guarantees double-clicks in custom titlebar surfaces trigger
462+ /// the standard macOS titlebar action even when the visible strip is hosted by
463+ /// higher-level SwiftUI/AppKit container views.
464+ struct TitlebarDoubleClickMonitorView : NSViewRepresentable {
465+ final class Coordinator {
466+ weak var view : NSView ?
467+ var monitor : Any ?
468+
469+ deinit {
470+ if let monitor {
471+ NSEvent . removeMonitor ( monitor)
472+ }
473+ }
474+ }
475+
476+ func makeCoordinator( ) -> Coordinator { Coordinator ( ) }
477+
478+ func makeNSView( context: Context ) -> NSView {
479+ let view = NSView ( frame: . zero)
480+ view. wantsLayer = true
481+ view. layer? . backgroundColor = NSColor . clear. cgColor
482+
483+ context. coordinator. view = view
484+
485+ let coordinator = context. coordinator
486+ coordinator. monitor = NSEvent . addLocalMonitorForEvents ( matching: [ . leftMouseDown] ) { [ weak coordinator] event in
487+ guard event. clickCount >= 2 else { return event }
488+ guard let coordinator, let view = coordinator. view, let window = view. window else { return event }
489+ guard event. window === window else { return event }
490+
491+ let point = view. convert ( event. locationInWindow, from: nil )
492+ guard view. bounds. contains ( point) else { return event }
493+
494+ let action = performStandardTitlebarDoubleClick ( window: window)
495+ return action == nil ? event : nil
496+ }
497+
498+ return view
499+ }
500+
501+ func updateNSView( _ nsView: NSView , context: Context ) {
502+ context. coordinator. view = nsView
503+ }
504+ }
0 commit comments