Flutter macOS Embedder
FlutterView.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 
7 #import <QuartzCore/QuartzCore.h>
8 
9 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
10 #import "flutter/shell/platform/darwin/macos/InternalFlutterSwift/InternalFlutterSwift.h"
12 
15  __weak id<FlutterViewDelegate> _viewDelegate;
16  FlutterResizeSynchronizer* _resizeSynchronizer;
18  NSCursor* _lastCursor;
19 }
20 
21 @end
22 
23 @implementation FlutterView
24 
25 - (instancetype)initWithMTLDevice:(id<MTLDevice>)device
26  commandQueue:(id<MTLCommandQueue>)commandQueue
27  delegate:(id<FlutterViewDelegate>)delegate
28  viewIdentifier:(FlutterViewIdentifier)viewIdentifier
29  enableWideGamut:(BOOL)enableWideGamut {
30  self = [super initWithFrame:NSZeroRect];
31  if (self) {
32  [self setWantsLayer:YES];
33  [self setBackgroundColor:[NSColor blackColor]];
34  [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawDuringViewResize];
35  _viewIdentifier = viewIdentifier;
36  _viewDelegate = delegate;
37  _surfaceManager = [[FlutterSurfaceManager alloc] initWithDevice:device
38  commandQueue:commandQueue
39  layer:self.layer
40  delegate:self
41  wideGamut:enableWideGamut];
42  _resizeSynchronizer = [[FlutterResizeSynchronizer alloc] init];
43  }
44  return self;
45 }
46 
47 - (void)onPresent:(CGSize)frameSize withBlock:(dispatch_block_t)block delay:(NSTimeInterval)delay {
48  // This block will be called in main thread same run loop turn as the layer content
49  // update.
50  auto notifyBlock = ^{
51  NSSize scaledSize = [self convertSizeFromBacking:frameSize];
52  [self.sizingDelegate viewDidUpdateContents:self withSize:scaledSize];
53  block();
54  };
55  [_resizeSynchronizer performCommitForSize:frameSize afterDelay:delay notify:notifyBlock];
56 }
57 
59  return _surfaceManager;
60 }
61 
62 - (void)setEnableWideGamut:(BOOL)enableWideGamut {
63  [_surfaceManager setEnableWideGamut:enableWideGamut];
64 }
65 
66 - (void)shutDown {
67  [_resizeSynchronizer shutDown];
68 }
69 
70 - (void)setBackgroundColor:(NSColor*)color {
71  self.layer.backgroundColor = color.CGColor;
72 }
73 
74 #pragma mark - NSView overrides
75 
76 - (void)setFrameSize:(NSSize)newSize {
77  [super setFrameSize:newSize];
78  if (!self.sizedToContents) {
79  CGSize scaledSize = [self convertSizeToBacking:self.bounds.size];
80  [_resizeSynchronizer beginResizeForSize:scaledSize
81  notify:^{
82  [_viewDelegate viewDidReshape:self];
83  }
84  onTimeout:^{
85  [FlutterLogger logError:@"Resize timed out"];
86  }];
87  }
88 }
89 
90 /**
91  * Declares that the view uses a flipped coordinate system, consistent with Flutter conventions.
92  */
93 - (BOOL)isFlipped {
94  return YES;
95 }
96 
97 - (BOOL)isOpaque {
98  return YES;
99 }
100 
101 /**
102  * Declares that the initial mouse-down when the view is not in focus will send an event to the
103  * view.
104  */
105 - (BOOL)acceptsFirstMouse:(NSEvent*)event {
106  return YES;
107 }
108 
109 - (BOOL)acceptsFirstResponder {
110  // This is to ensure that FlutterView does not take first responder status from TextInputPlugin
111  // on mouse clicks.
112  return [_viewDelegate viewShouldAcceptFirstResponder:self];
113 }
114 
115 - (void)didUpdateMouseCursor:(NSCursor*)cursor {
116  _lastCursor = cursor;
117 }
118 
119 // Restores mouse cursor. There are few cases when this is needed and framework will not handle
120 // this automatically:
121 // - When mouse cursor leaves subview of FlutterView (technically still within bound of
122 // FlutterView tracking area so the framework won't be notified)
123 // - When context menu above FlutterView is closed. Context menu will change current cursor to
124 // arrow and will not restore it back.
125 - (void)cursorUpdate:(NSEvent*)event {
126  // Make sure to not override cursor when over a platform view.
127  NSPoint mouseLocation = [[self superview] convertPoint:event.locationInWindow fromView:nil];
128  NSView* hitTestView = [self hitTest:mouseLocation];
129  if (hitTestView != self) {
130  return;
131  }
132  [_lastCursor set];
133  // It is possible that there is a platform view with NSTrackingArea below flutter content.
134  // This could override the mouse cursor as a result of mouse move event. There is no good way
135  // to prevent that short of swizzling [NSCursor set], so as a workaround force flutter cursor
136  // in next runloop turn. This is not ideal, as it may cause the cursor flicker a bit.
137  [[NSRunLoop currentRunLoop] performBlock:^{
138  [_lastCursor set];
139  }];
140 }
141 
142 - (void)viewDidChangeBackingProperties {
143  [super viewDidChangeBackingProperties];
144  // Force redraw
145  [_viewDelegate viewDidReshape:self];
146 }
147 
148 - (BOOL)layer:(CALayer*)layer
149  shouldInheritContentsScale:(CGFloat)newScale
150  fromWindow:(NSWindow*)window {
151  return YES;
152 }
153 
154 #pragma mark - NSAccessibility overrides
155 
156 - (BOOL)isAccessibilityElement {
157  return YES;
158 }
159 
160 - (NSAccessibilityRole)accessibilityRole {
161  return NSAccessibilityGroupRole;
162 }
163 
164 - (NSString*)accessibilityLabel {
165  // TODO(chunhtai): Provides a way to let developer customize the accessibility
166  // label.
167  // https://github.com/flutter/flutter/issues/75446
168  NSString* applicationName =
169  [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
170  if (!applicationName) {
171  applicationName = [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
172  }
173  return applicationName;
174 }
175 
176 - (BOOL)sizedToContents {
177  return _sizingDelegate != nil && [_sizingDelegate minimumViewSize:self] != std::nullopt;
178 }
179 
180 - (NSSize)minimumContentSize {
181  if (_sizingDelegate != nil) {
182  std::optional<NSSize> minSize = [_sizingDelegate minimumViewSize:self];
183  if (minSize) {
184  return *minSize;
185  }
186  }
187  return self.bounds.size;
188 }
189 
190 - (NSSize)maximumContentSize {
191  if (_sizingDelegate != nil) {
192  std::optional<NSSize> maxSize = [_sizingDelegate maximumViewSize:self];
193  if (maxSize) {
194  return *maxSize;
195  }
196  }
197  return self.bounds.size;
198 }
199 
200 - (void)constraintsDidChange {
201  [_viewDelegate viewDidReshape:self];
202 }
203 
204 @end
int64_t FlutterViewIdentifier
__weak id< FlutterViewDelegate > _viewDelegate
Definition: FlutterView.mm:15
NSCursor * _lastCursor
Definition: FlutterView.mm:18
FlutterSurfaceManager * _surfaceManager
Definition: FlutterView.mm:17
FlutterViewIdentifier _viewIdentifier
Definition: FlutterView.mm:14
FlutterResizeSynchronizer * _resizeSynchronizer
Definition: FlutterView.mm:16
CGSize maximumContentSize
Definition: FlutterView.h:133
void shutDown()
Definition: FlutterView.mm:66
BOOL sizedToContents
Definition: FlutterView.h:121
FlutterSurfaceManager * surfaceManager
Definition: FlutterView.h:89
void constraintsDidChange()
Definition: FlutterView.mm:200
CGSize minimumContentSize
Definition: FlutterView.h:127