Flutter macOS Embedder
FlutterTextInputSemanticsObject.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 
10 
11 #include "flutter/third_party/accessibility/ax/ax_action_data.h"
12 #include "flutter/third_party/accessibility/gfx/geometry/rect_conversions.h"
13 #include "flutter/third_party/accessibility/gfx/mac/coordinate_conversion.h"
14 
15 #pragma mark - FlutterTextFieldCell
16 /**
17  * A convenient class that can be used to set a custom field editor for an
18  * NSTextField.
19  *
20  * The FlutterTextField uses this class set the FlutterTextInputPlugin as
21  * its field editor.
22  */
23 @interface FlutterTextFieldCell : NSTextFieldCell
24 
25 /**
26  * Initializes the NSCell for the input NSTextField.
27  */
28 - (instancetype)initWithTextField:(NSTextField*)textField fieldEditor:(NSTextView*)editor;
29 
30 @end
31 
32 @implementation FlutterTextFieldCell {
33  NSTextView* _editor;
34 }
35 
36 #pragma mark - Private
37 
38 - (instancetype)initWithTextField:(NSTextField*)textField fieldEditor:(NSTextView*)editor {
39  self = [super initTextCell:textField.stringValue];
40  if (self) {
41  _editor = editor;
42  [self setControlView:textField];
43  // Read-only text fields are sent to the mac embedding as static
44  // text. This text field must be editable and selectable at this
45  // point.
46  self.editable = YES;
47  self.selectable = YES;
48  }
49  return self;
50 }
51 
52 #pragma mark - NSCell
53 
54 - (NSTextView*)fieldEditorForView:(NSView*)controlView {
55  return _editor;
56 }
57 
58 @end
59 
60 #pragma mark - FlutterTextField
61 
62 @implementation FlutterTextField {
65 }
66 
67 #pragma mark - Public
68 
69 - (instancetype)initWithPlatformNode:(flutter::FlutterTextPlatformNode*)node
70  fieldEditor:(FlutterTextInputPlugin*)plugin {
71  self = [super initWithFrame:NSZeroRect];
72  if (self) {
73  _node = node;
74  _plugin = plugin;
75  [self setCell:[[FlutterTextFieldCell alloc] initWithTextField:self fieldEditor:plugin]];
76  }
77  return self;
78 }
79 
80 - (void)updateString:(NSString*)string withSelection:(NSRange)selection {
81  NSAssert(_plugin.client == self,
82  @"Can't update FlutterTextField when it is not the first responder");
83  if (![[self stringValue] isEqualToString:string]) {
84  [self setStringValue:string];
85  }
86  if (!NSEqualRanges(_plugin.selectedRange, selection)) {
87  [_plugin setSelectedRange:selection];
88  }
89 }
90 
91 #pragma mark - NSView
92 
93 - (NSRect)frame {
94  if (!_node) {
95  return NSZeroRect;
96  }
97  return _node->GetFrame();
98 }
99 
100 #pragma mark - NSAccessibilityProtocol
101 
102 - (void)setAccessibilityFocused:(BOOL)isFocused {
103  if (!_node) {
104  return;
105  }
106  [super setAccessibilityFocused:isFocused];
107  ui::AXActionData data;
108  data.action = isFocused ? ax::mojom::Action::kFocus : ax::mojom::Action::kBlur;
109  _node->GetDelegate()->AccessibilityPerformAction(data);
110 }
111 
112 - (void)startEditing {
113  if (!_plugin) {
114  return;
115  }
116  if (self.currentEditor == _plugin) {
117  return;
118  }
119  if (!_node) {
120  return;
121  }
122  // Selecting text seems to be the only way to make the field editor
123  // current editor.
124  [self selectText:self];
125  NSAssert(self.currentEditor == _plugin, @"Failed to set current editor");
126 
127  _plugin.client = self;
128 
129  // Restore previous selection.
130  NSString* textValue = @(_node->GetStringAttribute(ax::mojom::StringAttribute::kValue).data());
131  int start = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart);
132  int end = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd);
133  NSAssert((start >= 0 && end >= 0) || (start == -1 && end == -1), @"selection is invalid");
134  NSRange selection;
135  if (start >= 0 && end >= 0) {
136  selection = NSMakeRange(MIN(start, end), ABS(end - start));
137  } else {
138  // The native behavior is to place the cursor at the end of the string if
139  // there is no selection.
140  selection = NSMakeRange([self stringValue].length, 0);
141  }
142  [self updateString:textValue withSelection:selection];
143 }
144 
145 - (void)setPlatformNode:(flutter::FlutterTextPlatformNode*)node {
146  _node = node;
147 }
148 
149 #pragma mark - NSObject
150 
151 - (void)dealloc {
152  if (_plugin.client == self) {
153  _plugin.client = nil;
154  }
155 }
156 
157 @end
158 
159 namespace flutter {
160 
162  __weak FlutterViewController* view_controller) {
163  Init(delegate);
164  view_controller_ = view_controller;
165  appkit_text_field_ =
166  [[FlutterTextField alloc] initWithPlatformNode:this
167  fieldEditor:view_controller.textInputPlugin];
168  appkit_text_field_.bezeled = NO;
169  appkit_text_field_.drawsBackground = NO;
170  appkit_text_field_.bordered = NO;
171  appkit_text_field_.focusRingType = NSFocusRingTypeNone;
172 }
173 
175  [appkit_text_field_ setPlatformNode:nil];
176  EnsureDetachedFromView();
177 }
178 
180  if (EnsureAttachedToView()) {
181  return appkit_text_field_;
182  }
183  return nil;
184 }
185 
187  if (!view_controller_.viewLoaded) {
188  return NSZeroRect;
189  }
190  FlutterPlatformNodeDelegate* delegate = static_cast<FlutterPlatformNodeDelegate*>(GetDelegate());
191  bool offscreen;
192  auto bridge_ptr = delegate->GetOwnerBridge().lock();
193  gfx::RectF bounds = bridge_ptr->RelativeToGlobalBounds(delegate->GetAXNode(), offscreen, true);
194 
195  // Converts to NSRect to use NSView rect conversion.
196  NSRect ns_local_bounds = NSMakeRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
197  // The macOS XY coordinates start at bottom-left and increase toward top-right,
198  // which is different from the Flutter's XY coordinates that start at top-left
199  // increasing to bottom-right. Flip the y coordinate to convert from Flutter
200  // coordinates to macOS coordinates.
201  ns_local_bounds.origin.y = -ns_local_bounds.origin.y - ns_local_bounds.size.height;
202  NSRect ns_view_bounds = [view_controller_.flutterView convertRectFromBacking:ns_local_bounds];
203  return [view_controller_.flutterView convertRect:ns_view_bounds toView:nil];
204 }
205 
206 bool FlutterTextPlatformNode::EnsureAttachedToView() {
207  if (!view_controller_.viewLoaded) {
208  return false;
209  }
210  if ([appkit_text_field_ isDescendantOf:view_controller_.view]) {
211  return true;
212  }
213  [view_controller_.view addSubview:appkit_text_field_
214  positioned:NSWindowBelow
215  relativeTo:view_controller_.flutterView];
216  return true;
217 }
218 
219 void FlutterTextPlatformNode::EnsureDetachedFromView() {
220  [appkit_text_field_ removeFromSuperview];
221 }
222 
223 } // namespace flutter
FlutterViewController
Definition: FlutterViewController.h:65
flutter::FlutterTextPlatformNode::FlutterTextPlatformNode
FlutterTextPlatformNode(FlutterPlatformNodeDelegate *delegate, __weak FlutterViewController *view_controller)
Creates a FlutterTextPlatformNode that uses a FlutterTextField as its NativeViewAccessible.
Definition: FlutterTextInputSemanticsObject.mm:161
flutter::FlutterTextPlatformNode
The ax platform node for a text field.
Definition: FlutterTextInputSemanticsObject.h:22
flutter::FlutterPlatformNodeDelegate::GetOwnerBridge
std::weak_ptr< OwnerBridge > GetOwnerBridge() const
Gets the owner of this platform node delegate. This is useful when you want to get the information ab...
Definition: flutter_platform_node_delegate.cc:115
FlutterTextInputPlugin.h
FlutterEngine_Internal.h
flutter::FlutterTextPlatformNode::GetNativeViewAccessible
gfx::NativeViewAccessible GetNativeViewAccessible() override
Definition: FlutterTextInputSemanticsObject.mm:179
-[FlutterTextField updateString:withSelection:]
void updateString:withSelection:(NSString *string,[withSelection] NSRange selection)
Definition: FlutterTextInputSemanticsObject.mm:80
FlutterTextInputPlugin::client
FlutterTextField * client
Definition: FlutterTextInputPlugin.h:34
flutter
Definition: AccessibilityBridgeMac.h:16
FlutterTextInputPlugin
Definition: FlutterTextInputPlugin.h:27
flutter::FlutterPlatformNodeDelegate::GetAXNode
ui::AXNode * GetAXNode() const
Gets the underlying ax node for this platform node delegate.
Definition: flutter_platform_node_delegate.cc:26
flutter::FlutterTextPlatformNode::~FlutterTextPlatformNode
~FlutterTextPlatformNode() override
Definition: FlutterTextInputSemanticsObject.mm:174
FlutterViewController_Internal.h
FlutterTextInputSemanticsObject.h
flutter::FlutterPlatformNodeDelegate
Definition: flutter_platform_node_delegate.h:33
_plugin
FlutterTextInputPlugin * _plugin
Definition: FlutterTextInputSemanticsObject.mm:62
FlutterTextField
Definition: FlutterTextInputSemanticsObject.h:81
flutter::FlutterTextPlatformNode::GetFrame
NSRect GetFrame()
Gets the frame of this platform node relative to the view of FlutterViewController....
Definition: FlutterTextInputSemanticsObject.mm:186
FlutterTextFieldCell
Definition: FlutterTextInputSemanticsObject.mm:23