| 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
5 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
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"
11 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
12
13 @interface FlutterView () <FlutterSurfaceManagerDelegate> {
14 FlutterViewIdentifier _viewIdentifier;
15 __weak id<FlutterViewDelegate> _viewDelegate;
16 FlutterResizeSynchronizer* _resizeSynchronizer;
17 FlutterSurfaceManager* _surfaceManager;
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
58 - (FlutterSurfaceManager*)surfaceManager {
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
FlutterSurfaceManager.h
FlutterView.h
FlutterViewIdentifier
int64_t FlutterViewIdentifier
Definition: FlutterViewController.h:21
FlutterView()::_viewDelegate
__weak id< FlutterViewDelegate > _viewDelegate
Definition: FlutterView.mm:15
FlutterView()::_lastCursor
NSCursor * _lastCursor
Definition: FlutterView.mm:18
FlutterView()::_surfaceManager
FlutterSurfaceManager * _surfaceManager
Definition: FlutterView.mm:17
FlutterView()::_viewIdentifier
FlutterViewIdentifier _viewIdentifier
Definition: FlutterView.mm:14
FlutterView()::_resizeSynchronizer
FlutterResizeSynchronizer * _resizeSynchronizer
Definition: FlutterView.mm:16
FlutterSurfaceManager
Definition: FlutterSurfaceManager.h:47
FlutterView
Definition: FlutterView.h:69
FlutterView::maximumContentSize
CGSize maximumContentSize
Definition: FlutterView.h:133
-[FlutterView shutDown]
void shutDown()
Definition: FlutterView.mm:66
FlutterView::sizedToContents
BOOL sizedToContents
Definition: FlutterView.h:121
FlutterView::surfaceManager
FlutterSurfaceManager * surfaceManager
Definition: FlutterView.h:89
-[FlutterView constraintsDidChange]
void constraintsDidChange()
Definition: FlutterView.mm:200
FlutterView::minimumContentSize
CGSize minimumContentSize
Definition: FlutterView.h:127
FlutterSurfaceManagerDelegate-p
Definition: FlutterSurfaceManager.h:27
FlutterViewDelegate-p
Definition: FlutterView.h:51