| 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
5 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
6
7 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
8 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
9 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
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 {
63 flutter::FlutterTextPlatformNode* _node;
64 FlutterTextInputPlugin* _plugin;
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
161 FlutterTextPlatformNode::FlutterTextPlatformNode(FlutterPlatformNodeDelegate* delegate,
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
174 FlutterTextPlatformNode::~FlutterTextPlatformNode() {
175 [appkit_text_field_ setPlatformNode:nil];
176 EnsureDetachedFromView();
177 }
178
179 gfx::NativeViewAccessible FlutterTextPlatformNode::GetNativeViewAccessible() {
180 if (EnsureAttachedToView()) {
181 return appkit_text_field_;
182 }
183 return nil;
184 }
185
186 NSRect FlutterTextPlatformNode::GetFrame() {
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 if (!bridge_ptr) {
194 return NSZeroRect;
195 }
196 gfx::RectF bounds = bridge_ptr->RelativeToGlobalBounds(delegate->GetAXNode(), offscreen, true);
197
198 // Converts to NSRect to use NSView rect conversion.
199 NSRect ns_local_bounds = NSMakeRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
200 // The macOS XY coordinates start at bottom-left and increase toward top-right,
201 // which is different from the Flutter's XY coordinates that start at top-left
202 // increasing to bottom-right. Flip the y coordinate to convert from Flutter
203 // coordinates to macOS coordinates.
204 ns_local_bounds.origin.y = -ns_local_bounds.origin.y - ns_local_bounds.size.height;
205 NSRect ns_view_bounds = [view_controller_.flutterView convertRectFromBacking:ns_local_bounds];
206 return [view_controller_.flutterView convertRect:ns_view_bounds toView:nil];
207 }
208
209 bool FlutterTextPlatformNode::EnsureAttachedToView() {
210 if (!view_controller_.viewLoaded) {
211 return false;
212 }
213 if ([appkit_text_field_ isDescendantOf:view_controller_.view]) {
214 return true;
215 }
216 [view_controller_.view addSubview:appkit_text_field_
217 positioned:NSWindowBelow
218 relativeTo:view_controller_.flutterView];
219 return true;
220 }
221
222 void FlutterTextPlatformNode::EnsureDetachedFromView() {
223 [appkit_text_field_ removeFromSuperview];
224 }
225
226 } // namespace flutter
FlutterViewController
Definition: FlutterViewController.h:73
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