Skip to content

Commit f6ff436

Browse files
committed
Fix hidden title bar window dragging and resizing on macOS
- With TitleBarStyle::Hidden the system no longer moves the window for drags in the former title bar area, which it did even over content that handles the mouse. SetMovable()/IsMovable() keep the caller's preference; StartDragging() still works. - Switching title bar styles keeps the window frame, as on Windows, instead of shifting the window by the title bar height. - GetTitleBarStyle() reads the state from the NSWindow, so every wrapper of the same window agrees.
1 parent e13e148 commit f6ff436

2 files changed

Lines changed: 68 additions & 6 deletions

File tree

‎src/platform/macos/window_macos.mm‎

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,32 @@
1919
// need to read the focusable flag without access to the Impl.
2020
static const void* kWindowFocusableKey = &kWindowFocusableKey;
2121
static 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

2349
static 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

360384
void 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

364394
bool Window::IsMovable() const {
365-
return [pimpl_->ns_window_ isMovable];
395+
NSWindow* window = pimpl_->ns_window_;
396+
return window && NativeApiWindowIsMovable(window);
366397
}
367398

368399
void Window::SetMinimizable(bool is_minimizable) {
@@ -499,7 +530,20 @@ static void NativeApiUpdateWindowClass(NSWindow* window, bool non_activating) {
499530
}
500531

501532
void 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

532579
TitleBarStyle Window::GetTitleBarStyle() const {
533-
return pimpl_->title_bar_style_;
580+
return NativeApiWindowIsTitleBarHidden(pimpl_->ns_window_) ? TitleBarStyle::Hidden
581+
: TitleBarStyle::Normal;
534582
}
535583

536584
void Window::SetHasShadow(bool has_shadow) {

‎src/window.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ enum class TitleBarStyle {
4040
/**
4141
* Hidden title bar with no visible decorations.
4242
* The window appears without a title bar, useful for custom chrome.
43+
*
44+
* The content owns the area where the title bar was: dragging there does
45+
* not move the window. Move it from custom chrome with
46+
* Window::StartDragging() (or a WindowDragSession).
47+
* - macOS: the content extends under a transparent title bar; the
48+
* window buttons stay. The system is kept from moving the window, even
49+
* while IsMovable() is true.
50+
*
51+
* Switching between styles keeps the window's frame (position and outer
52+
* size); the content area grows or shrinks by the title bar instead.
4353
*/
4454
Hidden
4555
};
@@ -439,6 +449,10 @@ class Window : public NativeObjectProvider, public std::enable_shared_from_this<
439449
*
440450
* When disabled, the user cannot move the window by dragging its title bar.
441451
* Programmatic positioning via SetPosition() is still possible.
452+
*
453+
* With TitleBarStyle::Hidden the system does not move the window on its
454+
* own regardless; this setting is kept and applies again once the title
455+
* bar is shown.
442456
*/
443457
void SetMovable(bool is_movable);
444458

0 commit comments

Comments
 (0)