Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
009fa69f
Unverified
Commit
009fa69f
authored
Nov 04, 2022
by
Casey Hillers
Committed by
GitHub
Nov 04, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Revert "Scribble mixin (#104128)" (#114647)
This reverts commit
b571abfb
.
parent
8a9ddade
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
462 additions
and
730 deletions
+462
-730
services.dart
packages/flutter/lib/services.dart
+0
-1
binding.dart
packages/flutter/lib/src/services/binding.dart
+3
-2
scribble.dart
packages/flutter/lib/src/services/scribble.dart
+0
-243
system_channels.dart
packages/flutter/lib/src/services/system_channels.dart
+0
-32
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+171
-4
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+59
-125
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+15
-0
binding_test.dart
packages/flutter/test/services/binding_test.dart
+7
-0
delta_text_input_test.dart
packages/flutter/test/services/delta_text_input_test.dart
+15
-0
scribble_test.dart
packages/flutter/test/services/scribble_test.dart
+0
-213
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+171
-3
text_input_utils.dart
packages/flutter/test/services/text_input_utils.dart
+1
-16
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+4
-75
test_text_input.dart
packages/flutter_test/lib/src/test_text_input.dart
+16
-16
No files found.
packages/flutter/lib/services.dart
View file @
009fa69f
...
...
@@ -37,7 +37,6 @@ export 'src/services/raw_keyboard_macos.dart';
export
'src/services/raw_keyboard_web.dart'
;
export
'src/services/raw_keyboard_windows.dart'
;
export
'src/services/restoration.dart'
;
export
'src/services/scribble.dart'
;
export
'src/services/service_extensions.dart'
;
export
'src/services/spell_check.dart'
;
export
'src/services/system_channels.dart'
;
...
...
packages/flutter/lib/src/services/binding.dart
View file @
009fa69f
...
...
@@ -15,9 +15,9 @@ import 'binary_messenger.dart';
import
'hardware_keyboard.dart'
;
import
'message_codec.dart'
;
import
'restoration.dart'
;
import
'scribble.dart'
;
import
'service_extensions.dart'
;
import
'system_channels.dart'
;
import
'text_input.dart'
;
export
'dart:ui'
show
ChannelBuffers
,
RootIsolateToken
;
...
...
@@ -43,7 +43,7 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
SystemChannels
.
system
.
setMessageHandler
((
dynamic
message
)
=>
handleSystemMessage
(
message
as
Object
));
SystemChannels
.
lifecycle
.
setMessageHandler
(
_handleLifecycleMessage
);
SystemChannels
.
platform
.
setMethodCallHandler
(
_handlePlatformMessage
);
Scribble
.
ensureInitialized
();
TextInput
.
ensureInitialized
();
readInitialLifecycleStateFromNativeWindow
();
}
...
...
@@ -326,6 +326,7 @@ mixin ServicesBinding on BindingBase, SchedulerBinding {
void
setSystemUiChangeCallback
(
SystemUiChangeCallback
?
callback
)
{
_systemUiChangeCallback
=
callback
;
}
}
/// Signature for listening to changes in the [SystemUiMode].
...
...
packages/flutter/lib/src/services/scribble.dart
deleted
100644 → 0
View file @
8a9ddade
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
;
import
'package:flutter/foundation.dart'
;
import
'message_codec.dart'
;
import
'platform_channel.dart'
;
import
'system_channels.dart'
;
/// An interface into system-level handwriting text input.
///
/// This is typically used by implemeting the methods in [ScribbleClient] in a
/// class, usually a [State], and setting an instance of it to [client]. The
/// relevant methods on [ScribbleClient] will be called in response to method
/// channel calls on [SystemChannels.scribble].
///
/// Currently, handwriting input is supported in the iOS embedder with the Apple
/// Pencil.
///
/// [EditableText] uses this class via [ScribbleClient] to automatically support
/// handwriting input when [EditableText.scribbleEnabled] is set to true.
///
/// See also:
///
/// * [SystemChannels.scribble], which is the [MethodChannel] used by this
/// class, and which has a list of the methods that this class handles.
class
Scribble
{
Scribble
.
_
()
{
_channel
.
setMethodCallHandler
(
_handleScribbleInvocation
);
}
/// Ensure that a [Scribble] instance has been set up so that the platform
/// can handle messages on the scribble method channel.
static
void
ensureInitialized
()
{
_instance
;
// ignore: unnecessary_statements
}
/// Set the [MethodChannel] used to communicate with the system's text input
/// control.
///
/// This is only meant for testing within the Flutter SDK. Changing this
/// will break the ability to do handwriting input. This has no effect if
/// asserts are disabled.
@visibleForTesting
static
void
setChannel
(
MethodChannel
newChannel
)
{
assert
(()
{
_instance
.
_channel
=
newChannel
..
setMethodCallHandler
(
_instance
.
_handleScribbleInvocation
);
return
true
;
}());
}
static
final
Scribble
_instance
=
Scribble
.
_
();
/// Set the given [ScribbleClient] as the single active client.
///
/// This is usually based on the [ScribbleClient] receiving focus.
static
set
client
(
ScribbleClient
?
client
)
{
_instance
.
_client
=
client
;
}
/// Return the current active [ScribbleClient], or null if none.
static
ScribbleClient
?
get
client
=>
_instance
.
_client
;
ScribbleClient
?
_client
;
MethodChannel
_channel
=
SystemChannels
.
scribble
;
final
Map
<
String
,
ScribbleClient
>
_scribbleClients
=
<
String
,
ScribbleClient
>{};
bool
_scribbleInProgress
=
false
;
/// Used for testing within the Flutter SDK to get the currently registered [ScribbleClient] list.
@visibleForTesting
static
Map
<
String
,
ScribbleClient
>
get
scribbleClients
=>
Scribble
.
_instance
.
_scribbleClients
;
/// Returns true if a scribble interaction is currently happening.
static
bool
get
scribbleInProgress
=>
_instance
.
_scribbleInProgress
;
Future
<
dynamic
>
_handleScribbleInvocation
(
MethodCall
methodCall
)
async
{
final
String
method
=
methodCall
.
method
;
if
(
method
==
'Scribble.focusElement'
)
{
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
_scribbleClients
[
args
[
0
]]?.
onScribbleFocus
(
Offset
((
args
[
1
]
as
num
).
toDouble
(),
(
args
[
2
]
as
num
).
toDouble
()));
return
;
}
else
if
(
method
==
'Scribble.requestElementsInRect'
)
{
final
List
<
double
>
args
=
(
methodCall
.
arguments
as
List
<
dynamic
>).
cast
<
num
>().
map
<
double
>((
num
value
)
=>
value
.
toDouble
()).
toList
();
return
_scribbleClients
.
keys
.
where
((
String
elementIdentifier
)
{
final
Rect
rect
=
Rect
.
fromLTWH
(
args
[
0
],
args
[
1
],
args
[
2
],
args
[
3
]);
if
(!(
_scribbleClients
[
elementIdentifier
]?.
isInScribbleRect
(
rect
)
??
false
))
{
return
false
;
}
final
Rect
bounds
=
_scribbleClients
[
elementIdentifier
]?.
bounds
??
Rect
.
zero
;
return
!(
bounds
==
Rect
.
zero
||
bounds
.
hasNaN
||
bounds
.
isInfinite
);
}).
map
((
String
elementIdentifier
)
{
final
Rect
bounds
=
_scribbleClients
[
elementIdentifier
]!.
bounds
;
return
<
dynamic
>[
elementIdentifier
,
...<
dynamic
>[
bounds
.
left
,
bounds
.
top
,
bounds
.
width
,
bounds
.
height
]];
}).
toList
();
}
else
if
(
method
==
'Scribble.scribbleInteractionBegan'
)
{
_scribbleInProgress
=
true
;
return
;
}
else
if
(
method
==
'Scribble.scribbleInteractionFinished'
)
{
_scribbleInProgress
=
false
;
return
;
}
// The methods below are only valid when a client exists, i.e. when a field
// is focused.
final
ScribbleClient
?
client
=
_client
;
if
(
client
==
null
)
{
return
;
}
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
switch
(
method
)
{
case
'Scribble.showToolbar'
:
client
.
showToolbar
();
break
;
case
'Scribble.insertTextPlaceholder'
:
client
.
insertTextPlaceholder
(
Size
((
args
[
1
]
as
num
).
toDouble
(),
(
args
[
2
]
as
num
).
toDouble
()));
break
;
case
'Scribble.removeTextPlaceholder'
:
client
.
removeTextPlaceholder
();
break
;
default
:
throw
MissingPluginException
();
}
}
/// Registers a [ScribbleClient] with [elementIdentifier] that can be focused
/// by the engine.
///
/// For example, the registered [ScribbleClient] list is used to respond to
/// UIIndirectScribbleInteraction on an iPad.
static
void
registerScribbleElement
(
String
elementIdentifier
,
ScribbleClient
scribbleClient
)
{
_instance
.
_scribbleClients
[
elementIdentifier
]
=
scribbleClient
;
}
/// Unregisters a [ScribbleClient] with [elementIdentifier].
static
void
unregisterScribbleElement
(
String
elementIdentifier
)
{
_instance
.
_scribbleClients
.
remove
(
elementIdentifier
);
}
List
<
SelectionRect
>
_cachedSelectionRects
=
<
SelectionRect
>[];
/// Send the bounding boxes of the current selected glyphs in the client to
/// the platform's text input plugin.
///
/// These are used by the engine during a UIDirectScribbleInteraction.
static
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
if
(!
listEquals
(
_instance
.
_cachedSelectionRects
,
selectionRects
))
{
_instance
.
_cachedSelectionRects
=
selectionRects
;
_instance
.
_channel
.
invokeMethod
<
void
>(
'Scribble.setSelectionRects'
,
selectionRects
.
map
((
SelectionRect
rect
)
{
return
<
num
>[
rect
.
bounds
.
left
,
rect
.
bounds
.
top
,
rect
.
bounds
.
width
,
rect
.
bounds
.
height
,
rect
.
position
];
}).
toList
(),
);
}
}
}
/// An interface to interact with the engine for handwriting text input.
///
/// This is currently only used to handle
/// [UIIndirectScribbleInteraction](https://developer.apple.com/documentation/uikit/uiindirectscribbleinteraction),
/// which is responsible for manually receiving handwritten text input in UIKit.
/// The Flutter engine uses this to receive handwriting input on Flutter text
/// input fields.
mixin
ScribbleClient
{
/// A unique identifier for this element.
String
get
elementIdentifier
;
/// Called by the engine when the [ScribbleClient] should receive focus.
///
/// For example, this method is called during a UIIndirectScribbleInteraction.
///
/// The [Offset] indicates the location where the focus event happened, which
/// is typically where the cursor should be placed.
void
onScribbleFocus
(
Offset
offset
);
/// Tests whether the [ScribbleClient] overlaps the given rectangle bounds,
/// where the rectangle bounds are in global coordinates.
bool
isInScribbleRect
(
Rect
rect
);
/// The current bounds of the [ScribbleClient].
Rect
get
bounds
;
/// Requests that the client show the editing toolbar.
///
/// This is used when the platform changes the selection during scribble
/// input.
void
showToolbar
();
/// Requests that the client add a text placeholder to reserve visual space
/// in the text.
///
/// For example, this is called when responding to UIKit requesting
/// a text placeholder be added at the current selection, such as when
/// requesting additional writing space with iPadOS14 Scribble.
void
insertTextPlaceholder
(
Size
size
);
/// Requests that the client remove the text placeholder.
void
removeTextPlaceholder
();
}
/// Represents a selection rect for a character and it's position in the text.
///
/// This is used to report the current text selection rect and position data
/// to the engine for Scribble support on iPadOS 14.
@immutable
class
SelectionRect
{
/// Constructor for creating a [SelectionRect] from a text [position] and
/// [bounds].
const
SelectionRect
({
required
this
.
position
,
required
this
.
bounds
});
/// The position of this selection rect within the text String.
final
int
position
;
/// The rectangle representing the bounds of this selection rect within the
/// currently focused [RenderEditable]'s coordinate space.
final
Rect
bounds
;
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
{
return
true
;
}
if
(
runtimeType
!=
other
.
runtimeType
)
{
return
false
;
}
return
other
is
SelectionRect
&&
other
.
position
==
position
&&
other
.
bounds
==
bounds
;
}
@override
int
get
hashCode
=>
Object
.
hash
(
position
,
bounds
);
@override
String
toString
()
=>
'SelectionRect(
$position
,
$bounds
)'
;
}
packages/flutter/lib/src/services/system_channels.dart
View file @
009fa69f
...
...
@@ -222,38 +222,6 @@ class SystemChannels {
JSONMethodCodec
(),
);
/// A JSON [MethodChannel] for handling handwriting input.
///
/// This method channel is used by iPadOS 14's Scribble feature where writing
/// with an Apple Pencil on top of a text field inserts text into the field.
///
/// The following methods are defined for this channel:
///
/// * `Scribble.focusElement`: Indicates that focus is requested at the given
/// [Offset].
///
/// * `Scribble.requestElementsInRect`: Returns a List of identifiers and
/// bounds for the [ScribbleClient]s that lie within the given Rect.
///
/// * `Scribble.scribbleInteractionBegan`: Indicates that handwriting input
/// has started.
///
/// * `Scribble.scribbleInteractionFinished`: Indicates that handwriting input
/// has ended.
///
/// * `Scribble.showToolbar`: Requests that the toolbar be shown, such as
/// when selection is changed by handwriting.
///
/// * `Scribble.insertTextPlaceholder`: Requests that visual writing space is
/// reserved.
///
/// * `Scribble.removeTextPlaceholder`: Requests that any placeholder writing
/// space is removed.
static
const
MethodChannel
scribble
=
OptionalMethodChannel
(
'flutter/scribble'
,
JSONMethodCodec
(),
);
/// A [MethodChannel] for handling spell check for text input.
///
/// This channel exposes the spell check framework for supported platforms.
...
...
packages/flutter/lib/src/services/text_input.dart
View file @
009fa69f
...
...
@@ -1162,12 +1162,84 @@ mixin TextInputClient {
/// * [TextInputControl.show], a method to show the new input control.
void
didChangeInputControl
(
TextInputControl
?
oldControl
,
TextInputControl
?
newControl
)
{}
/// Requests that the client show the editing toolbar, for example when the
/// platform changes the selection through a non-flutter method such as
/// scribble.
void
showToolbar
()
{}
/// Requests that the client add a text placeholder to reserve visual space
/// in the text.
///
/// For example, this is called when responding to UIKit requesting
/// a text placeholder be added at the current selection, such as when
/// requesting additional writing space with iPadOS14 Scribble.
void
insertTextPlaceholder
(
Size
size
)
{}
/// Requests that the client remove the text placeholder.
void
removeTextPlaceholder
()
{}
/// Performs the specified MacOS-specific selector from the
/// `NSStandardKeyBindingResponding` protocol or user-specified selector
/// from `DefaultKeyBinding.Dict`.
void
performSelector
(
String
selectorName
)
{}
}
/// An interface to receive focus from the engine.
///
/// This is currently only used to handle UIIndirectScribbleInteraction.
abstract
class
ScribbleClient
{
/// A unique identifier for this element.
String
get
elementIdentifier
;
/// Called by the engine when the [ScribbleClient] should receive focus.
///
/// For example, this method is called during a UIIndirectScribbleInteraction.
void
onScribbleFocus
(
Offset
offset
);
/// Tests whether the [ScribbleClient] overlaps the given rectangle bounds.
bool
isInScribbleRect
(
Rect
rect
);
/// The current bounds of the [ScribbleClient].
Rect
get
bounds
;
}
/// Represents a selection rect for a character and it's position in the text.
///
/// This is used to report the current text selection rect and position data
/// to the engine for Scribble support on iPadOS 14.
@immutable
class
SelectionRect
{
/// Constructor for creating a [SelectionRect] from a text [position] and
/// [bounds].
const
SelectionRect
({
required
this
.
position
,
required
this
.
bounds
});
/// The position of this selection rect within the text String.
final
int
position
;
/// The rectangle representing the bounds of this selection rect within the
/// currently focused [RenderEditable]'s coordinate space.
final
Rect
bounds
;
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
{
return
true
;
}
if
(
runtimeType
!=
other
.
runtimeType
)
{
return
false
;
}
return
other
is
SelectionRect
&&
other
.
position
==
position
&&
other
.
bounds
==
bounds
;
}
@override
int
get
hashCode
=>
Object
.
hash
(
position
,
bounds
);
@override
String
toString
()
=>
'SelectionRect(
$position
,
$bounds
)'
;
}
/// An interface to receive granular information from [TextInput].
///
/// See also:
...
...
@@ -1227,6 +1299,7 @@ class TextInputConnection {
Matrix4
?
_cachedTransform
;
Rect
?
_cachedRect
;
Rect
?
_cachedCaretRect
;
List
<
SelectionRect
>
_cachedSelectionRects
=
<
SelectionRect
>[];
static
int
_nextId
=
1
;
final
int
_id
;
...
...
@@ -1249,6 +1322,12 @@ class TextInputConnection {
/// Whether this connection is currently interacting with the text input control.
bool
get
attached
=>
TextInput
.
_instance
.
_currentConnection
==
this
;
/// Whether there is currently a Scribble interaction in progress.
///
/// This is used to make sure selection handles are shown when UIKit changes
/// the selection during a Scribble interaction.
bool
get
scribbleInProgress
=>
TextInput
.
_instance
.
scribbleInProgress
;
/// Requests that the text input control become visible.
void
show
()
{
assert
(
attached
);
...
...
@@ -1329,6 +1408,17 @@ class TextInputConnection {
TextInput
.
_instance
.
_setCaretRect
(
validRect
);
}
/// Send the bounding boxes of the current selected glyphs in the client to
/// the platform's text input plugin.
///
/// These are used by the engine during a UIDirectScribbleInteraction.
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
if
(!
listEquals
(
_cachedSelectionRects
,
selectionRects
))
{
_cachedSelectionRects
=
selectionRects
;
TextInput
.
_instance
.
_setSelectionRects
(
selectionRects
);
}
}
/// Send text styling information.
///
/// This information is used by the Flutter Web Engine to change the style
...
...
@@ -1586,10 +1676,6 @@ class TextInput {
/// Ensure that a [TextInput] instance has been set up so that the platform
/// can handle messages on the text input method channel.
@Deprecated
(
'Use Scribble.ensureInitialized instead. '
'This feature was deprecated after v3.1.0-9.0.pre.'
)
static
void
ensureInitialized
()
{
_instance
;
// ignore: unnecessary_statements
}
...
...
@@ -1652,6 +1738,16 @@ class TextInput {
TextInputConnection
?
_currentConnection
;
late
TextInputConfiguration
_currentConfiguration
;
final
Map
<
String
,
ScribbleClient
>
_scribbleClients
=
<
String
,
ScribbleClient
>{};
bool
_scribbleInProgress
=
false
;
/// Used for testing within the Flutter SDK to get the currently registered [ScribbleClient] list.
@visibleForTesting
static
Map
<
String
,
ScribbleClient
>
get
scribbleClients
=>
TextInput
.
_instance
.
_scribbleClients
;
/// Returns true if a scribble interaction is currently happening.
bool
get
scribbleInProgress
=>
_scribbleInProgress
;
Future
<
dynamic
>
_loudlyHandleTextInputInvocation
(
MethodCall
call
)
async
{
try
{
return
await
_handleTextInputInvocation
(
call
);
...
...
@@ -1668,8 +1764,33 @@ class TextInput {
rethrow
;
}
}
Future
<
dynamic
>
_handleTextInputInvocation
(
MethodCall
methodCall
)
async
{
final
String
method
=
methodCall
.
method
;
if
(
method
==
'TextInputClient.focusElement'
)
{
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
_scribbleClients
[
args
[
0
]]?.
onScribbleFocus
(
Offset
((
args
[
1
]
as
num
).
toDouble
(),
(
args
[
2
]
as
num
).
toDouble
()));
return
;
}
else
if
(
method
==
'TextInputClient.requestElementsInRect'
)
{
final
List
<
double
>
args
=
(
methodCall
.
arguments
as
List
<
dynamic
>).
cast
<
num
>().
map
<
double
>((
num
value
)
=>
value
.
toDouble
()).
toList
();
return
_scribbleClients
.
keys
.
where
((
String
elementIdentifier
)
{
final
Rect
rect
=
Rect
.
fromLTWH
(
args
[
0
],
args
[
1
],
args
[
2
],
args
[
3
]);
if
(!(
_scribbleClients
[
elementIdentifier
]?.
isInScribbleRect
(
rect
)
??
false
))
{
return
false
;
}
final
Rect
bounds
=
_scribbleClients
[
elementIdentifier
]?.
bounds
??
Rect
.
zero
;
return
!(
bounds
==
Rect
.
zero
||
bounds
.
hasNaN
||
bounds
.
isInfinite
);
}).
map
((
String
elementIdentifier
)
{
final
Rect
bounds
=
_scribbleClients
[
elementIdentifier
]!.
bounds
;
return
<
dynamic
>[
elementIdentifier
,
...<
dynamic
>[
bounds
.
left
,
bounds
.
top
,
bounds
.
width
,
bounds
.
height
]];
}).
toList
();
}
else
if
(
method
==
'TextInputClient.scribbleInteractionBegan'
)
{
_scribbleInProgress
=
true
;
return
;
}
else
if
(
method
==
'TextInputClient.scribbleInteractionFinished'
)
{
_scribbleInProgress
=
false
;
return
;
}
if
(
_currentConnection
==
null
)
{
return
;
}
...
...
@@ -1773,6 +1894,15 @@ class TextInput {
case
'TextInputClient.showAutocorrectionPromptRect'
:
_currentConnection
!.
_client
.
showAutocorrectionPromptRect
(
args
[
1
]
as
int
,
args
[
2
]
as
int
);
break
;
case
'TextInputClient.showToolbar'
:
_currentConnection
!.
_client
.
showToolbar
();
break
;
case
'TextInputClient.insertTextPlaceholder'
:
_currentConnection
!.
_client
.
insertTextPlaceholder
(
Size
((
args
[
1
]
as
num
).
toDouble
(),
(
args
[
2
]
as
num
).
toDouble
()));
break
;
case
'TextInputClient.removeTextPlaceholder'
:
_currentConnection
!.
_client
.
removeTextPlaceholder
();
break
;
default
:
throw
MissingPluginException
();
}
...
...
@@ -1856,6 +1986,12 @@ class TextInput {
}
}
void
_setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
for
(
final
TextInputControl
control
in
_inputControls
)
{
control
.
setSelectionRects
(
selectionRects
);
}
}
void
_setStyle
({
required
String
?
fontFamily
,
required
double
?
fontSize
,
...
...
@@ -1955,6 +2091,20 @@ class TextInput {
control
.
finishAutofillContext
(
shouldSave:
shouldSave
);
}
}
/// Registers a [ScribbleClient] with [elementIdentifier] that can be focused
/// by the engine.
///
/// For example, the registered [ScribbleClient] list is used to respond to
/// UIIndirectScribbleInteraction on an iPad.
static
void
registerScribbleElement
(
String
elementIdentifier
,
ScribbleClient
scribbleClient
)
{
TextInput
.
_instance
.
_scribbleClients
[
elementIdentifier
]
=
scribbleClient
;
}
/// Unregisters a [ScribbleClient] with [elementIdentifier].
static
void
unregisterScribbleElement
(
String
elementIdentifier
)
{
TextInput
.
_instance
.
_scribbleClients
.
remove
(
elementIdentifier
);
}
}
/// An interface for implementing text input controls that receive text editing
...
...
@@ -2038,6 +2188,12 @@ mixin TextInputControl {
/// changes.
void
setCaretRect
(
Rect
rect
)
{}
/// Informs the text input control about selection area changes.
///
/// This method is called when the attached input client's selection area
/// changes.
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{}
/// Informs the text input control about text style changes.
///
/// This method is called on the when the attached input client's text style
...
...
@@ -2160,6 +2316,17 @@ class _PlatformTextInputControl with TextInputControl {
);
}
@override
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
_channel
.
invokeMethod
<
void
>(
'TextInput.setSelectionRects'
,
selectionRects
.
map
((
SelectionRect
rect
)
{
return
<
num
>[
rect
.
bounds
.
left
,
rect
.
bounds
.
top
,
rect
.
bounds
.
width
,
rect
.
bounds
.
height
,
rect
.
position
];
}).
toList
(),
);
}
@override
void
setStyle
({
required
String
?
fontFamily
,
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
009fa69f
...
...
@@ -2558,12 +2558,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
value
.
text
==
_value
.
text
&&
value
.
composing
==
_value
.
composing
)
{
// `selection` is the only change.
_handleSelectionChanged
(
value
.
selection
,
Scribble
.
scribbleInProgress
?
SelectionChangedCause
.
scribble
:
SelectionChangedCause
.
keyboard
,
);
_handleSelectionChanged
(
value
.
selection
,
(
_textInputConnection
?.
scribbleInProgress
??
false
)
?
SelectionChangedCause
.
scribble
:
SelectionChangedCause
.
keyboard
);
}
else
{
// Only hide the toolbar overlay, the selection handle's visibility will be handled
// by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673
...
...
@@ -3522,7 +3517,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
graphemeStart
=
graphemeEnd
;
}
Scribble
.
setSelectionRects
(
rects
);
_textInputConnection
!
.
setSelectionRects
(
rects
);
}
void
_updateSizeAndTransform
()
{
...
...
@@ -3533,7 +3528,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_updateSelectionRects
();
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
_
)
=>
_updateSizeAndTransform
());
}
else
if
(
_placeholderLocation
!=
-
1
)
{
_
removeTextPlaceholder
();
removeTextPlaceholder
();
}
}
...
...
@@ -3626,6 +3621,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
///
/// Returns `false` if a toolbar couldn't be shown, such as when the toolbar
/// is already shown, or when no text selection currently exists.
@override
bool
showToolbar
()
{
// Web is using native dom elements to enable clipboard functionality of the
// toolbar: copy, paste, select, cut. It might also provide additional
...
...
@@ -3696,6 +3692,39 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
// text.
//
// A value of -1 indicates there should be no placeholder, otherwise the
// value should be between 0 and the length of the text, inclusive.
int
_placeholderLocation
=
-
1
;
@override
void
insertTextPlaceholder
(
Size
size
)
{
if
(!
widget
.
scribbleEnabled
)
{
return
;
}
if
(!
widget
.
controller
.
selection
.
isValid
)
{
return
;
}
setState
(()
{
_placeholderLocation
=
_value
.
text
.
length
-
widget
.
controller
.
selection
.
end
;
});
}
@override
void
removeTextPlaceholder
()
{
if
(!
widget
.
scribbleEnabled
)
{
return
;
}
setState
(()
{
_placeholderLocation
=
-
1
;
});
}
@override
void
performSelector
(
String
selectorName
)
{
final
Intent
?
intent
=
intentForMacOSSelector
(
selectorName
);
...
...
@@ -3978,35 +4007,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return
Actions
.
invoke
(
context
,
intent
);
}
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
// text.
//
// A value of -1 indicates there should be no placeholder, otherwise the
// value should be between 0 and the length of the text, inclusive.
int
_placeholderLocation
=
-
1
;
void
_onPlaceholderLocationChanged
(
int
location
)
{
setState
(()
{
_placeholderLocation
=
location
;
});
}
void
_onScribbleFocus
(
Offset
offset
)
{
widget
.
focusNode
.
requestFocus
();
renderEditable
.
selectPositionAt
(
from:
offset
,
cause:
SelectionChangedCause
.
scribble
);
_openInputConnection
();
_updateSelectionRects
(
force:
true
);
}
void
_removeTextPlaceholder
()
{
if
(!
widget
.
scribbleEnabled
)
{
return
;
}
setState
(()
{
_placeholderLocation
=
-
1
;
});
}
/// The default behavior used if [onTapOutside] is null.
///
...
...
@@ -4121,12 +4121,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onPaste:
_semanticsOnPaste
(
controls
),
child:
_ScribbleFocusable
(
focusNode:
widget
.
focusNode
,
editableKey:
_editableKey
,
enabled:
widget
.
scribbleEnabled
,
onPlaceholderLocationChanged:
_onPlaceholderLocationChanged
,
onScribbleFocus:
_onScribbleFocus
,
onShowToolbar:
showToolbar
,
readOnly:
widget
.
readOnly
,
value:
_value
,
updateSelectionRects:
()
{
_openInputConnection
();
_updateSelectionRects
(
force:
true
);
},
child:
_Editable
(
key:
_editableKey
,
startHandleLayerLink:
_startHandleLayerLink
,
...
...
@@ -4441,13 +4441,6 @@ class _Editable extends MultiChildRenderObjectWidget {
}
}
/// A function that that takes a placeholder location as an int offset into some
/// text.
typedef
_PlaceholderLocationCallback
=
void
Function
(
int
location
);
/// A function that takes the Offset at which focus is requested.
typedef
_ScribbleFocusCallback
=
void
Function
(
Offset
offset
);
@immutable
class
_ScribbleCacheKey
{
const
_ScribbleCacheKey
({
...
...
@@ -4488,88 +4481,55 @@ class _ScribbleCacheKey {
}
}
/// A widget that provides the ability to receive handwriting input from
/// [Scribble].
class
_ScribbleFocusable
extends
StatefulWidget
{
const
_ScribbleFocusable
({
required
this
.
child
,
required
this
.
enabled
,
required
this
.
focusNode
,
required
this
.
onPlaceholderLocationChanged
,
required
this
.
onScribbleFocus
,
required
this
.
onShowToolbar
,
required
this
.
readOnly
,
required
this
.
value
,
required
this
.
editableKey
,
required
this
.
updateSelectionRects
,
required
this
.
enabled
,
});
final
Widget
child
;
final
bool
enabled
;
final
FocusNode
focusNode
;
final
_PlaceholderLocationCallback
onPlaceholderLocationChanged
;
final
_ScribbleFocusCallback
onScribbleFocus
;
final
VoidCallback
onShowToolbar
;
final
bool
readOnly
;
final
TextEditingValue
value
;
final
GlobalKey
editableKey
;
final
VoidCallback
updateSelectionRects
;
final
bool
enabled
;
@override
_ScribbleFocusableState
createState
()
=>
_ScribbleFocusableState
();
}
class
_ScribbleFocusableState
extends
State
<
_ScribbleFocusable
>
with
ScribbleClient
{
class
_ScribbleFocusableState
extends
State
<
_ScribbleFocusable
>
implements
ScribbleClient
{
_ScribbleFocusableState
():
_elementIdentifier
=
(
_nextElementIdentifier
++).
toString
();
void
_onFocusChange
()
{
_updateClient
(
widget
.
focusNode
.
hasFocus
);
}
void
_updateClient
(
bool
hasFocus
)
{
if
(
hasFocus
)
{
if
(
Scribble
.
client
!=
this
)
{
Scribble
.
client
=
this
;
}
}
else
if
(
Scribble
.
client
==
this
)
{
Scribble
.
client
=
null
;
}
}
@override
void
initState
()
{
super
.
initState
();
_updateClient
(
widget
.
focusNode
.
hasFocus
);
widget
.
focusNode
.
addListener
(
_onFocusChange
);
if
(
widget
.
enabled
)
{
Scribble
.
registerScribbleElement
(
elementIdentifier
,
this
);
TextInput
.
registerScribbleElement
(
elementIdentifier
,
this
);
}
}
@override
void
didUpdateWidget
(
_ScribbleFocusable
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
focusNode
!=
widget
.
focusNode
)
{
oldWidget
.
focusNode
.
removeListener
(
_onFocusChange
);
widget
.
focusNode
.
addListener
(
_onFocusChange
);
_updateClient
(
widget
.
focusNode
.
hasFocus
);
}
if
(!
oldWidget
.
enabled
&&
widget
.
enabled
)
{
Scribble
.
registerScribbleElement
(
elementIdentifier
,
this
);
TextInput
.
registerScribbleElement
(
elementIdentifier
,
this
);
}
if
(
oldWidget
.
enabled
&&
!
widget
.
enabled
)
{
Scribble
.
unregisterScribbleElement
(
elementIdentifier
);
TextInput
.
unregisterScribbleElement
(
elementIdentifier
);
}
}
@override
void
dispose
()
{
Scribble
.
unregisterScribbleElement
(
elementIdentifier
);
widget
.
focusNode
.
removeListener
(
_onFocusChange
);
if
(
Scribble
.
client
==
this
)
{
Scribble
.
client
=
null
;
}
TextInput
.
unregisterScribbleElement
(
elementIdentifier
);
super
.
dispose
();
}
// Start ScribbleClient.
RenderEditable
?
get
renderEditable
=>
widget
.
editableKey
.
currentContext
?.
findRenderObject
()
as
RenderEditable
?;
static
int
_nextElementIdentifier
=
1
;
final
String
_elementIdentifier
;
...
...
@@ -4579,38 +4539,15 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
@override
void
onScribbleFocus
(
Offset
offset
)
{
return
widget
.
onScribbleFocus
(
offset
);
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
if
(!
widget
.
enabled
||
!
widget
.
value
.
selection
.
isValid
||
widget
.
readOnly
)
{
return
;
}
widget
.
onPlaceholderLocationChanged
(
widget
.
value
.
text
.
length
-
widget
.
value
.
selection
.
end
,
);
}
@override
void
removeTextPlaceholder
()
{
if
(!
widget
.
enabled
)
{
return
;
}
widget
.
onPlaceholderLocationChanged
(-
1
);
}
@override
void
showToolbar
()
{
widget
.
onShowToolbar
();
widget
.
focusNode
.
requestFocus
();
renderEditable
?.
selectPositionAt
(
from:
offset
,
cause:
SelectionChangedCause
.
scribble
);
widget
.
updateSelectionRects
();
}
@override
bool
isInScribbleRect
(
Rect
rect
)
{
final
Rect
calculatedBounds
=
bounds
;
if
(
widget
.
readOnly
)
{
if
(
renderEditable
?.
readOnly
??
false
)
{
return
false
;
}
if
(
calculatedBounds
==
Rect
.
zero
)
{
...
...
@@ -4622,8 +4559,7 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
final
Rect
intersection
=
calculatedBounds
.
intersect
(
rect
);
final
HitTestResult
result
=
HitTestResult
();
WidgetsBinding
.
instance
.
hitTest
(
result
,
intersection
.
center
);
final
RenderObject
?
renderObject
=
context
.
findRenderObject
();
return
result
.
path
.
any
((
HitTestEntry
entry
)
=>
entry
.
target
==
renderObject
);
return
result
.
path
.
any
((
HitTestEntry
entry
)
=>
entry
.
target
==
renderEditable
);
}
@override
...
...
@@ -4636,8 +4572,6 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> with ScribbleCli
return
MatrixUtils
.
transformRect
(
transform
,
Rect
.
fromLTWH
(
0
,
0
,
box
.
size
.
width
,
box
.
size
.
height
));
}
// End ScribbleClient.
@override
Widget
build
(
BuildContext
context
)
{
return
widget
.
child
;
...
...
packages/flutter/test/services/autofill_test.dart
View file @
009fa69f
...
...
@@ -147,6 +147,21 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
@override
void
autofill
(
TextEditingValue
newEditingValue
)
=>
updateEditingValue
(
newEditingValue
);
@override
void
showToolbar
()
{
latestMethodCall
=
'showToolbar'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
}
@override
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
...
...
packages/flutter/test/services/binding_test.dart
View file @
009fa69f
...
...
@@ -106,4 +106,11 @@ void main() {
await
rootBundle
.
loadString
(
'test_asset2'
);
expect
(
flutterAssetsCallCount
,
4
);
});
test
(
'initInstances sets a default method call handler for SystemChannels.textInput'
,
()
async
{
final
ByteData
message
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'method'
:
'TextInput.requestElementsInRect'
,
'args'
:
null
})!;
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
message
,
(
ByteData
?
data
)
{
expect
(
data
,
isNotNull
);
});
});
}
packages/flutter/test/services/delta_text_input_test.dart
View file @
009fa69f
...
...
@@ -271,6 +271,21 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
}
@override
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
}
@override
void
showToolbar
()
{
latestMethodCall
=
'showToolbar'
;
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
...
...
packages/flutter/test/services/scribble_test.dart
deleted
100644 → 0
View file @
8a9ddade
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'text_input_utils.dart'
;
void
main
(
)
{
TestWidgetsFlutterBinding
.
ensureInitialized
();
test
(
'ScribbleClient showToolbar method is called'
,
()
async
{
final
FakeScribbleElement
targetElement
=
FakeScribbleElement
(
elementIdentifier:
'target'
);
Scribble
.
client
=
targetElement
;
expect
(
targetElement
.
latestMethodCall
,
isEmpty
);
// Send showToolbar message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'Scribble.showToolbar'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
targetElement
.
latestMethodCall
,
'showToolbar'
);
});
test
(
'ScribbleClient removeTextPlaceholder method is called'
,
()
async
{
final
FakeScribbleElement
targetElement
=
FakeScribbleElement
(
elementIdentifier:
'target'
);
Scribble
.
client
=
targetElement
;
expect
(
targetElement
.
latestMethodCall
,
isEmpty
);
// Send removeTextPlaceholder message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'Scribble.removeTextPlaceholder'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
targetElement
.
latestMethodCall
,
'removeTextPlaceholder'
);
});
test
(
'ScribbleClient insertTextPlaceholder method is called'
,
()
async
{
final
FakeScribbleElement
targetElement
=
FakeScribbleElement
(
elementIdentifier:
'target'
);
Scribble
.
client
=
targetElement
;
expect
(
targetElement
.
latestMethodCall
,
isEmpty
);
// Send insertTextPlaceholder message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'Scribble.insertTextPlaceholder'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
targetElement
.
latestMethodCall
,
'insertTextPlaceholder'
);
});
test
(
'ScribbleClient scribbleInteractionBegan and scribbleInteractionFinished'
,
()
async
{
Scribble
.
ensureInitialized
();
expect
(
Scribble
.
scribbleInProgress
,
isFalse
);
// Send scribbleInteractionBegan message.
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'Scribble.scribbleInteractionBegan'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
Scribble
.
scribbleInProgress
,
isTrue
);
// Send scribbleInteractionFinished message.
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'Scribble.scribbleInteractionFinished'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
Scribble
.
scribbleInProgress
,
isFalse
);
});
test
(
'ScribbleClient focusElement'
,
()
async
{
final
FakeScribbleElement
targetElement
=
FakeScribbleElement
(
elementIdentifier:
'target'
);
Scribble
.
registerScribbleElement
(
targetElement
.
elementIdentifier
,
targetElement
);
final
FakeScribbleElement
otherElement
=
FakeScribbleElement
(
elementIdentifier:
'other'
);
Scribble
.
registerScribbleElement
(
otherElement
.
elementIdentifier
,
otherElement
);
expect
(
targetElement
.
latestMethodCall
,
isEmpty
);
expect
(
otherElement
.
latestMethodCall
,
isEmpty
);
// Send focusElement message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
targetElement
.
elementIdentifier
,
0.0
,
0.0
],
'method'
:
'Scribble.focusElement'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
Scribble
.
unregisterScribbleElement
(
targetElement
.
elementIdentifier
);
Scribble
.
unregisterScribbleElement
(
otherElement
.
elementIdentifier
);
expect
(
targetElement
.
latestMethodCall
,
'onScribbleFocus'
);
expect
(
otherElement
.
latestMethodCall
,
isEmpty
);
});
test
(
'ScribbleClient requestElementsInRect'
,
()
async
{
final
List
<
FakeScribbleElement
>
targetElements
=
<
FakeScribbleElement
>[
FakeScribbleElement
(
elementIdentifier:
'target1'
,
bounds:
const
Rect
.
fromLTWH
(
0.0
,
0.0
,
100.0
,
100.0
)),
FakeScribbleElement
(
elementIdentifier:
'target2'
,
bounds:
const
Rect
.
fromLTWH
(
0.0
,
100.0
,
100.0
,
100.0
)),
];
final
List
<
FakeScribbleElement
>
otherElements
=
<
FakeScribbleElement
>[
FakeScribbleElement
(
elementIdentifier:
'other1'
,
bounds:
const
Rect
.
fromLTWH
(
100.0
,
0.0
,
100.0
,
100.0
)),
FakeScribbleElement
(
elementIdentifier:
'other2'
,
bounds:
const
Rect
.
fromLTWH
(
100.0
,
100.0
,
100.0
,
100.0
)),
];
void
registerElements
(
FakeScribbleElement
element
)
=>
Scribble
.
registerScribbleElement
(
element
.
elementIdentifier
,
element
);
void
unregisterElements
(
FakeScribbleElement
element
)
=>
Scribble
.
unregisterScribbleElement
(
element
.
elementIdentifier
);
<
FakeScribbleElement
>[...
targetElements
,
...
otherElements
].
forEach
(
registerElements
);
// Send requestElementsInRect message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
0.0
,
50.0
,
50.0
,
100.0
],
'method'
:
'Scribble.requestElementsInRect'
,
});
ByteData
?
responseBytes
;
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/scribble'
,
messageBytes
,
(
ByteData
?
response
)
{
responseBytes
=
response
;
},
);
<
FakeScribbleElement
>[...
targetElements
,
...
otherElements
].
forEach
(
unregisterElements
);
final
List
<
List
<
dynamic
>>
responses
=
(
const
JSONMessageCodec
().
decodeMessage
(
responseBytes
)
as
List
<
dynamic
>).
cast
<
List
<
dynamic
>>();
expect
(
responses
.
first
.
length
,
2
);
expect
(
responses
.
first
.
first
,
containsAllInOrder
(<
dynamic
>[
targetElements
.
first
.
elementIdentifier
,
0.0
,
0.0
,
100.0
,
100.0
]));
expect
(
responses
.
first
.
last
,
containsAllInOrder
(<
dynamic
>[
targetElements
.
last
.
elementIdentifier
,
0.0
,
100.0
,
100.0
,
100.0
]));
});
}
class
FakeScribbleClient
implements
ScribbleClient
{
FakeScribbleClient
();
String
latestMethodCall
=
''
;
@override
String
get
elementIdentifier
=>
''
;
@override
void
onScribbleFocus
(
Offset
offset
)
{
latestMethodCall
=
'onScribbleFocus'
;
}
@override
bool
isInScribbleRect
(
Rect
rect
)
{
latestMethodCall
=
'isInScribbleRect'
;
return
false
;
}
@override
Rect
get
bounds
=>
Rect
.
zero
;
@override
void
showToolbar
()
{
latestMethodCall
=
'showToolbar'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
}
@override
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
}
}
packages/flutter/test/services/text_input_test.dart
View file @
009fa69f
...
...
@@ -610,6 +610,148 @@ void main() {
expect
(
client
.
latestMethodCall
,
'showAutocorrectionPromptRect'
);
});
test
(
'TextInputClient showToolbar method is called'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
TextInput
.
attach
(
client
,
configuration
);
expect
(
client
.
latestMethodCall
,
isEmpty
);
// Send showToolbar message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'TextInputClient.showToolbar'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
client
.
latestMethodCall
,
'showToolbar'
);
});
});
group
(
'Scribble interactions'
,
()
{
tearDown
(()
{
TextInputConnection
.
debugResetId
();
});
test
(
'TextInputClient scribbleInteractionBegan and scribbleInteractionFinished'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
final
TextInputConnection
connection
=
TextInput
.
attach
(
client
,
configuration
);
expect
(
connection
.
scribbleInProgress
,
false
);
// Send scribbleInteractionBegan message.
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'TextInputClient.scribbleInteractionBegan'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
connection
.
scribbleInProgress
,
true
);
// Send scribbleInteractionFinished message.
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
0
,
1
],
'method'
:
'TextInputClient.scribbleInteractionFinished'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
connection
.
scribbleInProgress
,
false
);
});
test
(
'TextInputClient focusElement'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
TextInput
.
attach
(
client
,
configuration
);
final
FakeScribbleElement
targetElement
=
FakeScribbleElement
(
elementIdentifier:
'target'
);
TextInput
.
registerScribbleElement
(
targetElement
.
elementIdentifier
,
targetElement
);
final
FakeScribbleElement
otherElement
=
FakeScribbleElement
(
elementIdentifier:
'other'
);
TextInput
.
registerScribbleElement
(
otherElement
.
elementIdentifier
,
otherElement
);
expect
(
targetElement
.
latestMethodCall
,
isEmpty
);
expect
(
otherElement
.
latestMethodCall
,
isEmpty
);
// Send focusElement message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
targetElement
.
elementIdentifier
,
0.0
,
0.0
],
'method'
:
'TextInputClient.focusElement'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
TextInput
.
unregisterScribbleElement
(
targetElement
.
elementIdentifier
);
TextInput
.
unregisterScribbleElement
(
otherElement
.
elementIdentifier
);
expect
(
targetElement
.
latestMethodCall
,
'onScribbleFocus'
);
expect
(
otherElement
.
latestMethodCall
,
isEmpty
);
});
test
(
'TextInputClient requestElementsInRect'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
TextInput
.
attach
(
client
,
configuration
);
final
List
<
FakeScribbleElement
>
targetElements
=
<
FakeScribbleElement
>[
FakeScribbleElement
(
elementIdentifier:
'target1'
,
bounds:
const
Rect
.
fromLTWH
(
0.0
,
0.0
,
100.0
,
100.0
)),
FakeScribbleElement
(
elementIdentifier:
'target2'
,
bounds:
const
Rect
.
fromLTWH
(
0.0
,
100.0
,
100.0
,
100.0
)),
];
final
List
<
FakeScribbleElement
>
otherElements
=
<
FakeScribbleElement
>[
FakeScribbleElement
(
elementIdentifier:
'other1'
,
bounds:
const
Rect
.
fromLTWH
(
100.0
,
0.0
,
100.0
,
100.0
)),
FakeScribbleElement
(
elementIdentifier:
'other2'
,
bounds:
const
Rect
.
fromLTWH
(
100.0
,
100.0
,
100.0
,
100.0
)),
];
void
registerElements
(
FakeScribbleElement
element
)
=>
TextInput
.
registerScribbleElement
(
element
.
elementIdentifier
,
element
);
void
unregisterElements
(
FakeScribbleElement
element
)
=>
TextInput
.
unregisterScribbleElement
(
element
.
elementIdentifier
);
<
FakeScribbleElement
>[...
targetElements
,
...
otherElements
].
forEach
(
registerElements
);
// Send requestElementsInRect message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
0.0
,
50.0
,
50.0
,
100.0
],
'method'
:
'TextInputClient.requestElementsInRect'
,
});
ByteData
?
responseBytes
;
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
response
)
{
responseBytes
=
response
;
},
);
<
FakeScribbleElement
>[...
targetElements
,
...
otherElements
].
forEach
(
unregisterElements
);
final
List
<
List
<
dynamic
>>
responses
=
(
const
JSONMessageCodec
().
decodeMessage
(
responseBytes
)
as
List
<
dynamic
>).
cast
<
List
<
dynamic
>>();
expect
(
responses
.
first
.
length
,
2
);
expect
(
responses
.
first
.
first
,
containsAllInOrder
(<
dynamic
>[
targetElements
.
first
.
elementIdentifier
,
0.0
,
0.0
,
100.0
,
100.0
]));
expect
(
responses
.
first
.
last
,
containsAllInOrder
(<
dynamic
>[
targetElements
.
last
.
elementIdentifier
,
0.0
,
100.0
,
100.0
,
100.0
]));
});
});
test
(
'TextEditingValue.isComposingRangeValid'
,
()
async
{
...
...
@@ -764,6 +906,12 @@ void main() {
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
6
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setEditableSizeAndTransform'
);
connection
.
setSelectionRects
(
const
<
SelectionRect
>[
SelectionRect
(
position:
0
,
bounds:
Rect
.
zero
)]);
expectedMethodCalls
.
add
(
'setSelectionRects'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
7
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setSelectionRects'
);
connection
.
setStyle
(
fontFamily:
null
,
fontSize:
null
,
...
...
@@ -773,20 +921,20 @@ void main() {
);
expectedMethodCalls
.
add
(
'setStyle'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
7
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
8
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.setStyle'
);
connection
.
close
();
expectedMethodCalls
.
add
(
'detach'
);
expect
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
8
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
9
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.clearClient'
);
expectedMethodCalls
.
add
(
'hide'
);
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
await
binding
.
runAsync
(()
async
{});
await
expectLater
(
control
.
methodCalls
,
expectedMethodCalls
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
9
);
expect
(
fakeTextChannel
.
outgoingCalls
.
length
,
10
);
expect
(
fakeTextChannel
.
outgoingCalls
.
last
.
method
,
'TextInput.hide'
);
});
...
...
@@ -850,6 +998,11 @@ class FakeTextInputClient with TextInputClient {
latestMethodCall
=
'showAutocorrectionPromptRect'
;
}
@override
void
showToolbar
()
{
latestMethodCall
=
'showToolbar'
;
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
();
@override
...
...
@@ -857,6 +1010,16 @@ class FakeTextInputClient with TextInputClient {
latestMethodCall
=
'didChangeInputControl'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
}
@override
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
...
...
@@ -915,6 +1078,11 @@ class FakeTextInputControl with TextInputControl {
methodCalls
.
add
(
'setEditableSizeAndTransform'
);
}
@override
void
setSelectionRects
(
List
<
SelectionRect
>
selectionRects
)
{
methodCalls
.
add
(
'setSelectionRects'
);
}
@override
void
setStyle
({
required
String
?
fontFamily
,
...
...
packages/flutter/test/services/text_input_utils.dart
View file @
009fa69f
...
...
@@ -65,7 +65,7 @@ class FakeTextChannel implements MethodChannel {
}
}
class
FakeScribbleElement
with
ScribbleClient
{
class
FakeScribbleElement
implements
ScribbleClient
{
FakeScribbleElement
({
required
String
elementIdentifier
,
Rect
bounds
=
Rect
.
zero
})
:
_elementIdentifier
=
elementIdentifier
,
_bounds
=
bounds
;
...
...
@@ -89,19 +89,4 @@ class FakeScribbleElement with ScribbleClient {
void
onScribbleFocus
(
Offset
offset
)
{
latestMethodCall
=
'onScribbleFocus'
;
}
@override
void
insertTextPlaceholder
(
Size
size
)
{
latestMethodCall
=
'insertTextPlaceholder'
;
}
@override
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
}
@override
void
showToolbar
()
{
latestMethodCall
=
'showToolbar'
;
}
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
009fa69f
...
...
@@ -2210,7 +2210,6 @@ void main() {
final
TextEditingController
controller
=
TextEditingController
(
text:
'Lorem ipsum dolor sit amet'
);
late
SelectionChangedCause
selectionCause
;
Scribble
.
ensureInitialized
();
await
tester
.
pumpWidget
(
MaterialApp
(
...
...
@@ -2230,7 +2229,7 @@ void main() {
),
);
await
tester
.
testTextInput
.
scribbleFocusElement
(
Scribble
.
scribbleClients
.
keys
.
first
,
Offset
.
zero
);
await
tester
.
testTextInput
.
scribbleFocusElement
(
TextInput
.
scribbleClients
.
keys
.
first
,
Offset
.
zero
);
expect
(
focusNode
.
hasFocus
,
true
);
expect
(
selectionCause
,
SelectionChangedCause
.
scribble
);
...
...
@@ -2256,7 +2255,7 @@ void main() {
),
);
final
List
<
dynamic
>
elementEntry
=
<
dynamic
>[
Scribble
.
scribbleClients
.
keys
.
first
,
0.0
,
0.0
,
800.0
,
600.0
];
final
List
<
dynamic
>
elementEntry
=
<
dynamic
>[
TextInput
.
scribbleClients
.
keys
.
first
,
0.0
,
0.0
,
800.0
,
600.0
];
List
<
List
<
dynamic
>>
elements
=
await
tester
.
testTextInput
.
scribbleRequestElementsInRect
(
const
Rect
.
fromLTWH
(
0
,
0
,
1
,
1
));
expect
(
elements
.
first
,
containsAll
(
elementEntry
));
...
...
@@ -4630,8 +4629,8 @@ void main() {
tester
.
binding
.
window
.
physicalSizeTestValue
=
const
Size
(
750.0
,
1334.0
);
final
List
<
List
<
SelectionRect
>>
log
=
<
List
<
SelectionRect
>>[];
SystemChannels
.
scribble
.
setMockMethodCallHandler
((
MethodCall
methodCall
)
async
{
if
(
methodCall
.
method
==
'
Scribble
.setSelectionRects'
)
{
SystemChannels
.
textInput
.
setMockMethodCallHandler
((
MethodCall
methodCall
)
async
{
if
(
methodCall
.
method
==
'
TextInput
.setSelectionRects'
)
{
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
final
List
<
SelectionRect
>
selectionRects
=
<
SelectionRect
>[];
for
(
final
dynamic
rect
in
args
)
{
...
...
@@ -4802,76 +4801,6 @@ void main() {
// On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects.
},
skip:
kIsWeb
,
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
}));
// [intended]
testWidgets
(
'scribble client is set based on most recent focus'
,
(
WidgetTester
tester
)
async
{
final
List
<
MethodCall
>
log
=
<
MethodCall
>[];
SystemChannels
.
textInput
.
setMockMethodCallHandler
((
MethodCall
methodCall
)
async
{
log
.
add
(
methodCall
);
});
final
TextEditingController
controller
=
TextEditingController
();
controller
.
text
=
'Text1'
;
final
GlobalKey
key1
=
GlobalKey
();
final
GlobalKey
key2
=
GlobalKey
();
final
FocusNode
focusNode1
=
FocusNode
();
final
FocusNode
focusNode2
=
FocusNode
();
Scribble
.
client
=
null
;
await
tester
.
pumpWidget
(
MediaQuery
(
data:
const
MediaQueryData
(),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
EditableText
(
key:
key1
,
controller:
TextEditingController
(),
focusNode:
focusNode1
,
style:
Typography
.
material2018
().
black
.
subtitle1
!,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scribbleEnabled:
false
,
),
EditableText
(
key:
key2
,
controller:
TextEditingController
(),
focusNode:
focusNode2
,
style:
Typography
.
material2018
().
black
.
subtitle1
!,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scribbleEnabled:
false
,
),
],
),
),
),
);
expect
(
Scribble
.
client
,
isNull
);
focusNode1
.
requestFocus
();
await
tester
.
pump
();
expect
(
Scribble
.
client
,
isNotNull
);
final
ScribbleClient
client1
=
Scribble
.
client
!;
focusNode2
.
requestFocus
();
await
tester
.
pump
();
expect
(
Scribble
.
client
,
isNot
(
client1
));
expect
(
Scribble
.
client
,
isNotNull
);
focusNode2
.
unfocus
();
await
tester
.
pump
();
expect
(
Scribble
.
client
,
isNull
);
// On web, we should rely on the browser's implementation of Scribble.
},
skip:
kIsWeb
);
// [intended]
testWidgets
(
'text styling info is sent on show keyboard'
,
(
WidgetTester
tester
)
async
{
final
List
<
MethodCall
>
log
=
<
MethodCall
>[];
tester
.
binding
.
defaultBinaryMessenger
.
setMockMethodCallHandler
(
SystemChannels
.
textInput
,
(
MethodCall
methodCall
)
async
{
...
...
packages/flutter_test/lib/src/test_text_input.dart
View file @
009fa69f
...
...
@@ -285,10 +285,10 @@ class TestTextInput {
Future
<
void
>
startScribbleInteraction
()
async
{
assert
(
isRegistered
);
await
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
scribble
.
name
,
SystemChannels
.
scribble
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'
Scribble
.scribbleInteractionBegan'
,
'
TextInputClient
.scribbleInteractionBegan'
,
<
dynamic
>[
_client
??
-
1
,]
),
),
...
...
@@ -300,10 +300,10 @@ class TestTextInput {
Future
<
void
>
scribbleFocusElement
(
String
elementIdentifier
,
Offset
offset
)
async
{
assert
(
isRegistered
);
await
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
scribble
.
name
,
SystemChannels
.
scribble
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'
Scribble
.focusElement'
,
'
TextInputClient
.focusElement'
,
<
dynamic
>[
elementIdentifier
,
offset
.
dx
,
offset
.
dy
]
),
),
...
...
@@ -316,15 +316,15 @@ class TestTextInput {
assert
(
isRegistered
);
List
<
List
<
dynamic
>>
response
=
<
List
<
dynamic
>>[];
await
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
scribble
.
name
,
SystemChannels
.
scribble
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'
Scribble
.requestElementsInRect'
,
'
TextInputClient
.requestElementsInRect'
,
<
dynamic
>[
rect
.
left
,
rect
.
top
,
rect
.
width
,
rect
.
height
]
),
),
(
ByteData
?
data
)
{
response
=
(
SystemChannels
.
scribble
.
codec
.
decodeEnvelope
(
data
!)
as
List
<
dynamic
>).
map
((
dynamic
element
)
=>
element
as
List
<
dynamic
>).
toList
();
response
=
(
SystemChannels
.
textInput
.
codec
.
decodeEnvelope
(
data
!)
as
List
<
dynamic
>).
map
((
dynamic
element
)
=>
element
as
List
<
dynamic
>).
toList
();
},
);
...
...
@@ -335,10 +335,10 @@ class TestTextInput {
Future
<
void
>
scribbleInsertPlaceholder
()
async
{
assert
(
isRegistered
);
await
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
scribble
.
name
,
SystemChannels
.
scribble
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'
Scribble
.insertTextPlaceholder'
,
'
TextInputClient
.insertTextPlaceholder'
,
<
dynamic
>[
_client
??
-
1
,
0.0
,
0.0
]
),
),
...
...
@@ -350,10 +350,10 @@ class TestTextInput {
Future
<
void
>
scribbleRemovePlaceholder
()
async
{
assert
(
isRegistered
);
await
TestDefaultBinaryMessengerBinding
.
instance
!.
defaultBinaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
scribble
.
name
,
SystemChannels
.
scribble
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'
Scribble
.removeTextPlaceholder'
,
'
TextInputClient
.removeTextPlaceholder'
,
<
dynamic
>[
_client
??
-
1
]
),
),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment