1919// need to read the focusable flag without access to the Impl.
2020static const void * kWindowFocusableKey = &kWindowFocusableKey ;
2121static const void * kWindowOriginalClassKey = &kWindowOriginalClassKey ;
22+ // Window::SetTitleBarStyle() / SetMovable() state, kept on the NSWindow for the same reason.
23+ static const void * kWindowTitleBarHiddenKey = &kWindowTitleBarHiddenKey ;
24+ static const void * kWindowMovableKey = &kWindowMovableKey ;
25+
26+ static BOOL NativeApiWindowIsTitleBarHidden (NSWindow * window) {
27+ return [objc_getAssociatedObject (window, kWindowTitleBarHiddenKey ) boolValue ];
28+ }
29+
30+ // Whether the user may move the window, as requested through Window::SetMovable().
31+ static BOOL NativeApiWindowIsMovable (NSWindow * window) {
32+ NSNumber * value = objc_getAssociatedObject (window, kWindowMovableKey );
33+ return value ? [value boolValue ] : [window isMovable ];
34+ }
35+
36+ // With the title bar hidden, the content extends under the title bar band, but AppKit still
37+ // moves the window for drags that start in that band, even over opaque content views that handle
38+ // the mouse themselves (the window server decides from its own drag region, not from
39+ // -mouseDownCanMoveWindow). The content owns the band then, so the system is kept from moving
40+ // the window; Window::StartDragging() (-performWindowDragWithEvent:) and SetPosition() still
41+ // work, and are how apps with a custom title bar move it.
42+ static void NativeApiUpdateWindowMovable (NSWindow * window) {
43+ BOOL requested = NativeApiWindowIsMovable (window);
44+ objc_setAssociatedObject (window, kWindowMovableKey , @(requested),
45+ OBJC_ASSOCIATION_RETAIN_NONATOMIC );
46+ [window setMovable: requested && !NativeApiWindowIsTitleBarHidden (window)];
47+ }
2248
2349static BOOL NativeApiWindowIsFocusable (NSWindow * window) {
2450 NSNumber * value = objc_getAssociatedObject (window, kWindowFocusableKey );
@@ -108,12 +134,10 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
108134 Impl (WindowId id , NSWindow * window)
109135 : id_(id ),
110136 ns_window_ (window),
111- title_bar_style_(TitleBarStyle::Normal),
112137 visual_effect_(VisualEffect::None),
113138 visual_effect_view_(nil ) {}
114139 WindowId id_;
115140 NSWindow * ns_window_;
116- TitleBarStyle title_bar_style_;
117141 VisualEffect visual_effect_;
118142 NSVisualEffectView * visual_effect_view_;
119143 double aspect_ratio_ = 0.0 ;
@@ -358,11 +382,18 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
358382}
359383
360384void Window::SetMovable (bool is_movable) {
361- [pimpl_->ns_window_ setMovable: is_movable];
385+ NSWindow * window = pimpl_->ns_window_ ;
386+ if (!window) {
387+ return ;
388+ }
389+ objc_setAssociatedObject (window, kWindowMovableKey , @(is_movable),
390+ OBJC_ASSOCIATION_RETAIN_NONATOMIC );
391+ NativeApiUpdateWindowMovable (window);
362392}
363393
364394bool Window::IsMovable () const {
365- return [pimpl_->ns_window_ isMovable ];
395+ NSWindow * window = pimpl_->ns_window_ ;
396+ return window && NativeApiWindowIsMovable (window);
366397}
367398
368399void Window::SetMinimizable (bool is_minimizable) {
@@ -499,7 +530,20 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
499530}
500531
501532void Window::SetTitleBarStyle (TitleBarStyle style) {
502- pimpl_->title_bar_style_ = style;
533+ if (!pimpl_->ns_window_ ) {
534+ return ;
535+ }
536+ // Record the movable preference before the hidden flag starts to affect -isMovable.
537+ NativeApiUpdateWindowMovable (pimpl_->ns_window_ );
538+ objc_setAssociatedObject (pimpl_->ns_window_ , kWindowTitleBarHiddenKey ,
539+ @(style == TitleBarStyle::Hidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC );
540+ NativeApiUpdateWindowMovable (pimpl_->ns_window_ );
541+
542+ // Changing NSWindowStyleMaskFullSizeContentView keeps the content size and the bottom-left
543+ // origin, so the window would grow or shrink by the title bar height and its top edge would
544+ // jump. Keep the frame instead, as on Windows: the content takes over, or gives back, the title
545+ // bar area.
546+ const NSRect frame = pimpl_->ns_window_ .frame ;
503547
504548 if (style == TitleBarStyle::Hidden) {
505549 // Hide title bar - make it transparent and full size content view
@@ -512,6 +556,9 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
512556 pimpl_->ns_window_ .titlebarAppearsTransparent = NO ;
513557 pimpl_->ns_window_ .styleMask &= ~NSWindowStyleMaskFullSizeContentView ;
514558 }
559+ if (!NSEqualRects (pimpl_->ns_window_ .frame , frame)) {
560+ [pimpl_->ns_window_ setFrame: frame display: YES ];
561+ }
515562
516563 // Ensure window remains opaque and has shadow
517564 pimpl_->ns_window_ .opaque = NO ;
@@ -530,7 +577,8 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
530577}
531578
532579TitleBarStyle Window::GetTitleBarStyle () const {
533- return pimpl_->title_bar_style_ ;
580+ return NativeApiWindowIsTitleBarHidden (pimpl_->ns_window_ ) ? TitleBarStyle::Hidden
581+ : TitleBarStyle::Normal;
534582}
535583
536584void Window::SetHasShadow (bool has_shadow) {
0 commit comments