|
12 | 12 | // Key for associated objects (used by both window_macos.mm and window_manager_macos.mm) |
13 | 13 | const void* kWindowIdKey = &kWindowIdKey; |
14 | 14 |
|
| 15 | +// NSWindow UI operations (ordering, visibility, key state) must run on the main |
| 16 | +// thread. Flutter calls these from the UI thread (io.flutter.ui), not the main |
| 17 | +// thread, so we dispatch async. The old window_manager package did the same. |
| 18 | +// ponytail: async means Show() returns before the window is actually visible; |
| 19 | +// acceptable — callers don't depend on synchronous visibility. |
| 20 | +static inline void RunOnMainThread(dispatch_block_t block) { |
| 21 | + if ([NSThread isMainThread]) { |
| 22 | + block(); |
| 23 | + } else { |
| 24 | + dispatch_async(dispatch_get_main_queue(), block); |
| 25 | + } |
| 26 | +} |
| 27 | + |
15 | 28 | namespace nativeapi { |
16 | 29 |
|
17 | 30 | // Private implementation class |
|
67 | 80 | Window::~Window() {} |
68 | 81 |
|
69 | 82 | void Window::Focus() { |
70 | | - [pimpl_->ns_window_ makeKeyAndOrderFront:nil]; |
| 83 | + NSWindow* w = pimpl_->ns_window_; |
| 84 | + RunOnMainThread(^{ [w makeKeyAndOrderFront:nil]; }); |
71 | 85 | } |
72 | 86 |
|
73 | 87 | void Window::Blur() { |
74 | | - [pimpl_->ns_window_ orderBack:nil]; |
| 88 | + NSWindow* w = pimpl_->ns_window_; |
| 89 | + RunOnMainThread(^{ [w orderBack:nil]; }); |
75 | 90 | } |
76 | 91 |
|
77 | 92 | bool Window::IsFocused() const { |
78 | 93 | return [pimpl_->ns_window_ isKeyWindow]; |
79 | 94 | } |
80 | 95 |
|
81 | 96 | void Window::Show() { |
82 | | - [pimpl_->ns_window_ setIsVisible:YES]; |
83 | | - // Panels receive key focus when shown but should not activate the app. |
84 | | - if (![pimpl_->ns_window_ isKindOfClass:[NSPanel class]]) { |
85 | | - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; |
86 | | - } |
87 | | - [pimpl_->ns_window_ makeKeyAndOrderFront:nil]; |
| 97 | + NSWindow* w = pimpl_->ns_window_; |
| 98 | + RunOnMainThread(^{ |
| 99 | + [w setIsVisible:YES]; |
| 100 | + // Panels receive key focus when shown but should not activate the app. |
| 101 | + if (![w isKindOfClass:[NSPanel class]]) { |
| 102 | + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; |
| 103 | + } |
| 104 | + [w makeKeyAndOrderFront:nil]; |
| 105 | + }); |
88 | 106 | } |
89 | 107 |
|
90 | 108 | void Window::ShowInactive() { |
91 | | - [pimpl_->ns_window_ setIsVisible:YES]; |
92 | | - [pimpl_->ns_window_ orderFrontRegardless]; |
| 109 | + NSWindow* w = pimpl_->ns_window_; |
| 110 | + RunOnMainThread(^{ |
| 111 | + [w setIsVisible:YES]; |
| 112 | + [w orderFrontRegardless]; |
| 113 | + }); |
93 | 114 | } |
94 | 115 |
|
95 | 116 | void Window::Hide() { |
96 | | - [pimpl_->ns_window_ setIsVisible:NO]; |
97 | | - [pimpl_->ns_window_ orderOut:nil]; |
| 117 | + NSWindow* w = pimpl_->ns_window_; |
| 118 | + RunOnMainThread(^{ |
| 119 | + [w setIsVisible:NO]; |
| 120 | + [w orderOut:nil]; |
| 121 | + }); |
98 | 122 | } |
99 | 123 |
|
100 | 124 | bool Window::IsVisible() const { |
101 | 125 | return [pimpl_->ns_window_ isVisible]; |
102 | 126 | } |
103 | 127 |
|
104 | 128 | void Window::Maximize() { |
105 | | - if (!IsMaximized()) { |
106 | | - [pimpl_->ns_window_ zoom:nil]; |
107 | | - } |
| 129 | + NSWindow* w = pimpl_->ns_window_; |
| 130 | + RunOnMainThread(^{ if (![w isZoomed]) [w zoom:nil]; }); |
108 | 131 | } |
109 | 132 |
|
110 | 133 | void Window::Unmaximize() { |
111 | | - if (IsMaximized()) { |
112 | | - [pimpl_->ns_window_ zoom:nil]; |
113 | | - } |
| 134 | + NSWindow* w = pimpl_->ns_window_; |
| 135 | + RunOnMainThread(^{ if ([w isZoomed]) [w zoom:nil]; }); |
114 | 136 | } |
115 | 137 |
|
116 | 138 | bool Window::IsMaximized() const { |
117 | 139 | return [pimpl_->ns_window_ isZoomed]; |
118 | 140 | } |
119 | 141 |
|
120 | 142 | void Window::Minimize() { |
121 | | - if (!IsMinimized()) { |
122 | | - [pimpl_->ns_window_ miniaturize:nil]; |
123 | | - } |
| 143 | + NSWindow* w = pimpl_->ns_window_; |
| 144 | + RunOnMainThread(^{ if (![w isMiniaturized]) [w miniaturize:nil]; }); |
124 | 145 | } |
125 | 146 |
|
126 | 147 | void Window::Restore() { |
127 | | - if (IsMinimized()) { |
128 | | - [pimpl_->ns_window_ deminiaturize:nil]; |
129 | | - } |
| 148 | + NSWindow* w = pimpl_->ns_window_; |
| 149 | + RunOnMainThread(^{ if ([w isMiniaturized]) [w deminiaturize:nil]; }); |
130 | 150 | } |
131 | 151 |
|
132 | 152 | bool Window::IsMinimized() const { |
133 | 153 | return [pimpl_->ns_window_ isMiniaturized]; |
134 | 154 | } |
135 | 155 |
|
136 | 156 | void Window::SetFullScreen(bool is_full_screen) { |
137 | | - if (is_full_screen) { |
138 | | - if (!IsFullScreen()) { |
139 | | - [pimpl_->ns_window_ toggleFullScreen:nil]; |
140 | | - } |
141 | | - } else { |
142 | | - if (IsFullScreen()) { |
143 | | - [pimpl_->ns_window_ toggleFullScreen:nil]; |
144 | | - } |
145 | | - } |
| 157 | + NSWindow* w = pimpl_->ns_window_; |
| 158 | + RunOnMainThread(^{ |
| 159 | + bool fs = ([w styleMask] & NSWindowStyleMaskFullScreen) != 0; |
| 160 | + if (is_full_screen != fs) [w toggleFullScreen:nil]; |
| 161 | + }); |
146 | 162 | } |
147 | 163 |
|
148 | 164 | bool Window::IsFullScreen() const { |
|
153 | 169 | //// Color Window::GetBackgroundColor() const; |
154 | 170 |
|
155 | 171 | void Window::SetBounds(Rectangle bounds) { |
156 | | - // Convert from topLeft coordinate system to bottom-left (macOS default) |
| 172 | + NSWindow* w = pimpl_->ns_window_; |
157 | 173 | NSRect topLeftRect = NSMakeRect(bounds.x, bounds.y, bounds.width, bounds.height); |
158 | 174 | NSRect nsRect = NSRectExt::bottomLeft(topLeftRect); |
159 | | - [pimpl_->ns_window_ setFrame:nsRect display:YES]; |
| 175 | + RunOnMainThread(^{ [w setFrame:nsRect display:YES]; }); |
160 | 176 | } |
161 | 177 |
|
162 | 178 | Rectangle Window::GetBounds() const { |
|
169 | 185 | } |
170 | 186 |
|
171 | 187 | void Window::SetSize(Size size, bool animate) { |
172 | | - NSRect frame = [pimpl_->ns_window_ frame]; |
173 | | - frame.origin.y += (frame.size.height - size.height); |
174 | | - frame.size.width = size.width; |
175 | | - frame.size.height = size.height; |
176 | | - if (animate) { |
177 | | - [[pimpl_->ns_window_ animator] setFrame:frame display:YES animate:YES]; |
178 | | - } else { |
179 | | - [pimpl_->ns_window_ setFrame:frame display:YES]; |
180 | | - } |
| 188 | + NSWindow* w = pimpl_->ns_window_; |
| 189 | + RunOnMainThread(^{ |
| 190 | + NSRect frame = [w frame]; |
| 191 | + frame.origin.y += (frame.size.height - size.height); |
| 192 | + frame.size.width = size.width; |
| 193 | + frame.size.height = size.height; |
| 194 | + if (animate) { |
| 195 | + [[w animator] setFrame:frame display:YES animate:YES]; |
| 196 | + } else { |
| 197 | + [w setFrame:frame display:YES]; |
| 198 | + } |
| 199 | + }); |
181 | 200 | } |
182 | 201 |
|
183 | 202 | Size Window::GetSize() const { |
|
338 | 357 | } |
339 | 358 |
|
340 | 359 | void Window::SetPosition(Point point) { |
341 | | - // Convert from topLeft coordinate system to bottom-left (macOS default) |
342 | | - // We need the window height to correctly convert the top-left position |
343 | | - NSRect frame = [pimpl_->ns_window_ frame]; |
344 | | - CGPoint topLeftPoint = {point.x, point.y}; |
345 | | - NSPoint bottomLeft = NSPointExt::bottomLeftForWindow(topLeftPoint, frame.size.height); |
346 | | - [pimpl_->ns_window_ setFrameOrigin:bottomLeft]; |
| 360 | + NSWindow* w = pimpl_->ns_window_; |
| 361 | + RunOnMainThread(^{ |
| 362 | + NSRect frame = [w frame]; |
| 363 | + CGPoint topLeftPoint = {point.x, point.y}; |
| 364 | + NSPoint bottomLeft = NSPointExt::bottomLeftForWindow(topLeftPoint, frame.size.height); |
| 365 | + [w setFrameOrigin:bottomLeft]; |
| 366 | + }); |
347 | 367 | } |
348 | 368 |
|
349 | 369 | Point Window::GetPosition() const { |
|
355 | 375 | } |
356 | 376 |
|
357 | 377 | void Window::Center() { |
358 | | - // Use NSWindow's center method which automatically centers on the main screen |
359 | | - [pimpl_->ns_window_ center]; |
| 378 | + NSWindow* w = pimpl_->ns_window_; |
| 379 | + RunOnMainThread(^{ [w center]; }); |
360 | 380 | } |
361 | 381 |
|
362 | 382 | void Window::SetTitle(std::string title) { |
|
525 | 545 | } |
526 | 546 |
|
527 | 547 | void Window::StartDragging() { |
528 | | - NSWindow* window = pimpl_->ns_window_; |
529 | | - if (window.currentEvent) { |
530 | | - [window performWindowDragWithEvent:window.currentEvent]; |
531 | | - } |
| 548 | + NSWindow* w = pimpl_->ns_window_; |
| 549 | + RunOnMainThread(^{ |
| 550 | + if (w.currentEvent) { |
| 551 | + [w performWindowDragWithEvent:w.currentEvent]; |
| 552 | + } |
| 553 | + }); |
532 | 554 | } |
533 | 555 |
|
534 | 556 | void Window::StartResizing() {} |
|
0 commit comments