Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1ae721c

Browse files
authored
Support Scribble Handwriting (#24224)
1 parent 24788dd commit 1ae721c

11 files changed

+1201
-183
lines changed

AUTHORS

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ Hidenori Matsubayashi <[email protected]>
2020
Sarbagya Dhaubanjar <[email protected]>
2121
Callum Moffat <[email protected]>
2222
Koutaro Mori <[email protected]>
23-
TheOneWithTheBraid <[email protected]>
23+
TheOneWithTheBraid <[email protected]>
24+
Twin Sun, LLC <[email protected]>

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_
10931093
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h
10941094
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.mm
10951095
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterHeadlessDartRunner.mm
1096+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h
10961097
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyPrimaryResponder.h
10971098
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h
10981099
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h
@@ -1132,6 +1133,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm
11321133
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
11331134
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm
11341135
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h
1136+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h
11351137
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm
11361138
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h
11371139
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap.mm

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#import "flutter/shell/platform/darwin/common/command_line.h"
2121
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h"
2222
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
23+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h"
2324
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterObservatoryPublisher.h"
2425
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
2526
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
@@ -42,7 +43,9 @@ @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
4243
- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine;
4344
@end
4445

45-
@interface FlutterEngine () <FlutterTextInputDelegate, FlutterBinaryMessenger>
46+
@interface FlutterEngine () <FlutterIndirectScribbleDelegate,
47+
FlutterTextInputDelegate,
48+
FlutterBinaryMessenger>
4649
// Maintains a dictionary of plugin names that have registered with the engine. Used by
4750
// FlutterEngineRegistrar to implement a FlutterPluginRegistrar.
4851
@property(nonatomic, readonly) NSMutableDictionary* pluginPublications;
@@ -331,6 +334,7 @@ - (void)setViewController:(FlutterViewController*)viewController {
331334

332335
- (void)attachView {
333336
self.iosPlatformView->attachView();
337+
[_textInputPlugin.get() setupIndirectScribbleInteraction:self.viewController];
334338
}
335339

336340
- (void)setFlutterViewControllerWillDeallocObserver:(id<NSObject>)observer {
@@ -355,6 +359,7 @@ - (void)notifyViewControllerDeallocated {
355359
platform_view->SetOwnerViewController({});
356360
}
357361
}
362+
[_textInputPlugin.get() resetViewResponder];
358363
_viewController.reset();
359364
}
360365

@@ -514,6 +519,8 @@ - (void)setupChannels {
514519

515520
_textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
516521
_textInputPlugin.get().textInputDelegate = self;
522+
_textInputPlugin.get().indirectScribbleDelegate = self;
523+
[_textInputPlugin.get() setupIndirectScribbleInteraction:self.viewController];
517524

518525
_platformPlugin.reset([[FlutterPlatformPlugin alloc] initWithEngine:[self getWeakPtr]]);
519526

@@ -720,22 +727,30 @@ - (void)notifyLowMemory {
720727

721728
#pragma mark - Text input delegate
722729

723-
- (void)updateEditingClient:(int)client withState:(NSDictionary*)state {
730+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
731+
updateEditingClient:(int)client
732+
withState:(NSDictionary*)state {
724733
[_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingState"
725734
arguments:@[ @(client), state ]];
726735
}
727736

728-
- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag {
737+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
738+
updateEditingClient:(int)client
739+
withState:(NSDictionary*)state
740+
withTag:(NSString*)tag {
729741
[_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag"
730742
arguments:@[ @(client), @{tag : state} ]];
731743
}
732744

733-
- (void)updateEditingClient:(int)client withDelta:(NSDictionary*)delta {
745+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
746+
updateEditingClient:(int)client
747+
withDelta:(NSDictionary*)delta {
734748
[_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithDeltas"
735749
arguments:@[ @(client), delta ]];
736750
}
737751

738-
- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
752+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
753+
updateFloatingCursor:(FlutterFloatingCursorDragState)state
739754
withClient:(int)client
740755
withPosition:(NSDictionary*)position {
741756
NSString* stateString;
@@ -754,7 +769,9 @@ - (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
754769
arguments:@[ @(client), stateString, position ]];
755770
}
756771

757-
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
772+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
773+
performAction:(FlutterTextInputAction)action
774+
withClient:(int)client {
758775
NSString* actionString;
759776
switch (action) {
760777
case FlutterTextInputActionUnspecified:
@@ -799,15 +816,63 @@ - (void)performAction:(FlutterTextInputAction)action withClient:(int)client {
799816
arguments:@[ @(client), actionString ]];
800817
}
801818

802-
- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start
803-
end:(NSUInteger)end
804-
withClient:(int)client {
819+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
820+
showAutocorrectionPromptRectForStart:(NSUInteger)start
821+
end:(NSUInteger)end
822+
withClient:(int)client {
805823
[_textInputChannel.get() invokeMethod:@"TextInputClient.showAutocorrectionPromptRect"
806824
arguments:@[ @(client), @(start), @(end) ]];
807825
}
808826

809827
#pragma mark - FlutterViewEngineDelegate
810828

829+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client {
830+
[_textInputChannel.get() invokeMethod:@"TextInputClient.showToolbar" arguments:@[ @(client) ]];
831+
}
832+
833+
- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
834+
focusElement:(UIScribbleElementIdentifier)elementIdentifier
835+
atPoint:(CGPoint)referencePoint
836+
result:(FlutterResult)callback {
837+
[_textInputChannel.get()
838+
invokeMethod:@"TextInputClient.focusElement"
839+
arguments:@[ elementIdentifier, @(referencePoint.x), @(referencePoint.y) ]
840+
result:callback];
841+
}
842+
843+
- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
844+
requestElementsInRect:(CGRect)rect
845+
result:(FlutterResult)callback {
846+
[_textInputChannel.get()
847+
invokeMethod:@"TextInputClient.requestElementsInRect"
848+
arguments:@[ @(rect.origin.x), @(rect.origin.y), @(rect.size.width), @(rect.size.height) ]
849+
result:callback];
850+
}
851+
852+
- (void)flutterTextInputViewScribbleInteractionBegan:(FlutterTextInputView*)textInputView {
853+
[_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionBegan" arguments:nil];
854+
}
855+
856+
- (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView {
857+
[_textInputChannel.get() invokeMethod:@"TextInputClient.scribbleInteractionFinished"
858+
arguments:nil];
859+
}
860+
861+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
862+
insertTextPlaceholderWithSize:(CGSize)size
863+
withClient:(int)client {
864+
[_textInputChannel.get() invokeMethod:@"TextInputClient.insertTextPlaceholder"
865+
arguments:@[ @(client), @(size.width), @(size.height) ]];
866+
}
867+
868+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
869+
removeTextPlaceholder:(int)client {
870+
[_textInputChannel.get() invokeMethod:@"TextInputClient.removeTextPlaceholder"
871+
arguments:@[ @(client) ]];
872+
}
873+
874+
#pragma mark - Screenshot Delegate
875+
811876
- (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
812877
asBase64Encoded:(BOOL)base64Encode {
813878
FML_DCHECK(_shell) << "Cannot takeScreenshot without a shell";

shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
2323
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
24+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h"
2425
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
2526
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
2627
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_
6+
#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_
7+
8+
#import <Foundation/Foundation.h>
9+
10+
NS_ASSUME_NONNULL_BEGIN
11+
@class FlutterTextInputPlugin;
12+
13+
@protocol FlutterIndirectScribbleDelegate <NSObject>
14+
- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
15+
focusElement:(UIScribbleElementIdentifier)elementIdentifier
16+
atPoint:(CGPoint)referencePoint
17+
result:(FlutterResult)callback;
18+
- (void)flutterTextInputPlugin:(FlutterTextInputPlugin*)textInputPlugin
19+
requestElementsInRect:(CGRect)rect
20+
result:(FlutterResult)callback;
21+
@end
22+
NS_ASSUME_NONNULL_END
23+
24+
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERINDIRECTSCRIBBLEDELEGATE_H_

shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
#import <Foundation/Foundation.h>
99

10+
@class FlutterTextInputPlugin;
11+
@class FlutterTextInputView;
12+
1013
typedef NS_ENUM(NSInteger, FlutterTextInputAction) {
1114
FlutterTextInputActionUnspecified,
1215
FlutterTextInputActionDone,
@@ -28,16 +31,35 @@ typedef NS_ENUM(NSInteger, FlutterFloatingCursorDragState) {
2831
};
2932

3033
@protocol FlutterTextInputDelegate <NSObject>
31-
- (void)updateEditingClient:(int)client withState:(NSDictionary*)state;
32-
- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag;
33-
- (void)updateEditingClient:(int)client withDelta:(NSDictionary*)state;
34-
- (void)performAction:(FlutterTextInputAction)action withClient:(int)client;
35-
- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
34+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
35+
updateEditingClient:(int)client
36+
withState:(NSDictionary*)state;
37+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
38+
updateEditingClient:(int)client
39+
withState:(NSDictionary*)state
40+
withTag:(NSString*)tag;
41+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
42+
updateEditingClient:(int)client
43+
withDelta:(NSDictionary*)state;
44+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
45+
performAction:(FlutterTextInputAction)action
46+
withClient:(int)client;
47+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
48+
updateFloatingCursor:(FlutterFloatingCursorDragState)state
3649
withClient:(int)client
3750
withPosition:(NSDictionary*)point;
38-
- (void)showAutocorrectionPromptRectForStart:(NSUInteger)start
39-
end:(NSUInteger)end
40-
withClient:(int)client;
51+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
52+
showAutocorrectionPromptRectForStart:(NSUInteger)start
53+
end:(NSUInteger)end
54+
withClient:(int)client;
55+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView showToolbar:(int)client;
56+
- (void)flutterTextInputViewScribbleInteractionBegan:(FlutterTextInputView*)textInputView;
57+
- (void)flutterTextInputViewScribbleInteractionFinished:(FlutterTextInputView*)textInputView;
58+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView
59+
insertTextPlaceholderWithSize:(CGSize)size
60+
withClient:(int)client;
61+
- (void)flutterTextInputView:(FlutterTextInputView*)textInputView removeTextPlaceholder:(int)client;
62+
4163
@end
4264

4365
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTDELEGATE_H_

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,32 @@
88
#import <UIKit/UIKit.h>
99

1010
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
11+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterIndirectScribbleDelegate.h"
1112
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeySecondaryResponder.h"
1213
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextEditingDelta.h"
1314
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
15+
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h"
1416

15-
@interface FlutterTextInputPlugin : NSObject <FlutterKeySecondaryResponder>
17+
typedef NS_ENUM(NSInteger, FlutterScribbleFocusStatus) {
18+
FlutterScribbleFocusStatusUnfocused,
19+
FlutterScribbleFocusStatusFocusing,
20+
FlutterScribbleFocusStatusFocused,
21+
};
22+
23+
typedef NS_ENUM(NSInteger, FlutterScribbleInteractionStatus) {
24+
FlutterScribbleInteractionStatusNone,
25+
FlutterScribbleInteractionStatusStarted,
26+
FlutterScribbleInteractionStatusEnding,
27+
};
28+
29+
@interface FlutterTextInputPlugin
30+
: NSObject <FlutterKeySecondaryResponder, UIIndirectScribbleInteractionDelegate>
1631

1732
@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
1833
@property(nonatomic, assign) UIViewController* viewController;
34+
@property(nonatomic, assign) id<FlutterIndirectScribbleDelegate> indirectScribbleDelegate;
35+
@property(nonatomic, strong)
36+
NSMutableDictionary<UIScribbleElementIdentifier, NSValue*>* scribbleElements;
1937
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
2038

2139
/**
@@ -26,6 +44,13 @@
2644
*/
2745
- (UIView<UITextInput>*)textInputView;
2846

47+
/**
48+
* These are used by the UIIndirectScribbleInteractionDelegate methods to handle focusing on the
49+
* correct element.
50+
*/
51+
- (void)setupIndirectScribbleInteraction:(id<FlutterViewResponder>)viewResponder;
52+
- (void)resetViewResponder;
53+
2954
@end
3055

3156
/** An indexed position in the buffer of a Flutter text editing widget. */
@@ -51,10 +76,41 @@
5176
@interface FlutterTokenizer : UITextInputStringTokenizer
5277
@end
5378

79+
@interface FlutterTextSelectionRect : UITextSelectionRect
80+
81+
@property(nonatomic, assign) CGRect rect;
82+
@property(nonatomic) NSUInteger position;
83+
@property(nonatomic, assign) NSWritingDirection writingDirection;
84+
@property(nonatomic) BOOL containsStart;
85+
@property(nonatomic) BOOL containsEnd;
86+
@property(nonatomic) BOOL isVertical;
87+
88+
+ (instancetype)selectionRectWithRectAndInfo:(CGRect)rect
89+
position:(NSUInteger)position
90+
writingDirection:(NSWritingDirection)writingDirection
91+
containsStart:(BOOL)containsStart
92+
containsEnd:(BOOL)containsEnd
93+
isVertical:(BOOL)isVertical;
94+
95+
+ (instancetype)selectionRectWithRect:(CGRect)rect position:(NSUInteger)position;
96+
97+
- (instancetype)initWithRectAndInfo:(CGRect)rect
98+
position:(NSUInteger)position
99+
writingDirection:(NSWritingDirection)writingDirection
100+
containsStart:(BOOL)containsStart
101+
containsEnd:(BOOL)containsEnd
102+
isVertical:(BOOL)isVertical;
103+
104+
- (instancetype)init NS_UNAVAILABLE;
105+
@end
106+
107+
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder : UITextPlaceholder
108+
@end
109+
54110
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
55111
FLUTTER_DARWIN_EXPORT
56112
#endif
57-
@interface FlutterTextInputView : UIView <UITextInput>
113+
@interface FlutterTextInputView : UIView <UITextInput, UIScribbleInteractionDelegate>
58114

59115
// UITextInput
60116
@property(nonatomic, readonly) NSMutableString* text;
@@ -81,5 +137,11 @@ FLUTTER_DARWIN_EXPORT
81137
@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
82138
@property(nonatomic, assign) UIAccessibilityElement* backingTextInputAccessibilityObject;
83139

140+
// Scribble Support
141+
@property(nonatomic, assign) id<FlutterViewResponder> viewResponder;
142+
@property(nonatomic) FlutterScribbleFocusStatus scribbleFocusStatus;
143+
@property(nonatomic, strong) NSArray<FlutterTextSelectionRect*>* selectionRects;
144+
- (void)resetScribbleInteractionStatusIfEnding;
145+
84146
@end
85147
#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_

0 commit comments

Comments
 (0)