Flutter macOS Embedder
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> {
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 
25 @property(readonly, nonatomic) NSWindow* window;
26 @property(readonly, nonatomic) FlutterViewController* flutterViewController;
27 
28 - (instancetype)initWithWindow:(NSWindow*)window
29  flutterViewController:(FlutterViewController*)viewController
30  creationRequest:(const FlutterWindowCreationRequest&)creationRequest;
31 
32 @end
33 
35 
36 - (void)flutterSetContentSize:(FlutterWindowSize)contentSize;
37 - (void)flutterSetConstraints:(FlutterWindowConstraints)constraints;
38 
39 @end
40 
41 @implementation NSWindow (FlutterWindowSizing)
42 - (void)flutterSetContentSize:(FlutterWindowSize)contentSize {
43  [self setContentSize:NSMakeSize(contentSize.width, contentSize.height)];
44 }
45 
46 - (void)flutterSetConstraints:(FlutterWindowConstraints)constraints {
47  NSSize size = [self frameRectForContentRect:self.frame].size;
48  NSSize originalSize = size;
49  [self setContentMinSize:NSMakeSize(constraints.min_width, constraints.min_height)];
50  size.width = std::max(size.width, constraints.min_width);
51  size.height = std::max(size.height, constraints.min_height);
52  if (constraints.max_width > 0 && constraints.max_height > 0) {
53  [self setContentMaxSize:NSMakeSize(constraints.max_width, constraints.max_height)];
54  size.width = std::min(size.width, constraints.max_width);
55  size.height = std::min(size.height, constraints.max_height);
56  } else {
57  [self setContentMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
58  }
59  if (!NSEqualSizes(originalSize, size)) {
60  [self setContentSize:size];
61  }
62 }
63 
64 @end
65 
66 @implementation FlutterWindowOwner
67 
68 @synthesize window = _window;
70 
71 - (instancetype)initWithWindow:(NSWindow*)window
72  flutterViewController:(FlutterViewController*)viewController
73  creationRequest:(const FlutterWindowCreationRequest&)creationRequest {
74  if (self = [super init]) {
75  _window = window;
76  _flutterViewController = viewController;
77  _creationRequest = creationRequest;
79  }
80  return self;
81 }
82 
83 - (void)windowDidBecomeKey:(NSNotification*)notification {
84  [_flutterViewController.engine windowDidBecomeKey:_flutterViewController.viewIdentifier];
85 }
86 
87 - (void)windowDidResignKey:(NSNotification*)notification {
88  [_flutterViewController.engine windowDidResignKey:_flutterViewController.viewIdentifier];
89 }
90 
91 - (BOOL)windowShouldClose:(NSWindow*)sender {
92  flutter::IsolateScope isolate_scope(*_isolate);
94  return NO;
95 }
96 
97 - (void)windowWillClose {
99 }
100 
101 - (void)windowDidResize:(NSNotification*)notification {
102  flutter::IsolateScope isolate_scope(*_isolate);
104 }
105 
106 // Miniaturize does not trigger resize event, but for now there
107 // is no other way to get notification about the state change.
108 - (void)windowDidMiniaturize:(NSNotification*)notification {
109  flutter::IsolateScope isolate_scope(*_isolate);
111 }
112 
113 // Deminiaturize does not trigger resize event, but for now there
114 // is no other way to get notification about the state change.
115 - (void)windowDidDeminiaturize:(NSNotification*)notification {
116  flutter::IsolateScope isolate_scope(*_isolate);
118 }
119 
120 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
121  flutter::IsolateScope isolate_scope(*_isolate);
123 }
124 
125 - (void)windowWillExitFullScreen:(NSNotification*)notification {
126  flutter::IsolateScope isolate_scope(*_isolate);
128 }
129 
130 @end
131 
132 @interface FlutterWindowController () {
133  NSMutableArray<FlutterWindowOwner*>* _windows;
134 }
135 
136 @end
137 
138 @implementation FlutterWindowController
139 
140 - (instancetype)init {
141  self = [super init];
142  if (self != nil) {
143  _windows = [NSMutableArray array];
144  }
145  return self;
146 }
147 
148 - (FlutterViewIdentifier)createDialogWindow:(const FlutterWindowCreationRequest*)request {
149  FlutterViewController* c = [[FlutterViewController alloc] initWithEngine:_engine
150  nibName:nil
151  bundle:nil];
152 
153  NSWindow* window = [[NSWindow alloc] init];
154  // If this is not set there will be double free on window close when
155  // using ARC.
156  [window setReleasedWhenClosed:NO];
157 
158  window.contentViewController = c;
159  window.styleMask =
160  NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
161  window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
162  if (request->has_size) {
163  [window flutterSetContentSize:request->size];
164  }
165  if (request->has_constraints) {
166  [window flutterSetConstraints:request->constraints];
167  }
168 
169  FlutterWindowOwner* w = [[FlutterWindowOwner alloc] initWithWindow:window
170  flutterViewController:c
171  creationRequest:*request];
172  window.delegate = w;
173  [_windows addObject:w];
174 
175  NSWindow* parent = nil;
176 
177  if (request->parent_view_id != 0) {
178  for (FlutterWindowOwner* owner in _windows) {
179  if (owner.flutterViewController.viewIdentifier == request->parent_view_id) {
180  parent = owner.window;
181  break;
182  }
183  }
184  if (parent == nil) {
185  FML_LOG(WARNING) << "Failed to find parent window for ID " << request->parent_view_id;
186  }
187  }
188 
189  if (parent != nil) {
190  [parent beginCriticalSheet:window
191  completionHandler:^(NSModalResponse response){
192  }];
193  } else {
194  [window setIsVisible:YES];
195  [window makeKeyAndOrderFront:nil];
196  }
197 
198  return c.viewIdentifier;
199 }
200 
201 - (FlutterViewIdentifier)createRegularWindow:(const FlutterWindowCreationRequest*)request {
202  FlutterViewController* c = [[FlutterViewController alloc] initWithEngine:_engine
203  nibName:nil
204  bundle:nil];
205 
206  NSWindow* window = [[NSWindow alloc] init];
207  // If this is not set there will be double free on window close when
208  // using ARC.
209  [window setReleasedWhenClosed:NO];
210 
211  window.contentViewController = c;
212  window.styleMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled |
213  NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
214  if (request->has_size) {
215  [window flutterSetContentSize:request->size];
216  }
217  if (request->has_constraints) {
218  [window flutterSetConstraints:request->constraints];
219  }
220  [window setIsVisible:YES];
221  [window makeKeyAndOrderFront:nil];
222 
223  FlutterWindowOwner* w = [[FlutterWindowOwner alloc] initWithWindow:window
224  flutterViewController:c
225  creationRequest:*request];
226  window.delegate = w;
227  [_windows addObject:w];
228 
229  return c.viewIdentifier;
230 }
231 
232 - (void)destroyWindow:(NSWindow*)window {
233  FlutterWindowOwner* owner = nil;
234  for (FlutterWindowOwner* o in _windows) {
235  if (o.window == window) {
236  owner = o;
237  break;
238  }
239  }
240  if (owner != nil) {
241  [_windows removeObject:owner];
242 
243  for (NSWindow* win in owner.window.sheets) {
244  [self destroyWindow:win];
245  }
246 
247  for (NSWindow* win in owner.window.childWindows) {
248  [self destroyWindow:win];
249  }
250 
251  // Make sure to unregister the controller from the engine and remove the FlutterView
252  // before destroying the window and Flutter NSView.
253  [owner.flutterViewController dispose];
254  owner.window.delegate = nil;
255  [owner.window close];
256  [owner windowWillClose];
257  }
258 }
259 
260 - (void)closeAllWindows {
261  for (FlutterWindowOwner* owner in _windows) {
262  [owner.flutterViewController dispose];
263  [owner.window close];
264  }
265  [_windows removeAllObjects];
266 }
267 
268 @end
269 
270 // NOLINTBEGIN(google-objc-function-naming)
271 
273  int64_t engine_id,
274  const FlutterWindowCreationRequest* request) {
275  FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
276  [engine enableMultiView];
277  return [engine.windowController createRegularWindow:request];
278 }
279 
281  int64_t engine_id,
282  const FlutterWindowCreationRequest* request) {
283  FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
284  [engine enableMultiView];
285  return [engine.windowController createDialogWindow:request];
286 }
287 
288 void InternalFlutter_Window_Destroy(int64_t engine_id, void* window) {
289  NSWindow* w = (__bridge NSWindow*)window;
290  FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
291  [engine.windowController destroyWindow:w];
292 }
293 
294 void* InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id) {
295  FlutterEngine* engine = [FlutterEngine engineForIdentifier:engine_id];
296  FlutterViewController* controller = [engine viewControllerForIdentifier:view_id];
297  return (__bridge void*)controller.view.window;
298 }
299 
301  NSWindow* w = (__bridge NSWindow*)window;
302  NSRect contentRect = [w contentRectForFrameRect:w.frame];
303  return {
304  .width = contentRect.size.width,
305  .height = contentRect.size.height,
306  };
307 }
308 
310  NSWindow* w = (__bridge NSWindow*)window;
311  [w flutterSetContentSize:*size];
312 }
313 
316  const FlutterWindowConstraints* constraints) {
317  NSWindow* w = (__bridge NSWindow*)window;
318  [w flutterSetConstraints:*constraints];
319 }
320 
321 void InternalFlutter_Window_SetTitle(void* window, const char* title) {
322  NSWindow* w = (__bridge NSWindow*)window;
323  w.title = [NSString stringWithUTF8String:title];
324 }
325 
326 void InternalFlutter_Window_SetMaximized(void* window, bool maximized) {
327  NSWindow* w = (__bridge NSWindow*)window;
328  if (maximized & !w.isZoomed) {
329  [w zoom:nil];
330  } else if (!maximized && w.isZoomed) {
331  [w zoom:nil];
332  }
333 }
334 
336  NSWindow* w = (__bridge NSWindow*)window;
337  return w.isZoomed;
338 }
339 
341  NSWindow* w = (__bridge NSWindow*)window;
342  [w miniaturize:nil];
343 }
344 
346  NSWindow* w = (__bridge NSWindow*)window;
347  [w deminiaturize:nil];
348 }
349 
351  NSWindow* w = (__bridge NSWindow*)window;
352  return w.isMiniaturized;
353 }
354 
355 void InternalFlutter_Window_SetFullScreen(void* window, bool fullScreen) {
356  NSWindow* w = (__bridge NSWindow*)window;
357  bool isFullScreen = (w.styleMask & NSWindowStyleMaskFullScreen) != 0;
358  if (fullScreen && !isFullScreen) {
359  [w toggleFullScreen:nil];
360  } else if (!fullScreen && isFullScreen) {
361  [w toggleFullScreen:nil];
362  }
363 }
364 
366  NSWindow* w = (__bridge NSWindow*)window;
367  return (w.styleMask & NSWindowStyleMaskFullScreen) != 0;
368 }
369 
371  NSWindow* w = (__bridge NSWindow*)window;
372  [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
373  [w makeKeyAndOrderFront:nil];
374 }
375 
376 char* InternalFlutter_Window_GetTitle(void* window) {
377  NSWindow* w = (__bridge NSWindow*)window;
378  return strdup(w.title.UTF8String);
379 }
380 
382  NSWindow* w = (__bridge NSWindow*)window;
383  return w.isKeyWindow;
384 }
385 
386 // NOLINTEND(google-objc-function-naming)
#define FLUTTER_DARWIN_EXPORT
Definition: FlutterMacros.h:14
int64_t FlutterViewIdentifier
void InternalFlutter_Window_Activate(void *window)
void * InternalFlutter_Window_GetHandle(int64_t engine_id, FlutterViewIdentifier view_id)
bool InternalFlutter_Window_IsMaximized(void *window)
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)
bool InternalFlutter_Window_IsFullScreen(void *window)
void InternalFlutter_Window_SetContentSize(void *window, const FlutterWindowSize *size)
void InternalFlutter_Window_Minimize(void *window)
char * InternalFlutter_Window_GetTitle(void *window)
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)
FlutterWindowSize InternalFlutter_Window_GetContentSize(void *window)
void InternalFlutter_Window_Unminimize(void *window)
NSMutableArray< FlutterWindowOwner * > * _windows
static Isolate Current()
Definition: isolate_scope.cc:9
FlutterViewIdentifier viewIdentifier
FlutterViewController * _flutterViewController
FlutterViewController * flutterViewController
std::optional< flutter::Isolate > _isolate
FlutterWindowCreationRequest _creationRequest