Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
FlutterWindowController.mm
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6#include <Foundation/Foundation.h>
7
12
14
15// A delegate for a Flutter managed window.
16@interface FlutterWindowOwner : NSObject <NSWindowDelegate, FlutterViewSizingDelegate> {
17 // Strong reference to the window. This is the only strong reference to the
18 // window.
19 NSWindow* _window;
21 std::optional<flutter::Isolate> _isolate;
23
24 // Extra size constraints coming from the window positioner.
26}
27
28@property(readonly, nonatomic) NSWindow* window;
29@property(readonly, nonatomic) FlutterViewController* flutterViewController;
30@property(readwrite, nonatomic) BOOL closeWhenParentResignsKey;
31
32- (instancetype)initWithWindow:(NSWindow*)window
33 flutterViewController:(FlutterViewController*)viewController
34 creationRequest:(const FlutterWindowCreationRequest&)creationRequest;
35
36@end
37
38@interface NSWindow (FlutterWindowSizing)
39
40- (void)flutterSetContentSize:(FlutterWindowSize)contentSize;
41- (void)flutterSetConstraints:(FlutterWindowConstraints)constraints;
42
43@end
44
45@implementation NSWindow (FlutterWindowSizing)
46- (void)flutterSetContentSize:(FlutterWindowSize)contentSize {
47 [self setContentSize:NSMakeSize(contentSize.width, contentSize.height)];
48}
49
50- (void)flutterSetConstraints:(FlutterWindowConstraints)constraints {
51 NSSize size = [self frameRectForContentRect:self.frame].size;
52 NSSize originalSize = size;
53 [self setContentMinSize:NSMakeSize(constraints.min_width, constraints.min_height)];
54 size.width = std::max(size.width, constraints.min_width);
55 size.height = std::max(size.height, constraints.min_height);
56 if (constraints.max_width > 0 && constraints.max_height > 0) {
57 [self setContentMaxSize:NSMakeSize(constraints.max_width, constraints.max_height)];
58 size.width = std::min(size.width, constraints.max_width);
59 size.height = std::min(size.height, constraints.max_height);
60 } else {
61 [self setContentMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
62 }
63 if (!NSEqualSizes(originalSize, size)) {
64 [self setContentSize:size];
65 }
66}
67
68@end
69
71 NSMutableArray<FlutterWindowOwner*>* _windows;
72}
73
74- (void)windowDidResignKey:(FlutterWindowOwner*)window;
75
76@end
77
78@implementation FlutterWindowOwner
79
80@synthesize window = _window;
81@synthesize flutterViewController = _flutterViewController;
82
83- (instancetype)initWithWindow:(NSWindow*)window
84 flutterViewController:(FlutterViewController*)viewController
85 creationRequest:(const FlutterWindowCreationRequest&)creationRequest {
86 if (self = [super init]) {
87 _window = window;
89 _creationRequest = creationRequest;
90 _isolate = flutter::Isolate::Current();
91 }
92 return self;
93}
94
95- (void)windowDidBecomeKey:(NSNotification*)notification {
96 [_flutterViewController.engine windowDidBecomeKey:_flutterViewController.viewIdentifier];
97}
98
99- (void)windowDidResignKey:(NSNotification*)notification {
100 [_flutterViewController.engine windowDidResignKey:_flutterViewController.viewIdentifier];
101 [[_flutterViewController.engine windowController] windowDidResignKey:self];
102}
103
104- (BOOL)windowShouldClose:(NSWindow*)sender {
105 flutter::IsolateScope isolate_scope(*_isolate);
106 _creationRequest.on_should_close();
107 return NO;
108}
109
110- (void)windowWillClose {
111 _creationRequest.on_will_close();
112}
113
114- (void)windowDidResize:(NSNotification*)notification {
115 flutter::IsolateScope isolate_scope(*_isolate);
116 _creationRequest.notify_listeners();
117}
118
119// Miniaturize does not trigger resize event, but for now there
120// is no other way to get notification about the state change.
121- (void)windowDidMiniaturize:(NSNotification*)notification {
122 flutter::IsolateScope isolate_scope(*_isolate);
123 _creationRequest.notify_listeners();
124}
125
126// Deminiaturize does not trigger resize event, but for now there
127// is no other way to get notification about the state change.
128- (void)windowDidDeminiaturize:(NSNotification*)notification {
129 flutter::IsolateScope isolate_scope(*_isolate);
130 _creationRequest.notify_listeners();
131}
132
133- (void)windowWillEnterFullScreen:(NSNotification*)notification {
134 flutter::IsolateScope isolate_scope(*_isolate);
135 _creationRequest.notify_listeners();
136}
137
138- (void)windowWillExitFullScreen:(NSNotification*)notification {
139 flutter::IsolateScope isolate_scope(*_isolate);
140 _creationRequest.notify_listeners();
141}
142
143- (std::optional<NSSize>)minimumViewSize:(FlutterView*)view {
144 if (_creationRequest.has_constraints) {
145 return NSMakeSize(_creationRequest.constraints.min_width,
146 _creationRequest.constraints.min_height);
147 } else {
148 return std::nullopt;
149 }
150}
151
152- (std::optional<NSSize>)maximumViewSize:(FlutterView*)view {
153 if (!_creationRequest.has_constraints) {
154 // Window is not sized to contents.
155 return std::nullopt;
156 }
157 NSSize screenSize = self.window.screen.visibleFrame.size;
158 double width = screenSize.width;
159 width = std::min(width, _creationRequest.constraints.max_width);
160 if (_positionerSizeConstraints.width > 0) {
161 width = std::min(width, _positionerSizeConstraints.width);
162 }
163 double height = screenSize.height;
164 height = std::min(height, _creationRequest.constraints.max_height);
165 if (_positionerSizeConstraints.height > 0) {
166 height = std::min(height, _positionerSizeConstraints.height);
167 }
168 return NSMakeSize(width, height);
169}
170
171// Returns the frame that includes all screen. This is used to flip coordinates
172// of individual screen to match Flutter coordinate system.
173static NSRect ComputeGlobalScreenFrame() {
174 NSRect frame = NSZeroRect;
175 for (NSScreen* screen in [NSScreen screens]) {
176 NSRect screenFrame = screen.frame;
177 if (NSIsEmptyRect(frame)) {
178 frame = screenFrame;
179 } else {
180 frame = NSUnionRect(frame, screenFrame);
181 }
182 }
183 return frame;
184}
185
186static void FlipRect(NSRect& rect, const NSRect& globalScreenFrame) {
187 // Flip the y coordinate to match Flutter coordinate system.
188 rect.origin.y = (globalScreenFrame.origin.y + globalScreenFrame.size.height) -
189 (rect.origin.y + rect.size.height);
190}
191
192- (void)updatePosition {
193 [self viewDidUpdateContents:self.flutterViewController.flutterView
194 withSize:self.flutterViewController.flutterView.bounds.size];
195}
196
197- (void)viewDidUpdateContents:(FlutterView*)view withSize:(NSSize)newSize {
198 if (_creationRequest.on_get_window_position == nullptr) {
199 // There is no positioner associated with this window.
200 return;
201 }
202
203 NSRect globalScreenFrame = ComputeGlobalScreenFrame();
204
205 NSRect parentRect =
206 [self.window.parentWindow contentRectForFrameRect:self.window.parentWindow.frame];
207 FlipRect(parentRect, globalScreenFrame);
208
209 NSRect screenRect = [self.window.screen visibleFrame];
210 FlipRect(screenRect, globalScreenFrame);
211
212 flutter::IsolateScope isolate_scope(*_isolate);
213 auto position = _creationRequest.on_get_window_position(
216
217 NSRect positionRect = position->toNSRect();
218 FlipRect(positionRect, globalScreenFrame);
219
220 [self.window setFrame:positionRect display:NO animate:NO];
221
222 free(position);
223
224 // For windows sized to contents if the positioner size doesn't match actual size
225 // the requested size needs to be passed through constraints.
226 if (view.sizedToContents &&
227 (positionRect.size.width < newSize.width || positionRect.size.height < newSize.height)) {
228 _positionerSizeConstraints = positionRect.size;
230 } else {
231 // Only show the window initially if positioner agrees with the size.
232 self.window.alphaValue = 1.0;
233 }
234}
235
236- (void)setConstraints:(FlutterWindowConstraints)constraints {
237 if (_flutterViewController.flutterView.sizedToContents) {
238 self->_creationRequest.constraints = constraints;
239 [_flutterViewController.flutterView constraintsDidChange];
240 } else {
241 [self.window flutterSetConstraints:constraints];
242 }
243}
244
245@end
246
247@implementation FlutterWindowController
248
249- (instancetype)init {
250 self = [super init];
251 if (self != nil) {
252 _windows = [NSMutableArray array];
253 }
254 return self;
255}
256
257- (FlutterViewIdentifier)createDialogWindow:(const FlutterWindowCreationRequest*)request {
258 FlutterViewController* controller = [[FlutterViewController alloc] initWithEngine:_engine
259 nibName:nil
260 bundle:nil];
261
262 NSWindow* window = [[NSWindow alloc] init];
263 // If this is not set there will be double free on window close when
264 // using ARC.
265 [window setReleasedWhenClosed:NO];
266
267 window.contentViewController = controller;
268 window.styleMask =
269 NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
270 window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
271 if (request->has_size) {
272 [window flutterSetContentSize:request->size];
273 }
274 if (request->has_constraints) {
275 [window flutterSetConstraints:request->constraints];
276 }
277
278 FlutterWindowOwner* owner = [[FlutterWindowOwner alloc] initWithWindow:window
279 flutterViewController:controller
280 creationRequest:*request];
281 window.delegate = owner;
282 [_windows addObject:owner];
283
284 NSWindow* parent = nil;
285
286 if (request->parent_view_id != 0) {
287 for (FlutterWindowOwner* owner in _windows) {
289 parent = owner.window;
290 break;
291 }
292 }
293 if (parent == nil) {
294 FML_LOG(WARNING) << "Failed to find parent window for ID " << request->parent_view_id;
295 }
296 }
297
298 if (parent != nil) {
299 dispatch_async(dispatch_get_main_queue(), ^{
300 // beginCriticalSheet blocks with nested run loop until the
301 // sheet animation is finished.
302 [parent beginCriticalSheet:window
303 completionHandler:^(NSModalResponse response){
304 }];
305 });
306
307 } else {
308 [window setIsVisible:YES];
309 [window makeKeyAndOrderFront:nil];
310 }
311
312 return controller.viewIdentifier;
313}
314
315- (FlutterViewIdentifier)createTooltipWindow:(const FlutterWindowCreationRequest*)request {
316 FlutterViewController* controller = [[FlutterViewController alloc] initWithEngine:_engine
317 nibName:nil
318 bundle:nil];
319
320 NSWindow* window = [[NSWindow alloc] init];
321 // If this is not set there will be double free on window close when
322 // using ARC.
323 [window setReleasedWhenClosed:NO];
324
325 window.contentViewController = controller;
326 window.styleMask = NSWindowStyleMaskBorderless;
327 window.hasShadow = NO;
328 window.opaque = NO;
329 window.backgroundColor = [NSColor clearColor];
330
331 FlutterWindowOwner* owner = [[FlutterWindowOwner alloc] initWithWindow:window
332 flutterViewController:controller
333 creationRequest:*request];
334
335 controller.flutterView.sizingDelegate = owner;
336 controller.flutterView.backgroundColor = [NSColor clearColor];
337 // Resend configure event after setting the sizing delegate.
338 [controller.flutterView constraintsDidChange];
339 owner.closeWhenParentResignsKey = YES;
340
341 window.delegate = owner;
342 [_windows addObject:owner];
343
344 NSWindow* parent = nil;
345
346 if (request->parent_view_id != 0) {
347 for (FlutterWindowOwner* owner in _windows) {
349 parent = owner.window;
350 break;
351 }
352 }
353 }
354
355 NSAssert(parent != nil, @"Tooltip window must have a parent window.");
356
357 window.ignoresMouseEvents = YES;
358 window.collectionBehavior = NSWindowCollectionBehaviorAuxiliary;
359 [parent addChildWindow:window ordered:NSWindowAbove];
360 window.alphaValue = 0.0;
361 return controller.viewIdentifier;
362}
363
364- (FlutterViewIdentifier)createRegularWindow:(const FlutterWindowCreationRequest*)request {
365 FlutterViewController* controller = [[FlutterViewController alloc] initWithEngine:_engine
366 nibName:nil
367 bundle:nil];
368
369 NSWindow* window = [[NSWindow alloc] init];
370 // If this is not set there will be double free on window close when
371 // using ARC.
372 [window setReleasedWhenClosed:NO];
373
374 window.contentViewController = controller;
375 window.styleMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled |
376 NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
377 if (request->has_size) {
378 [window flutterSetContentSize:request->size];
379 }
380 if (request->has_constraints) {
381 [window flutterSetConstraints:request->constraints];
382 }
383 [window setIsVisible:YES];
384 [window makeKeyAndOrderFront:nil];
385
386 FlutterWindowOwner* owner = [[FlutterWindowOwner alloc] initWithWindow:window
387 flutterViewController:controller
388 creationRequest:*request];
389 window.delegate = owner;
390 [_windows addObject:owner];
391
392 return controller.viewIdentifier;
393}
394
395- (void)destroyWindow:(NSWindow*)window {
396 FlutterWindowOwner* owner = nil;
397 for (FlutterWindowOwner* o in _windows) {
398 if (o.window == window) {
399 owner = o;
400 break;
401 }
402 }
403 if (owner != nil) {
404 [_windows removeObject:owner];
405 for (NSWindow* win in owner.window.sheets) {
406 [self destroyWindow:win];
407 }
408
409 for (NSWindow* win in owner.window.childWindows) {
410 [self destroyWindow:win];
411 }
412 // Make sure to unregister the controller from the engine and remove the FlutterView
413 // before destroying the window and Flutter NSView.
414 [owner.flutterViewController dispose];
415 owner.window.delegate = nil;
416 [owner.window close];
417 [owner windowWillClose];
418 }
419}
420
421- (void)closeAllWindows {
422 for (FlutterWindowOwner* owner in _windows) {
423 [owner.flutterViewController dispose];
424 [owner.window close];
425 }
426 [_windows removeAllObjects];
427}
428
429static BOOL IsChildAncestor(NSWindow* child, NSWindow* ancestor) {
430 NSWindow* current = child.parentWindow;
431 while (current) {
432 if (current == ancestor) {
433 return YES;
434 }
435 current = current.parentWindow;
436 }
437
438 return NO;
439}
440
441- (void)windowDidResignKey:(FlutterWindowOwner*)parent {
442 for (FlutterWindowOwner* possibleChild in _windows) {
443 if (possibleChild.closeWhenParentResignsKey &&
444 IsChildAncestor(possibleChild.window, parent.window)) {
445 [possibleChild windowShouldClose:possibleChild.window];
446 }
447 }
448}
449
450@end
451
452// NOLINTBEGIN(google-objc-function-naming)
453
455 int64_t engine_id,
456 const FlutterWindowCreationRequest* request) {
457 FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
458 [engine enableMultiView];
459 return [engine.windowController createRegularWindow:request];
460}
461
463 int64_t engine_id,
464 const FlutterWindowCreationRequest* request) {
465 FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
466 [engine enableMultiView];
467 return [engine.windowController createDialogWindow:request];
468}
469
471 int64_t engine_id,
472 const FlutterWindowCreationRequest* request) {
473 FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
474 [engine enableMultiView];
475 return [engine.windowController createTooltipWindow:request];
476}
477
478void InternalFlutter_Window_Destroy(int64_t engine_id, void* window) {
479 NSWindow* w = (__bridge NSWindow*)window;
480 FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
481 [engine.windowController destroyWindow:w];
482}
483
485 FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
486 FlutterViewController* controller = [engine viewControllerForIdentifier:view_id];
487 return (__bridge void*)controller.view.window;
488}
489
491 NSWindow* w = (__bridge NSWindow*)window;
492 NSRect contentRect = [w contentRectForFrameRect:w.frame];
493 return {
494 .width = contentRect.size.width,
495 .height = contentRect.size.height,
496 };
497}
498
500 NSWindow* w = (__bridge NSWindow*)window;
501 [w flutterSetContentSize:*size];
502}
503
506 const FlutterWindowConstraints* constraints) {
507 NSWindow* w = (__bridge NSWindow*)window;
508 FlutterWindowOwner* owner = (FlutterWindowOwner*)w.delegate;
509 [owner setConstraints:*constraints];
510}
511
512void InternalFlutter_Window_SetTitle(void* window, const char* title) {
513 NSWindow* w = (__bridge NSWindow*)window;
514 w.title = [NSString stringWithUTF8String:title];
515}
516
517void InternalFlutter_Window_SetMaximized(void* window, bool maximized) {
518 NSWindow* w = (__bridge NSWindow*)window;
519 if (maximized & !w.isZoomed) {
520 [w zoom:nil];
521 } else if (!maximized && w.isZoomed) {
522 [w zoom:nil];
523 }
524}
525
527 NSWindow* w = (__bridge NSWindow*)window;
528 return w.isZoomed;
529}
530
532 NSWindow* w = (__bridge NSWindow*)window;
533 [w miniaturize:nil];
534}
535
537 NSWindow* w = (__bridge NSWindow*)window;
538 [w deminiaturize:nil];
539}
540
542 NSWindow* w = (__bridge NSWindow*)window;
543 return w.isMiniaturized;
544}
545
546void InternalFlutter_Window_SetFullScreen(void* window, bool fullScreen) {
547 NSWindow* w = (__bridge NSWindow*)window;
548 bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0;
549 if (fullScreen && !isFullScreen) {
550 [w toggleFullScreen:nil];
551 } else if (!fullScreen && isFullScreen) {
552 [w toggleFullScreen:nil];
553 }
554}
555
557 NSWindow* w = (__bridge NSWindow*)window;
558 return (w.styleMask & NSWindowStyleMaskFullScreen) != 0;
559}
560
562 NSWindow* w = (__bridge NSWindow*)window;
563 [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
564 [w makeKeyAndOrderFront:nil];
565}
566
568 NSWindow* w = (__bridge NSWindow*)window;
569 return strdup(w.title.UTF8String);
570}
571
573 NSWindow* w = (__bridge NSWindow*)window;
574 return w.isKeyWindow;
575}
576
578 NSWindow* w = (__bridge NSWindow*)window;
579 FlutterWindowOwner* owner = (FlutterWindowOwner*)w.delegate;
580 [owner updatePosition];
581}
582
583// NOLINTEND(google-objc-function-naming)
#define FLUTTER_DARWIN_EXPORT
UIViewController< FlutterViewResponder > * _flutterViewController
void InternalFlutter_Window_Activate(void *window)
bool InternalFlutter_Window_IsMaximized(void *window)
int64_t InternalFlutter_WindowController_CreateTooltipWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
FLUTTER_DARWIN_EXPORT void InternalFlutter_Window_SetConstraints(void *window, const FlutterWindowConstraints *constraints)
bool InternalFlutter_Window_IsActivated(void *window)
int64_t InternalFlutter_WindowController_CreateDialogWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
int64_t InternalFlutter_WindowController_CreateRegularWindow(int64_t engine_id, const FlutterWindowCreationRequest *request)
void InternalFlutter_Window_SetFullScreen(void *window, bool fullScreen)
void InternalFlutter_Window_UpdatePosition(void *window)
bool InternalFlutter_Window_IsFullScreen(void *window)
void InternalFlutter_Window_SetContentSize(void *window, const FlutterWindowSize *size)
void InternalFlutter_Window_Minimize(void *window)
void * InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id)
void InternalFlutter_Window_SetTitle(void *window, const char *title)
bool InternalFlutter_Window_IsMinimized(void *window)
void InternalFlutter_Window_Destroy(int64_t engine_id, void *window)
void InternalFlutter_Window_SetMaximized(void *window, bool maximized)
char * InternalFlutter_Window_GetTitle(void *window)
FlutterWindowSize InternalFlutter_Window_GetContentSize(void *window)
void InternalFlutter_Window_Unminimize(void *window)
NSMutableArray< FlutterWindowOwner * > * _windows
static Isolate Current()
GLFWwindow * window
Definition main.cc:60
FlutterEngine engine
Definition main.cc:84
FlView * view
G_BEGIN_DECLS FlutterViewId view_id
#define FML_LOG(severity)
Definition logging.h:101
FlutterViewIdentifier viewIdentifier
UIScreen * screen()
void constraintsDidChange()
FlutterViewController * _flutterViewController
FlutterViewController * flutterViewController
std::optional< flutter::Isolate > _isolate
FlutterWindowCreationRequest _creationRequest
FlutterViewController * viewController
int64_t FlutterViewIdentifier
Definition ref_ptr.h:261
int32_t height
int32_t width
static FlutterWindowRect fromNSRect(const NSRect &rect)
static FlutterWindowSize fromNSSize(const NSSize &size)
int BOOL