Flutter Engine
FlutterViewController.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/Headers/FlutterViewController.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
7 
8 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
10 #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
11 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
12 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
13 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
14 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
15 #import "flutter/shell/platform/embedder/embedder.h"
16 
17 namespace {
18 
19 /// Clipboard plain text format.
20 constexpr char kTextPlainFormat[] = "text/plain";
21 
22 /**
23  * State tracking for mouse events, to adapt between the events coming from the system and the
24  * events that the embedding API expects.
25  */
26 struct MouseState {
27  /**
28  * Whether or not a kAdd event has been sent (or sent again since the last kRemove if tracking is
29  * enabled). Used to determine whether to send a kAdd event before sending an incoming mouse
30  * event, since Flutter expects pointers to be added before events are sent for them.
31  */
32  bool flutter_state_is_added = false;
33 
34  /**
35  * Whether or not a kDown has been sent since the last kAdd/kUp.
36  */
37  bool flutter_state_is_down = false;
38 
39  /**
40  * Whether or not mouseExited: was received while a button was down. Cocoa's behavior when
41  * dragging out of a tracked area is to send an exit, then keep sending drag events until the last
42  * button is released. If it was released inside the view, mouseEntered: is sent the next time the
43  * mouse moves. Flutter doesn't expect to receive events after a kRemove, so the kRemove for the
44  * exit needs to be delayed until after the last mouse button is released.
45  */
46  bool has_pending_exit = false;
47 
48  /**
49  * The currently pressed buttons, as represented in FlutterPointerEvent.
50  */
51  int64_t buttons = 0;
52 
53  /**
54  * Resets all state to default values.
55  */
56  void Reset() {
57  flutter_state_is_added = false;
58  flutter_state_is_down = false;
59  has_pending_exit = false;
60  buttons = 0;
61  }
62 };
63 
64 /**
65  * State tracking for keyboard events, to adapt between the events coming from the system and the
66  * events that the embedding API expects.
67  */
68 struct KeyboardState {
69  /**
70  * The last known pressed modifier flag keys.
71  */
72  uint64_t previously_pressed_flags = 0;
73 };
74 
75 } // namespace
76 
77 #pragma mark - Private interface declaration.
78 
79 /**
80  * Private interface declaration for FlutterViewController.
81  */
82 @interface FlutterViewController () <FlutterViewReshapeListener>
83 
84 /**
85  * A list of additional responders to keyboard events. Keybord events are forwarded to all of them.
86  */
87 @property(nonatomic) NSMutableOrderedSet<NSResponder*>* additionalKeyResponders;
88 
89 /**
90  * The tracking area used to generate hover events, if enabled.
91  */
92 @property(nonatomic) NSTrackingArea* trackingArea;
93 
94 /**
95  * The current state of the mouse and the sent mouse events.
96  */
97 @property(nonatomic) MouseState mouseState;
98 
99 /**
100  * The current state of the keyboard and pressed keys.
101  */
102 @property(nonatomic) KeyboardState keyboardState;
103 
104 /**
105  * Event monitor for keyUp events.
106  */
107 @property(nonatomic) id keyUpMonitor;
108 
109 /**
110  * Starts running |engine|, including any initial setup.
111  */
112 - (BOOL)launchEngine;
113 
114 /**
115  * Updates |trackingArea| for the current tracking settings, creating it with
116  * the correct mode if tracking is enabled, or removing it if not.
117  */
118 - (void)configureTrackingArea;
119 
120 /**
121  * Creates and registers plugins used by this view controller.
122  */
123 - (void)addInternalPlugins;
124 
125 /**
126  * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.
127  *
128  * mouseState.buttons should be updated before calling this method.
129  */
130 - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
131 
132 /**
133  * Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.
134  */
135 - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
136 
137 /**
138  * Converts |event| to a key event channel message, and sends it to the engine.
139  */
140 - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type;
141 
142 /**
143  * Initializes the KVO for user settings and passes the initial user settings to the engine.
144  */
145 - (void)sendInitialSettings;
146 
147 /**
148  * Responds to updates in the user settings and passes this data to the engine.
149  */
150 - (void)onSettingsChanged:(NSNotification*)notification;
151 
152 /**
153  * Handles messages received from the Flutter engine on the _*Channel channels.
154  */
155 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
156 
157 /**
158  * Plays a system sound. |soundType| specifies which system sound to play. Valid
159  * values can be found in the SystemSoundType enum in the services SDK package.
160  */
161 - (void)playSystemSound:(NSString*)soundType;
162 
163 /**
164  * Reads the data from the clipboard. |format| specifies the media type of the
165  * data to obtain.
166  */
167 - (NSDictionary*)getClipboardData:(NSString*)format;
168 
169 /**
170  * Clears contents and writes new data into clipboard. |data| is a dictionary where
171  * the keys are the type of data, and tervalue the data to be stored.
172  */
173 - (void)setClipboardData:(NSDictionary*)data;
174 
175 /**
176  * Returns true iff the clipboard contains nonempty string data.
177  */
178 - (BOOL)clipboardHasStrings;
179 
180 @end
181 
182 #pragma mark - FlutterViewController implementation.
183 
184 @implementation FlutterViewController {
185  // The project to run in this controller's engine.
187 
188  // The plugin used to handle text input. This is not an FlutterPlugin, so must be owned
189  // separately.
191 
192  // A message channel for passing key events to the Flutter engine. This should be replaced with
193  // an embedding API; see Issue #47.
195 
196  // A message channel for sending user settings to the flutter engine.
198 
199  // A method channel for miscellaneous platform functionality.
201 }
202 
203 @dynamic view;
204 
205 /**
206  * Performs initialization that's common between the different init paths.
207  */
208 static void CommonInit(FlutterViewController* controller) {
209  controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
210  project:controller->_project
211  allowHeadlessExecution:NO];
212  controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
213  controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
214 }
215 
216 - (instancetype)initWithCoder:(NSCoder*)coder {
217  self = [super initWithCoder:coder];
218  NSAssert(self, @"Super init cannot be nil");
219 
220  CommonInit(self);
221  return self;
222 }
223 
224 - (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
225  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
226  NSAssert(self, @"Super init cannot be nil");
227 
228  CommonInit(self);
229  return self;
230 }
231 
232 - (instancetype)initWithProject:(nullable FlutterDartProject*)project {
233  self = [super initWithNibName:nil bundle:nil];
234  NSAssert(self, @"Super init cannot be nil");
235 
236  _project = project;
237  CommonInit(self);
238  return self;
239 }
240 
241 - (void)loadView {
242  NSOpenGLContext* resourceContext = _engine.resourceContext;
243  if (!resourceContext) {
244  NSLog(@"Unable to create FlutterView; no resource context available.");
245  return;
246  }
247  FlutterView* flutterView = [[FlutterView alloc] initWithShareContext:resourceContext
248  reshapeListener:self];
249  self.view = flutterView;
250 }
251 
252 - (void)viewDidLoad {
253  [self configureTrackingArea];
254 }
255 
256 - (void)viewWillAppear {
257  [super viewWillAppear];
258  if (!_engine.running) {
259  [self launchEngine];
260  }
261  [self listenForMetaModifiedKeyUpEvents];
262 }
263 
264 - (void)viewWillDisappear {
265  // Per Apple's documentation, it is discouraged to call removeMonitor: in dealloc, and it's
266  // recommended to be called earlier in the lifecycle.
267  [NSEvent removeMonitor:_keyUpMonitor];
268  _keyUpMonitor = nil;
269 }
270 
271 - (void)dealloc {
272  _engine.viewController = nil;
273 }
274 
275 #pragma mark - Public methods
276 
277 - (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {
278  if (_mouseTrackingMode == mode) {
279  return;
280  }
281  _mouseTrackingMode = mode;
282  [self configureTrackingArea];
283 }
284 
285 #pragma mark - Framework-internal methods
286 
287 - (FlutterView*)flutterView {
288  return static_cast<FlutterView*>(self.view);
289 }
290 
291 - (void)addKeyResponder:(NSResponder*)responder {
292  [self.additionalKeyResponders addObject:responder];
293 }
294 
295 - (void)removeKeyResponder:(NSResponder*)responder {
296 }
297 
298 #pragma mark - Private methods
299 
300 - (BOOL)launchEngine {
301  // Register internal plugins before starting the engine.
302  [self addInternalPlugins];
303 
304  _engine.viewController = self;
305  if (![_engine runWithEntrypoint:nil]) {
306  return NO;
307  }
308  // Send the initial user settings such as brightness and text scale factor
309  // to the engine.
310  // TODO(stuartmorgan): Move this logic to FlutterEngine.
311  [self sendInitialSettings];
312  return YES;
313 }
314 
315 // macOS does not call keyUp: on a key while the command key is pressed. This results in a loss
316 // of a key event once the modified key is released. This method registers the
317 // ViewController as a listener for a keyUp event before it's handled by NSApplication, and should
318 // NOT modify the event to avoid any unexpected behavior.
319 - (void)listenForMetaModifiedKeyUpEvents {
320  NSAssert(_keyUpMonitor == nil, @"_keyUpMonitor was already created");
321  FlutterViewController* __weak weakSelf = self;
322  _keyUpMonitor = [NSEvent
323  addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
324  handler:^NSEvent*(NSEvent* event) {
325  // Intercept keyUp only for events triggered on the current
326  // view.
327  if (weakSelf.view &&
328  ([[event window] firstResponder] == weakSelf.view) &&
329  ([event modifierFlags] & NSEventModifierFlagCommand) &&
330  ([event type] == NSEventTypeKeyUp))
331  [weakSelf keyUp:event];
332  return event;
333  }];
334 }
335 
336 - (void)configureTrackingArea {
337  if (_mouseTrackingMode != FlutterMouseTrackingModeNone && self.view) {
338  NSTrackingAreaOptions options =
339  NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect;
340  switch (_mouseTrackingMode) {
341  case FlutterMouseTrackingModeInKeyWindow:
342  options |= NSTrackingActiveInKeyWindow;
343  break;
344  case FlutterMouseTrackingModeInActiveApp:
345  options |= NSTrackingActiveInActiveApp;
346  break;
347  case FlutterMouseTrackingModeAlways:
348  options |= NSTrackingActiveAlways;
349  break;
350  default:
351  NSLog(@"Error: Unrecognized mouse tracking mode: %ld", _mouseTrackingMode);
352  return;
353  }
354  _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect
355  options:options
356  owner:self
357  userInfo:nil];
358  [self.view addTrackingArea:_trackingArea];
359  } else if (_trackingArea) {
360  [self.view removeTrackingArea:_trackingArea];
361  _trackingArea = nil;
362  }
363 }
364 
365 - (void)addInternalPlugins {
366  [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
367  _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self];
370  binaryMessenger:_engine.binaryMessenger
374  binaryMessenger:_engine.binaryMessenger
377  [FlutterMethodChannel methodChannelWithName:@"flutter/platform"
378  binaryMessenger:_engine.binaryMessenger
379  codec:[FlutterJSONMethodCodec sharedInstance]];
380  __weak FlutterViewController* weakSelf = self;
381  [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
382  [weakSelf handleMethodCall:call result:result];
383  }];
384 }
385 
386 - (void)dispatchMouseEvent:(nonnull NSEvent*)event {
387  FlutterPointerPhase phase = _mouseState.buttons == 0
388  ? (_mouseState.flutter_state_is_down ? kUp : kHover)
389  : (_mouseState.flutter_state_is_down ? kMove : kDown);
390  [self dispatchMouseEvent:event phase:phase];
391 }
392 
393 - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
394  // There are edge cases where the system will deliver enter out of order relative to other
395  // events (e.g., drag out and back in, release, then click; mouseDown: will be called before
396  // mouseEntered:). Discard those events, since the add will already have been synthesized.
397  if (_mouseState.flutter_state_is_added && phase == kAdd) {
398  return;
399  }
400 
401  // If a pointer added event hasn't been sent, synthesize one using this event for the basic
402  // information.
403  if (!_mouseState.flutter_state_is_added && phase != kAdd) {
404  // Only the values extracted for use in flutterEvent below matter, the rest are dummy values.
405  NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered
406  location:event.locationInWindow
407  modifierFlags:0
408  timestamp:event.timestamp
409  windowNumber:event.windowNumber
410  context:nil
411  eventNumber:0
412  trackingNumber:0
413  userData:NULL];
414  [self dispatchMouseEvent:addEvent phase:kAdd];
415  }
416 
417  NSPoint locationInView = [self.view convertPoint:event.locationInWindow fromView:nil];
418  NSPoint locationInBackingCoordinates = [self.view convertPointToBacking:locationInView];
419  FlutterPointerEvent flutterEvent = {
420  .struct_size = sizeof(flutterEvent),
421  .phase = phase,
422  .timestamp = static_cast<size_t>(event.timestamp * USEC_PER_SEC),
423  .x = locationInBackingCoordinates.x,
424  .y = -locationInBackingCoordinates.y, // convertPointToBacking makes this negative.
425  .device_kind = kFlutterPointerDeviceKindMouse,
426  // If a click triggered a synthesized kAdd, don't pass the buttons in that event.
427  .buttons = phase == kAdd ? 0 : _mouseState.buttons,
428  };
429 
430  if (event.type == NSEventTypeScrollWheel) {
432 
433  double pixelsPerLine = 1.0;
434  if (!event.hasPreciseScrollingDeltas) {
435  CGEventSourceRef source = CGEventCreateSourceFromEvent(event.CGEvent);
436  pixelsPerLine = CGEventSourceGetPixelsPerLine(source);
437  if (source) {
438  CFRelease(source);
439  }
440  }
441  double scaleFactor = self.view.layer.contentsScale;
442  flutterEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
443  flutterEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
444  }
445  [_engine sendPointerEvent:flutterEvent];
446 
447  // Update tracking of state as reported to Flutter.
448  if (phase == kDown) {
449  _mouseState.flutter_state_is_down = true;
450  } else if (phase == kUp) {
451  _mouseState.flutter_state_is_down = false;
452  if (_mouseState.has_pending_exit) {
453  [self dispatchMouseEvent:event phase:kRemove];
454  _mouseState.has_pending_exit = false;
455  }
456  } else if (phase == kAdd) {
457  _mouseState.flutter_state_is_added = true;
458  } else if (phase == kRemove) {
459  _mouseState.Reset();
460  }
461 }
462 
463 - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type {
464  NSMutableDictionary* keyMessage = [@{
465  @"keymap" : @"macos",
466  @"type" : type,
467  @"keyCode" : @(event.keyCode),
468  @"modifiers" : @(event.modifierFlags),
469  } mutableCopy];
470  // Calling these methods on any other type of event will raise an exception.
471  if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
472  keyMessage[@"characters"] = event.characters;
473  keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
474  }
475  [_keyEventChannel sendMessage:keyMessage];
476 }
477 
478 - (void)onSettingsChanged:(NSNotification*)notification {
479  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
480  NSString* brightness =
481  [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
482  [_settingsChannel sendMessage:@{
483  @"platformBrightness" : [brightness isEqualToString:@"Dark"] ? @"dark" : @"light",
484  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32006.
485  @"textScaleFactor" : @1.0,
486  @"alwaysUse24HourFormat" : @false
487  }];
488 }
489 
490 - (void)sendInitialSettings {
491  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.
492  [[NSDistributedNotificationCenter defaultCenter]
493  addObserver:self
494  selector:@selector(onSettingsChanged:)
495  name:@"AppleInterfaceThemeChangedNotification"
496  object:nil];
497  [self onSettingsChanged:nil];
498 }
499 
500 - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
501  if ([call.method isEqualToString:@"SystemNavigator.pop"]) {
502  [NSApp terminate:self];
503  result(nil);
504  } else if ([call.method isEqualToString:@"SystemSound.play"]) {
505  [self playSystemSound:call.arguments];
506  result(nil);
507  } else if ([call.method isEqualToString:@"Clipboard.getData"]) {
508  result([self getClipboardData:call.arguments]);
509  } else if ([call.method isEqualToString:@"Clipboard.setData"]) {
510  [self setClipboardData:call.arguments];
511  result(nil);
512  } else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
513  result(@{@"value" : @([self clipboardHasStrings])});
514  } else {
516  }
517 }
518 
519 - (void)playSystemSound:(NSString*)soundType {
520  if ([soundType isEqualToString:@"SystemSoundType.alert"]) {
521  NSBeep();
522  }
523 }
524 
525 - (NSDictionary*)getClipboardData:(NSString*)format {
526  NSPasteboard* pasteboard = self.pasteboard;
527  if ([format isEqualToString:@(kTextPlainFormat)]) {
528  NSString* stringInPasteboard = [pasteboard stringForType:NSPasteboardTypeString];
529  return stringInPasteboard == nil ? nil : @{@"text" : stringInPasteboard};
530  }
531  return nil;
532 }
533 
534 - (void)setClipboardData:(NSDictionary*)data {
535  NSPasteboard* pasteboard = self.pasteboard;
536  NSString* text = data[@"text"];
537  [pasteboard clearContents];
538  if (text && ![text isEqual:[NSNull null]]) {
539  [pasteboard setString:text forType:NSPasteboardTypeString];
540  }
541 }
542 
543 - (BOOL)clipboardHasStrings {
544  return [self.pasteboard stringForType:NSPasteboardTypeString].length > 0;
545 }
546 
547 - (NSPasteboard*)pasteboard {
548  return [NSPasteboard generalPasteboard];
549 }
550 
551 #pragma mark - FlutterViewReshapeListener
552 
553 /**
554  * Responds to view reshape by notifying the engine of the change in dimensions.
555  */
556 - (void)viewDidReshape:(NSView*)view {
557  [_engine updateWindowMetrics];
558 }
559 
560 #pragma mark - FlutterPluginRegistry
561 
562 - (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
563  return [_engine registrarForPlugin:pluginName];
564 }
565 
566 #pragma mark - NSResponder
567 
568 - (BOOL)acceptsFirstResponder {
569  return YES;
570 }
571 
572 - (void)keyDown:(NSEvent*)event {
573  [self dispatchKeyEvent:event ofType:@"keydown"];
574  for (NSResponder* responder in self.additionalKeyResponders) {
575  if ([responder respondsToSelector:@selector(keyDown:)]) {
576  [responder keyDown:event];
577  }
578  }
579 }
580 
581 - (void)keyUp:(NSEvent*)event {
582  [self dispatchKeyEvent:event ofType:@"keyup"];
583  for (NSResponder* responder in self.additionalKeyResponders) {
584  if ([responder respondsToSelector:@selector(keyUp:)]) {
585  [responder keyUp:event];
586  }
587  }
588 }
589 
590 - (void)flagsChanged:(NSEvent*)event {
591  if (event.modifierFlags < _keyboardState.previously_pressed_flags) {
592  [self keyUp:event];
593  } else {
594  [self keyDown:event];
595  }
596  _keyboardState.previously_pressed_flags = event.modifierFlags;
597 }
598 
599 - (void)mouseEntered:(NSEvent*)event {
600  [self dispatchMouseEvent:event phase:kAdd];
601 }
602 
603 - (void)mouseExited:(NSEvent*)event {
604  if (_mouseState.buttons != 0) {
605  _mouseState.has_pending_exit = true;
606  return;
607  }
608  [self dispatchMouseEvent:event phase:kRemove];
609 }
610 
611 - (void)mouseDown:(NSEvent*)event {
612  _mouseState.buttons |= kFlutterPointerButtonMousePrimary;
613  [self dispatchMouseEvent:event];
614 }
615 
616 - (void)mouseUp:(NSEvent*)event {
617  _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMousePrimary);
618  [self dispatchMouseEvent:event];
619 }
620 
621 - (void)mouseDragged:(NSEvent*)event {
622  [self dispatchMouseEvent:event];
623 }
624 
625 - (void)rightMouseDown:(NSEvent*)event {
626  _mouseState.buttons |= kFlutterPointerButtonMouseSecondary;
627  [self dispatchMouseEvent:event];
628 }
629 
630 - (void)rightMouseUp:(NSEvent*)event {
631  _mouseState.buttons &= ~static_cast<uint64_t>(kFlutterPointerButtonMouseSecondary);
632  [self dispatchMouseEvent:event];
633 }
634 
635 - (void)rightMouseDragged:(NSEvent*)event {
636  [self dispatchMouseEvent:event];
637 }
638 
639 - (void)otherMouseDown:(NSEvent*)event {
640  _mouseState.buttons |= (1 << event.buttonNumber);
641  [self dispatchMouseEvent:event];
642 }
643 
644 - (void)otherMouseUp:(NSEvent*)event {
645  _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);
646  [self dispatchMouseEvent:event];
647 }
648 
649 - (void)otherMouseDragged:(NSEvent*)event {
650  [self dispatchMouseEvent:event];
651 }
652 
653 - (void)mouseMoved:(NSEvent*)event {
654  [self dispatchMouseEvent:event];
655 }
656 
657 - (void)scrollWheel:(NSEvent*)event {
658  // TODO: Add gesture-based (trackpad) scroll support once it's supported by the engine rather
659  // than always using kHover.
660  [self dispatchMouseEvent:event phase:kHover];
661 }
662 
663 @end
Definition: embedder.h:479
double scroll_delta_y
The y offset of the scroll in physical pixels.
Definition: embedder.h:549
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name, [binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger, [codec] NSObject< FlutterMethodCodec > *codec)
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name, [binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger, [codec] NSObject< FlutterMessageCodec > *codec)
FlutterBasicMessageChannel * _settingsChannel
FlutterPointerPhase
The phase of the pointer event.
Definition: embedder.h:471
double scroll_delta_x
The x offset of the scroll in physical pixels.
Definition: embedder.h:547
instancetype sharedInstance()
fml::scoped_nsobject< FlutterEngine > _engine
FlutterDartProject * _project
FlutterMethodChannel * _platformChannel
size_t struct_size
The size of this struct. Must be sizeof(FlutterPointerEvent).
Definition: embedder.h:532
FlutterPointerPhase phase
Definition: fl_view.cc:78
The pointer moved while up.
Definition: embedder.h:503
void(^ FlutterResult)(id _Nullable result)
FLUTTER_EXPORT NSObject const * FlutterMethodNotImplemented
Definition: embedder.h:496
GdkEventButton * event
Definition: fl_view.cc:62
FlutterPointerSignalKind signal_kind
Definition: embedder.h:545
FlutterTextInputPlugin * _textInputPlugin
FlutterBasicMessageChannel * _keyEventChannel
static constexpr char kTextPlainFormat[]