@@ -184,6 +184,91 @@ static void NativeApiUpdateWindowMovable(NSWindow* window) {
184184 [window setMovable: requested && !NativeApiWindowIsTitleBarHidden (window)];
185185}
186186
187+ static bool NativeApiPerformTitleBarDoubleClick (NSWindow * window) {
188+ if (!window) return false ;
189+ NSString * action = [NSUserDefaults .standardUserDefaults stringForKey: @" AppleActionOnDoubleClick" ];
190+ if ([action isEqualToString: @" None" ]) return true ;
191+ if ((window.styleMask & NSWindowStyleMaskFullScreen) || window.isMiniaturized ) return false ;
192+ if ([action isEqualToString: @" Minimize" ]) {
193+ if (!(window.styleMask & NSWindowStyleMaskMiniaturizable)) return false ;
194+ [window miniaturize: nil ];
195+ return true ;
196+ }
197+ if ([action isEqualToString: @" Fill" ]) {
198+ if (!(window.styleMask & NSWindowStyleMaskResizable)) return false ;
199+ // AppKit exposes no public Fill action. Ask for the native action only
200+ // when this OS provides it, rather than substituting standard-state zoom.
201+ SEL selector = NSSelectorFromString (@" _zoomFill:" );
202+ if (![window respondsToSelector: selector]) return false ;
203+ using Action = void (*)(id , SEL , id );
204+ reinterpret_cast <Action>([window methodForSelector: selector])(window, selector, nil );
205+ return true ;
206+ }
207+ if (!action || [action isEqualToString: @" Maximize" ] || [action isEqualToString: @" Zoom" ]) {
208+ if (!(window.styleMask & NSWindowStyleMaskResizable)) return false ;
209+ // Use the action itself so custom chrome does not require a native zoom box.
210+ [window zoom: nil ];
211+ return true ;
212+ }
213+ return false ;
214+ }
215+
216+ // A plain hidden window still has a conventional title-bar band. Only empty
217+ // backgrounds inherit its double-click action: controls and views that handle
218+ // mouse presses/releases own their gestures (Flutter uses the explicit action).
219+ static bool NativeApiIsEmptyTitleBarBackground (NSWindow * window, NSEvent * event) {
220+ if (!NativeApiWindowIsTitleBarHidden (window) || event.window != window ||
221+ event.type != NSEventTypeLeftMouseUp || event.clickCount != 2 ) return false ;
222+ NSView * content = window.contentView ;
223+ if (!content) return false ;
224+ const NSWindowStyleMask mask = (window.styleMask | NSWindowStyleMaskTitled) &
225+ ~NSWindowStyleMaskFullSizeContentView ;
226+ const NSRect frame = NSMakeRect (0 , 0 , window.frame .size .width , window.frame .size .height );
227+ const CGFloat height = NSHeight (frame) - NSHeight ([NSWindow contentRectForFrameRect: frame styleMask: mask]);
228+ const NSPoint point = [content convertPoint: event.locationInWindow fromView: nil ];
229+ const NSRect band = NSMakeRect (NSMinX (content.bounds ),
230+ content.isFlipped ? NSMinY (content.bounds ) : NSMaxY (content.bounds ) - height,
231+ NSWidth (content.bounds), height);
232+ if (!NSPointInRect (point, band)) return false ;
233+ const NSPoint hit_point = content.superview
234+ ? [content.superview convertPoint: event.locationInWindow fromView: nil ] : event.locationInWindow ;
235+ NSView * target = [content hitTest: hit_point];
236+ if (!target) return false ;
237+ const IMP default_down = class_getMethodImplementation ([NSView class ], @selector (mouseDown: ));
238+ const IMP default_up = class_getMethodImplementation ([NSView class ], @selector (mouseUp: ));
239+ for (NSView * view = target; view; view = view.superview ) {
240+ if ([view isKindOfClass: [NSControl class ]] ||
241+ class_getMethodImplementation (object_getClass (view), @selector (mouseDown: )) != default_down ||
242+ class_getMethodImplementation (object_getClass (view), @selector (mouseUp: )) != default_up)
243+ return false ;
244+ if (view == content) return true ;
245+ }
246+ return false ;
247+ }
248+
249+ // Preserve the live runtime class (including AppKit's KVO bookkeeping). The
250+ // associated hidden flag makes this override inert on other class instances.
251+ static void NativeApiInstallTitleBarDoubleClick (NSWindow * window) {
252+ static const void * installed_key = &installed_key;
253+ Class cls = object_getClass (window);
254+ if (objc_getAssociatedObject (cls, installed_key)) return ;
255+ SEL selector = @selector (sendEvent: );
256+ Method method = class_getInstanceMethod (cls, selector);
257+ auto original = reinterpret_cast <void (*)(id , SEL , NSEvent *)>(method_getImplementation (method));
258+ // A host with its own window-level event dispatch owns the gesture as well.
259+ const IMP implementation = method_getImplementation (method);
260+ if (implementation != class_getMethodImplementation ([NSWindow class ], selector) &&
261+ implementation != class_getMethodImplementation ([NSPanel class ], selector)) return ;
262+ IMP replacement = imp_implementationWithBlock (^(NSWindow * instance, NSEvent * event) {
263+ if (NativeApiIsEmptyTitleBarBackground (instance, event) &&
264+ NativeApiPerformTitleBarDoubleClick (instance)) return ;
265+ original (instance, selector, event);
266+ });
267+ if (!class_addMethod (cls, selector, replacement, method_getTypeEncoding (method)))
268+ class_replaceMethod (cls, selector, replacement, method_getTypeEncoding (method));
269+ objc_setAssociatedObject (cls, installed_key, @YES , OBJC_ASSOCIATION_RETAIN_NONATOMIC );
270+ }
271+
187272static BOOL NativeApiWindowIsFocusable (NSWindow * window) {
188273 NSNumber * value = objc_getAssociatedObject (window, kWindowFocusableKey );
189274 return value ? [value boolValue ] : YES ;
@@ -258,6 +343,7 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
258343 // helper window away as soon as the user clicks into another app.
259344 panel.hidesOnDeactivate = NO ;
260345 panel.becomesKeyOnlyIfNeeded = NO ;
346+ if (NativeApiWindowIsTitleBarHidden (window)) NativeApiInstallTitleBarDoubleClick (window);
261347 return ;
262348 }
263349 if (NativeApiWindowIsNonActivating (window)) {
@@ -268,6 +354,7 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
268354 (NativeApiWindowIsTitleBarHidden (window) && objc_getAssociatedObject (window, kWindowShapeFrameKey ))) {
269355 NativeApiInstallFocusOverride (window);
270356 }
357+ if (NativeApiWindowIsTitleBarHidden (window)) NativeApiInstallTitleBarDoubleClick (window);
271358}
272359
273360#include " window_shadow_macos.h"
@@ -1072,6 +1159,10 @@ static void NativeApiSendMouseUp(NSWindow* window, NSEvent* mouse_up) {
10721159 return false ;
10731160}
10741161
1162+ bool Window::PerformTitleBarDoubleClick () {
1163+ return NativeApiPerformTitleBarDoubleClick (pimpl_->ns_window_ );
1164+ }
1165+
10751166void Window::StartDragging () {
10761167 NSWindow * window = pimpl_->ns_window_ ;
10771168 NSEvent * event = window.currentEvent ;
0 commit comments