Flutter Engine
The Flutter Engine
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 {
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];
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
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
159namespace 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
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 }
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
206bool 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
219void FlutterTextPlatformNode::EnsureDetachedFromView() {
220 [appkit_text_field_ removeFromSuperview];
221}
222
223} // namespace flutter
FlutterTextInputPlugin * _plugin
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...
ui::AXNode * GetAXNode() const
Gets the underlying ax node for this platform node delegate.
The ax platform node for a text field.
FlutterTextPlatformNode(FlutterPlatformNodeDelegate *delegate, __weak FlutterViewController *view_controller)
Creates a FlutterTextPlatformNode that uses a FlutterTextField as its NativeViewAccessible.
NSRect GetFrame()
Gets the frame of this platform node relative to the view of FlutterViewController....
gfx::NativeViewAccessible GetNativeViewAccessible() override
AXPlatformNodeDelegate * GetDelegate() const override
virtual void Init(AXPlatformNodeDelegate *delegate)
double frame
Definition: examples.cpp:31
glong glong end
void setPlatformNode:(flutter::FlutterTextPlatformNode *node)
size_t length
Optional< SkRect > bounds
Definition: SkRecords.h:189
UnimplementedNativeViewAccessible * NativeViewAccessible
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
int BOOL
Definition: windows_types.h:37