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
18ca3754
Unverified
Commit
18ca3754
authored
May 14, 2019
by
Mouad Debbar
Committed by
GitHub
May 14, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Show/hide toolbar and handles based on device kind (#29683)
parent
fe8cddbb
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
686 additions
and
92 deletions
+686
-92
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+49
-6
multitap.dart
packages/flutter/lib/src/gestures/multitap.dart
+15
-2
recognizer.dart
packages/flutter/lib/src/gestures/recognizer.dart
+31
-5
tap.dart
packages/flutter/lib/src/gestures/tap.dart
+17
-7
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+53
-5
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+22
-3
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+24
-16
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+189
-0
debug_test.dart
packages/flutter/test/gestures/debug_test.dart
+1
-1
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+280
-2
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+5
-45
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
18ca3754
...
@@ -469,6 +469,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -469,6 +469,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
FocusNode
_focusNode
;
FocusNode
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
// The selection overlay should only be shown when the user is interacting
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
// trigger the selection overlay.
// For backwards-compatibility, we treat a null kind the same as touch.
bool
_shouldShowSelectionToolbar
=
true
;
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
...
@@ -501,14 +507,26 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -501,14 +507,26 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
super
.
dispose
();
super
.
dispose
();
}
}
EditableTextState
get
_editableText
=>
_editableTextKey
.
currentState
;
void
_requestKeyboard
()
{
void
_requestKeyboard
()
{
_editableText
Key
.
currentState
?.
requestKeyboard
();
_editableText
?.
requestKeyboard
();
}
}
RenderEditable
get
_renderEditable
=>
_editableText
Key
.
currentState
.
renderEditable
;
RenderEditable
get
_renderEditable
=>
_editableText
.
renderEditable
;
void
_handleTapDown
(
TapDownDetails
details
)
{
void
_handleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
handleTapDown
(
details
);
_renderEditable
.
handleTapDown
(
details
);
// The selection overlay should only be shown when the user is interacting
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
// trigger the selection overlay.
// For backwards-compatibility, we treat a null kind the same as touch.
final
PointerDeviceKind
kind
=
details
.
kind
;
_shouldShowSelectionToolbar
=
kind
==
null
||
kind
==
PointerDeviceKind
.
touch
||
kind
==
PointerDeviceKind
.
stylus
;
}
}
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
...
@@ -523,7 +541,8 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -523,7 +541,8 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
from:
details
.
globalPosition
,
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
forcePress
,
cause:
SelectionChangedCause
.
forcePress
,
);
);
_editableTextKey
.
currentState
.
showToolbar
();
if
(
_shouldShowSelectionToolbar
)
_editableText
.
showToolbar
();
}
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
...
@@ -546,12 +565,33 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -546,12 +565,33 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
}
}
void
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
void
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
_editableTextKey
.
currentState
.
showToolbar
();
if
(
_shouldShowSelectionToolbar
)
_editableText
.
showToolbar
();
}
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
tap
);
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
tap
);
_editableTextKey
.
currentState
.
showToolbar
();
if
(
_shouldShowSelectionToolbar
)
_editableText
.
showToolbar
();
}
bool
_shouldShowSelectionHandles
(
SelectionChangedCause
cause
)
{
// When the text field is activated by something that doesn't trigger the
// selection overlay, we shouldn't show the handles either.
if
(!
_shouldShowSelectionToolbar
)
return
false
;
// On iOS, we don't show handles when the selection is collapsed.
if
(
_effectiveController
.
selection
.
isCollapsed
)
return
false
;
if
(
cause
==
SelectionChangedCause
.
keyboard
)
return
false
;
if
(
_effectiveController
.
text
.
isNotEmpty
)
return
true
;
return
false
;
}
}
void
_handleMouseDragSelectionStart
(
DragStartDetails
details
)
{
void
_handleMouseDragSelectionStart
(
DragStartDetails
details
)
{
...
@@ -578,7 +618,10 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -578,7 +618,10 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
_editableTextKey
.
currentState
?.
bringIntoView
(
selection
.
base
);
_editableText
?.
bringIntoView
(
selection
.
base
);
}
if
(
_shouldShowSelectionHandles
(
cause
))
{
_editableText
?.
showHandles
();
}
}
}
}
...
...
packages/flutter/lib/src/gestures/multitap.dart
View file @
18ca3754
...
@@ -397,7 +397,12 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
...
@@ -397,7 +397,12 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
longTapDelay:
longTapDelay
,
longTapDelay:
longTapDelay
,
);
);
if
(
onTapDown
!=
null
)
if
(
onTapDown
!=
null
)
invokeCallback
<
void
>(
'onTapDown'
,
()
=>
onTapDown
(
event
.
pointer
,
TapDownDetails
(
globalPosition:
event
.
position
)));
invokeCallback
<
void
>(
'onTapDown'
,
()
{
onTapDown
(
event
.
pointer
,
TapDownDetails
(
globalPosition:
event
.
position
,
kind:
event
.
kind
,
));
});
}
}
@override
@override
...
@@ -432,7 +437,15 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
...
@@ -432,7 +437,15 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
void
_dispatchLongTap
(
int
pointer
,
Offset
lastPosition
)
{
void
_dispatchLongTap
(
int
pointer
,
Offset
lastPosition
)
{
assert
(
_gestureMap
.
containsKey
(
pointer
));
assert
(
_gestureMap
.
containsKey
(
pointer
));
if
(
onLongTapDown
!=
null
)
if
(
onLongTapDown
!=
null
)
invokeCallback
<
void
>(
'onLongTapDown'
,
()
=>
onLongTapDown
(
pointer
,
TapDownDetails
(
globalPosition:
lastPosition
)));
invokeCallback
<
void
>(
'onLongTapDown'
,
()
{
onLongTapDown
(
pointer
,
TapDownDetails
(
globalPosition:
lastPosition
,
kind:
getKindForPointer
(
pointer
),
),
);
});
}
}
@override
@override
...
...
packages/flutter/lib/src/gestures/recognizer.dart
View file @
18ca3754
...
@@ -64,7 +64,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
...
@@ -64,7 +64,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
/// by providing the optional [kind] argument. If [kind] is null,
/// by providing the optional [kind] argument. If [kind] is null,
/// the recognizer will accept pointer events from all device kinds.
/// the recognizer will accept pointer events from all device kinds.
/// {@endtemplate}
/// {@endtemplate}
GestureRecognizer
({
this
.
debugOwner
,
PointerDeviceKind
kind
})
:
_kind
=
kind
;
GestureRecognizer
({
this
.
debugOwner
,
PointerDeviceKind
kind
})
:
_kind
Filter
=
kind
;
/// The recognizer's owner.
/// The recognizer's owner.
///
///
...
@@ -74,7 +74,11 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
...
@@ -74,7 +74,11 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
/// The kind of device that's allowed to be recognized. If null, events from
/// The kind of device that's allowed to be recognized. If null, events from
/// all device kinds will be tracked and recognized.
/// all device kinds will be tracked and recognized.
final
PointerDeviceKind
_kind
;
final
PointerDeviceKind
_kindFilter
;
/// Holds a mapping between pointer IDs and the kind of devices they are
/// coming from.
final
Map
<
int
,
PointerDeviceKind
>
_pointerToKind
=
<
int
,
PointerDeviceKind
>{};
/// Registers a new pointer that might be relevant to this gesture
/// Registers a new pointer that might be relevant to this gesture
/// detector.
/// detector.
...
@@ -92,6 +96,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
...
@@ -92,6 +96,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
/// This method is called for each and all pointers being added. In
/// This method is called for each and all pointers being added. In
/// most cases, you want to override [addAllowedPointer] instead.
/// most cases, you want to override [addAllowedPointer] instead.
void
addPointer
(
PointerDownEvent
event
)
{
void
addPointer
(
PointerDownEvent
event
)
{
_pointerToKind
[
event
.
pointer
]
=
event
.
kind
;
if
(
isPointerAllowed
(
event
))
{
if
(
isPointerAllowed
(
event
))
{
addAllowedPointer
(
event
);
addAllowedPointer
(
event
);
}
else
{
}
else
{
...
@@ -123,7 +128,17 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
...
@@ -123,7 +128,17 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
bool
isPointerAllowed
(
PointerDownEvent
event
)
{
bool
isPointerAllowed
(
PointerDownEvent
event
)
{
// Currently, it only checks for device kind. But in the future we could check
// Currently, it only checks for device kind. But in the future we could check
// for other things e.g. mouse button.
// for other things e.g. mouse button.
return
_kind
==
null
||
_kind
==
event
.
kind
;
return
_kindFilter
==
null
||
_kindFilter
==
event
.
kind
;
}
/// For a given pointer ID, returns the device kind associated with it.
///
/// The pointer ID is expected to be a valid one i.e. an event was received
/// with that pointer ID.
@protected
PointerDeviceKind
getKindForPointer
(
int
pointer
)
{
assert
(
_pointerToKind
.
containsKey
(
pointer
));
return
_pointerToKind
[
pointer
];
}
}
/// Releases any resources used by the object.
/// Releases any resources used by the object.
...
@@ -411,7 +426,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
...
@@ -411,7 +426,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
primaryPointer
=
event
.
pointer
;
primaryPointer
=
event
.
pointer
;
initialPosition
=
event
.
position
;
initialPosition
=
event
.
position
;
if
(
deadline
!=
null
)
if
(
deadline
!=
null
)
_timer
=
Timer
(
deadline
,
didExceedDeadline
);
_timer
=
Timer
(
deadline
,
()
=>
didExceedDeadlineWithEvent
(
event
)
);
}
}
}
}
...
@@ -444,12 +459,23 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
...
@@ -444,12 +459,23 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
/// Override to be notified when [deadline] is exceeded.
/// Override to be notified when [deadline] is exceeded.
///
///
/// You must override this method if you supply a [deadline].
/// You must override this method or [didExceedDeadlineWithEvent] if you
/// supply a [deadline].
@protected
@protected
void
didExceedDeadline
()
{
void
didExceedDeadline
()
{
assert
(
deadline
==
null
);
assert
(
deadline
==
null
);
}
}
/// Same as [didExceedDeadline] but receives the [event] that initiated the
/// gesture.
///
/// You must override this method or [didExceedDeadline] if you supply a
/// [deadline].
@protected
void
didExceedDeadlineWithEvent
(
PointerDownEvent
event
)
{
didExceedDeadline
();
}
@override
@override
void
acceptGesture
(
int
pointer
)
{
void
acceptGesture
(
int
pointer
)
{
_gestureAccepted
=
true
;
_gestureAccepted
=
true
;
...
...
packages/flutter/lib/src/gestures/tap.dart
View file @
18ca3754
...
@@ -19,11 +19,16 @@ class TapDownDetails {
...
@@ -19,11 +19,16 @@ class TapDownDetails {
/// Creates details for a [GestureTapDownCallback].
/// Creates details for a [GestureTapDownCallback].
///
///
/// The [globalPosition] argument must not be null.
/// The [globalPosition] argument must not be null.
TapDownDetails
({
this
.
globalPosition
=
Offset
.
zero
})
TapDownDetails
({
:
assert
(
globalPosition
!=
null
);
this
.
globalPosition
=
Offset
.
zero
,
this
.
kind
,
})
:
assert
(
globalPosition
!=
null
);
/// The global position at which the pointer contacted the screen.
/// The global position at which the pointer contacted the screen.
final
Offset
globalPosition
;
final
Offset
globalPosition
;
/// The kind of the device that initiated the event.
final
PointerDeviceKind
kind
;
}
}
/// Signature for when a pointer that might cause a tap has contacted the
/// Signature for when a pointer that might cause a tap has contacted the
...
@@ -198,15 +203,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
...
@@ -198,15 +203,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
}
}
@override
@override
void
didExceedDeadline
(
)
{
void
didExceedDeadline
WithEvent
(
PointerDownEvent
event
)
{
_checkDown
();
_checkDown
(
event
.
pointer
);
}
}
@override
@override
void
acceptGesture
(
int
pointer
)
{
void
acceptGesture
(
int
pointer
)
{
super
.
acceptGesture
(
pointer
);
super
.
acceptGesture
(
pointer
);
if
(
pointer
==
primaryPointer
)
{
if
(
pointer
==
primaryPointer
)
{
_checkDown
();
_checkDown
(
pointer
);
_wonArenaForPrimaryPointer
=
true
;
_wonArenaForPrimaryPointer
=
true
;
_checkUp
();
_checkUp
();
}
}
...
@@ -224,10 +229,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
...
@@ -224,10 +229,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
}
}
}
}
void
_checkDown
()
{
void
_checkDown
(
int
pointer
)
{
if
(!
_sentTapDown
)
{
if
(!
_sentTapDown
)
{
if
(
onTapDown
!=
null
)
if
(
onTapDown
!=
null
)
invokeCallback
<
void
>(
'onTapDown'
,
()
{
onTapDown
(
TapDownDetails
(
globalPosition:
initialPosition
));
});
invokeCallback
<
void
>(
'onTapDown'
,
()
{
onTapDown
(
TapDownDetails
(
globalPosition:
initialPosition
,
kind:
getKindForPointer
(
pointer
),
));
});
_sentTapDown
=
true
;
_sentTapDown
=
true
;
}
}
}
}
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
18ca3754
...
@@ -513,6 +513,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -513,6 +513,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
&&
widget
.
decoration
!=
null
&&
widget
.
decoration
!=
null
&&
widget
.
decoration
.
counterText
==
null
;
&&
widget
.
decoration
.
counterText
==
null
;
bool
_shouldShowSelectionToolbar
=
true
;
InputDecoration
_getEffectiveDecoration
()
{
InputDecoration
_getEffectiveDecoration
()
{
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
...
@@ -605,17 +607,41 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -605,17 +607,41 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
super
.
dispose
();
super
.
dispose
();
}
}
EditableTextState
get
_editableText
=>
_editableTextKey
.
currentState
;
void
_requestKeyboard
()
{
void
_requestKeyboard
()
{
_editableTextKey
.
currentState
?.
requestKeyboard
();
_editableText
?.
requestKeyboard
();
}
bool
_shouldShowSelectionHandles
(
SelectionChangedCause
cause
)
{
// When the text field is activated by something that doesn't trigger the
// selection overlay, we shouldn't show the handles either.
if
(!
_shouldShowSelectionToolbar
)
return
false
;
if
(
cause
==
SelectionChangedCause
.
keyboard
)
return
false
;
if
(
cause
==
SelectionChangedCause
.
longPress
)
return
true
;
if
(
_effectiveController
.
text
.
isNotEmpty
)
return
true
;
return
false
;
}
}
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
// iOS cursor doesn't move via a selection handle. The scroll happens
// iOS cursor doesn't move via a selection handle. The scroll happens
// directly from new text selection changes.
// directly from new text selection changes.
if
(
_shouldShowSelectionHandles
(
cause
))
{
_editableText
?.
showHandles
();
}
switch
(
Theme
.
of
(
context
).
platform
)
{
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
iOS
:
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
_editableText
Key
.
currentState
?.
bringIntoView
(
selection
.
base
);
_editableText
?.
bringIntoView
(
selection
.
base
);
}
}
return
;
return
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
android
:
...
@@ -624,6 +650,13 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -624,6 +650,13 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
}
}
}
}
/// Toggle the toolbar when a selection handle is tapped.
void
_handleSelectionHandleTapped
()
{
if
(
_effectiveController
.
selection
.
isCollapsed
)
{
_editableText
.
toggleToolbar
();
}
}
InteractiveInkFeature
_createInkFeature
(
Offset
globalPosition
)
{
InteractiveInkFeature
_createInkFeature
(
Offset
globalPosition
)
{
final
MaterialInkController
inkController
=
Material
.
of
(
context
);
final
MaterialInkController
inkController
=
Material
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
...
@@ -663,6 +696,16 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -663,6 +696,16 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
void
_handleTapDown
(
TapDownDetails
details
)
{
void
_handleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
handleTapDown
(
details
);
_renderEditable
.
handleTapDown
(
details
);
_startSplash
(
details
.
globalPosition
);
_startSplash
(
details
.
globalPosition
);
// The selection overlay should only be shown when the user is interacting
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
// trigger the selection overlay.
// For backwards-compatibility, we treat a null kind the same as touch.
final
PointerDeviceKind
kind
=
details
.
kind
;
_shouldShowSelectionToolbar
=
kind
==
null
||
kind
==
PointerDeviceKind
.
touch
||
kind
==
PointerDeviceKind
.
stylus
;
}
}
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
...
@@ -671,7 +714,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -671,7 +714,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
from:
details
.
globalPosition
,
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
forcePress
,
cause:
SelectionChangedCause
.
forcePress
,
);
);
_editableTextKey
.
currentState
.
showToolbar
();
if
(
_shouldShowSelectionToolbar
)
_editableTextKey
.
currentState
.
showToolbar
();
}
}
}
}
...
@@ -738,13 +782,16 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -738,13 +782,16 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
}
}
void
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
void
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
_editableTextKey
.
currentState
.
showToolbar
();
print
(
'long tap end'
);
if
(
_shouldShowSelectionToolbar
)
_editableTextKey
.
currentState
.
showToolbar
();
}
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
if
(
widget
.
selectionEnabled
)
{
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
_editableTextKey
.
currentState
.
showToolbar
();
if
(
_shouldShowSelectionToolbar
)
_editableTextKey
.
currentState
.
showToolbar
();
}
}
}
}
...
@@ -884,6 +931,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
...
@@ -884,6 +931,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onSelectionChanged:
_handleSelectionChanged
,
onSelectionChanged:
_handleSelectionChanged
,
onEditingComplete:
widget
.
onEditingComplete
,
onEditingComplete:
widget
.
onEditingComplete
,
onSubmitted:
widget
.
onSubmitted
,
onSubmitted:
widget
.
onSubmitted
,
onSelectionHandleTapped:
_handleSelectionHandleTapped
,
inputFormatters:
formatters
,
inputFormatters:
formatters
,
rendererIgnoresPointer:
true
,
rendererIgnoresPointer:
true
,
cursorWidth:
widget
.
cursorWidth
,
cursorWidth:
widget
.
cursorWidth
,
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
18ca3754
...
@@ -290,6 +290,7 @@ class EditableText extends StatefulWidget {
...
@@ -290,6 +290,7 @@ class EditableText extends StatefulWidget {
this
.
onEditingComplete
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
this
.
onSubmitted
,
this
.
onSelectionChanged
,
this
.
onSelectionChanged
,
this
.
onSelectionHandleTapped
,
List
<
TextInputFormatter
>
inputFormatters
,
List
<
TextInputFormatter
>
inputFormatters
,
this
.
rendererIgnoresPointer
=
false
,
this
.
rendererIgnoresPointer
=
false
,
this
.
cursorWidth
=
2.0
,
this
.
cursorWidth
=
2.0
,
...
@@ -645,6 +646,9 @@ class EditableText extends StatefulWidget {
...
@@ -645,6 +646,9 @@ class EditableText extends StatefulWidget {
/// location).
/// location).
final
SelectionChangedCallback
onSelectionChanged
;
final
SelectionChangedCallback
onSelectionChanged
;
/// {@macro flutter.widgets.textSelection.onSelectionHandleTapped}
final
VoidCallback
onSelectionHandleTapped
;
/// {@template flutter.widgets.editableText.inputFormatters}
/// {@template flutter.widgets.editableText.inputFormatters}
/// Optional input validation and formatting overrides.
/// Optional input validation and formatting overrides.
///
///
...
@@ -1135,10 +1139,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -1135,10 +1139,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
selectionControls:
widget
.
selectionControls
,
selectionControls:
widget
.
selectionControls
,
selectionDelegate:
this
,
selectionDelegate:
this
,
dragStartBehavior:
widget
.
dragStartBehavior
,
dragStartBehavior:
widget
.
dragStartBehavior
,
onSelectionHandleTapped:
widget
.
onSelectionHandleTapped
,
);
);
final
bool
longPress
=
cause
==
SelectionChangedCause
.
longPress
;
if
(
cause
!=
SelectionChangedCause
.
keyboard
&&
(
_value
.
text
.
isNotEmpty
||
longPress
))
_selectionOverlay
.
showHandles
();
if
(
widget
.
onSelectionChanged
!=
null
)
if
(
widget
.
onSelectionChanged
!=
null
)
widget
.
onSelectionChanged
(
selection
,
cause
);
widget
.
onSelectionChanged
(
selection
,
cause
);
}
}
...
@@ -1381,6 +1384,22 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -1381,6 +1384,22 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_selectionOverlay
?.
hide
();
_selectionOverlay
?.
hide
();
}
}
/// Toggles the visibility of the toolbar.
void
toggleToolbar
()
{
assert
(
_selectionOverlay
!=
null
);
if
(
_selectionOverlay
.
toolbarIsVisible
)
{
hideToolbar
();
}
else
{
showToolbar
();
}
}
/// Shows the handles at the location of the current selection.
void
showHandles
()
{
assert
(
_selectionOverlay
!=
null
);
_selectionOverlay
.
showHandles
();
}
VoidCallback
_semanticsOnCopy
(
TextSelectionControls
controls
)
{
VoidCallback
_semanticsOnCopy
(
TextSelectionControls
controls
)
{
return
widget
.
selectionEnabled
&&
_hasFocus
&&
controls
?.
canCopy
(
this
)
==
true
return
widget
.
selectionEnabled
&&
_hasFocus
&&
controls
?.
canCopy
(
this
)
==
true
?
()
=>
controls
.
handleCopy
(
this
)
?
()
=>
controls
.
handleCopy
(
this
)
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
18ca3754
...
@@ -263,6 +263,7 @@ class TextSelectionOverlay {
...
@@ -263,6 +263,7 @@ class TextSelectionOverlay {
this
.
selectionControls
,
this
.
selectionControls
,
this
.
selectionDelegate
,
this
.
selectionDelegate
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
onSelectionHandleTapped
,
})
:
assert
(
value
!=
null
),
})
:
assert
(
value
!=
null
),
assert
(
context
!=
null
),
assert
(
context
!=
null
),
_value
=
value
{
_value
=
value
{
...
@@ -316,6 +317,14 @@ class TextSelectionOverlay {
...
@@ -316,6 +317,14 @@ class TextSelectionOverlay {
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
final
DragStartBehavior
dragStartBehavior
;
final
DragStartBehavior
dragStartBehavior
;
/// {@template flutter.widgets.textSelection.onSelectionHandleTapped}
/// A callback that's invoked when a selection handle is tapped.
///
/// Both regular taps and long presses invoke this callback, but a drag
/// gesture won't.
/// {@endtemplate}
final
VoidCallback
onSelectionHandleTapped
;
/// Controls the fade-in and fade-out animations for the toolbar and handles.
/// Controls the fade-in and fade-out animations for the toolbar and handles.
static
const
Duration
fadeDuration
=
Duration
(
milliseconds:
150
);
static
const
Duration
fadeDuration
=
Duration
(
milliseconds:
150
);
...
@@ -393,17 +402,26 @@ class TextSelectionOverlay {
...
@@ -393,17 +402,26 @@ class TextSelectionOverlay {
/// Whether the toolbar is currently visible.
/// Whether the toolbar is currently visible.
bool
get
toolbarIsVisible
=>
_toolbar
!=
null
;
bool
get
toolbarIsVisible
=>
_toolbar
!=
null
;
/// Hides the
overlay
.
/// Hides the
entire overlay including the toolbar and the handles
.
void
hide
()
{
void
hide
()
{
if
(
_handles
!=
null
)
{
if
(
_handles
!=
null
)
{
_handles
[
0
].
remove
();
_handles
[
0
].
remove
();
_handles
[
1
].
remove
();
_handles
[
1
].
remove
();
_handles
=
null
;
_handles
=
null
;
}
}
_toolbar
?.
remove
();
if
(
_toolbar
!=
null
)
{
_toolbar
=
null
;
hideToolbar
();
}
}
/// Hides the toolbar part of the overlay.
///
/// To hide the whole overlay, see [hide].
void
hideToolbar
()
{
assert
(
_toolbar
!=
null
);
_toolbarController
.
stop
();
_toolbarController
.
stop
();
_toolbar
.
remove
();
_toolbar
=
null
;
}
}
/// Final cleanup.
/// Final cleanup.
...
@@ -418,7 +436,7 @@ class TextSelectionOverlay {
...
@@ -418,7 +436,7 @@ class TextSelectionOverlay {
return
Container
();
// hide the second handle when collapsed
return
Container
();
// hide the second handle when collapsed
return
_TextSelectionHandleOverlay
(
return
_TextSelectionHandleOverlay
(
onSelectionHandleChanged:
(
TextSelection
newSelection
)
{
_handleSelectionHandleChanged
(
newSelection
,
position
);
},
onSelectionHandleChanged:
(
TextSelection
newSelection
)
{
_handleSelectionHandleChanged
(
newSelection
,
position
);
},
onSelectionHandleTapped:
_handle
SelectionHandleTapped
,
onSelectionHandleTapped:
on
SelectionHandleTapped
,
layerLink:
layerLink
,
layerLink:
layerLink
,
renderObject:
renderObject
,
renderObject:
renderObject
,
selection:
_selection
,
selection:
_selection
,
...
@@ -476,17 +494,6 @@ class TextSelectionOverlay {
...
@@ -476,17 +494,6 @@ class TextSelectionOverlay {
selectionDelegate
.
textEditingValue
=
_value
.
copyWith
(
selection:
newSelection
,
composing:
TextRange
.
empty
);
selectionDelegate
.
textEditingValue
=
_value
.
copyWith
(
selection:
newSelection
,
composing:
TextRange
.
empty
);
selectionDelegate
.
bringIntoView
(
textPosition
);
selectionDelegate
.
bringIntoView
(
textPosition
);
}
}
void
_handleSelectionHandleTapped
()
{
if
(
_value
.
selection
.
isCollapsed
)
{
if
(
_toolbar
!=
null
)
{
_toolbar
?.
remove
();
_toolbar
=
null
;
}
else
{
showToolbar
();
}
}
}
}
}
/// This widget represents a single draggable text selection handle.
/// This widget represents a single draggable text selection handle.
...
@@ -602,7 +609,8 @@ class _TextSelectionHandleOverlayState
...
@@ -602,7 +609,8 @@ class _TextSelectionHandleOverlayState
}
}
void
_handleTap
()
{
void
_handleTap
()
{
widget
.
onSelectionHandleTapped
();
if
(
widget
.
onSelectionHandleTapped
!=
null
)
widget
.
onSelectionHandleTapped
();
}
}
@override
@override
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
18ca3754
...
@@ -2008,6 +2008,195 @@ void main() {
...
@@ -2008,6 +2008,195 @@ void main() {
await
gesture
.
removePointer
();
await
gesture
.
removePointer
();
});
});
testWidgets
(
'Tap does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
// Tap to trigger the text field.
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
});
testWidgets
(
'Long press shows toolbar but not handles'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
// Long press to trigger the text field.
await
tester
.
longPress
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
pump
();
// A long press in Cupertino should position the cursor without any selection.
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
});
testWidgets
(
'Double tap shows handles and toolbar if selection is not collapsed'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
final
Offset
hPos
=
textOffsetToPosition
(
tester
,
9
);
// Position of 'h'.
// Double tap on 'h' to select 'ghi'.
await
tester
.
tapAt
(
hPos
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
hPos
);
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
},
);
testWidgets
(
'Double tap shows toolbar but not handles if selection is collapsed'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
final
Offset
textEndPos
=
textOffsetToPosition
(
tester
,
11
);
// Position at the end of text.
// Double tap to place the cursor at the end.
await
tester
.
tapAt
(
textEndPos
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tapAt
(
textEndPos
);
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
},
);
testWidgets
(
'Mouse long press does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
// Long press to trigger the text field.
final
Offset
textFieldPos
=
tester
.
getCenter
(
find
.
byType
(
CupertinoTextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldPos
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
(
const
Duration
(
seconds:
2
));
await
gesture
.
up
();
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
},
);
testWidgets
(
'Mouse double tap does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
),
),
),
);
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
// Double tap at the end of text.
final
Offset
textEndPos
=
textOffsetToPosition
(
tester
,
11
);
// Position at the end of text.
TestGesture
gesture
=
await
tester
.
startGesture
(
textEndPos
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
up
();
await
tester
.
pump
();
await
gesture
.
down
(
textEndPos
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
final
Offset
hPos
=
textOffsetToPosition
(
tester
,
9
);
// Position of 'h'.
// Double tap on 'h' to select 'ghi'.
gesture
=
await
tester
.
startGesture
(
hPos
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
up
();
await
tester
.
pump
();
await
gesture
.
down
(
hPos
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
},
);
testWidgets
(
testWidgets
(
'text field respects theme'
,
'text field respects theme'
,
(
WidgetTester
tester
)
async
{
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/gestures/debug_test.dart
View file @
18ca3754
...
@@ -151,7 +151,7 @@ void main() {
...
@@ -151,7 +151,7 @@ void main() {
expect
(
tap
.
toString
(),
equalsIgnoringHashCodes
(
'TapGestureRecognizer#00000(state: ready)'
));
expect
(
tap
.
toString
(),
equalsIgnoringHashCodes
(
'TapGestureRecognizer#00000(state: ready)'
));
const
PointerEvent
event
=
PointerDownEvent
(
pointer:
1
,
position:
Offset
(
10.0
,
10.0
));
const
PointerEvent
event
=
PointerDownEvent
(
pointer:
1
,
position:
Offset
(
10.0
,
10.0
));
tap
.
addPointer
(
event
);
tap
.
addPointer
(
event
);
tap
.
didExceedDeadline
(
);
tap
.
didExceedDeadline
WithEvent
(
event
);
expect
(
tap
.
toString
(),
equalsIgnoringHashCodes
(
'TapGestureRecognizer#00000(state: possible, sent tap down)'
));
expect
(
tap
.
toString
(),
equalsIgnoringHashCodes
(
'TapGestureRecognizer#00000(state: possible, sent tap down)'
));
});
});
}
}
packages/flutter/test/material/text_field_test.dart
View file @
18ca3754
...
@@ -5909,8 +5909,9 @@ void main() {
...
@@ -5909,8 +5909,9 @@ void main() {
),
),
);
);
final
RenderEditable
renderEditable
=
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
)).
renderEditable
;
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
final
RenderEditable
renderEditable
=
state
.
renderEditable
;
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
...
@@ -5961,4 +5962,281 @@ void main() {
...
@@ -5961,4 +5962,281 @@ void main() {
debugDefaultTargetPlatformOverride
=
null
;
debugDefaultTargetPlatformOverride
=
null
;
});
});
testWidgets
(
'Tap shows handles but not toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Tap to trigger the text field.
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
});
testWidgets
(
'Tap in empty text field does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Tap to trigger the text field.
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
},
);
testWidgets
(
'Long press shows handles and toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Long press to trigger the text field.
await
tester
.
longPress
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
});
testWidgets
(
'Long press in empty text field shows handles and toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Tap to trigger the text field.
await
tester
.
longPress
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
},
);
testWidgets
(
'Double tap shows handles and toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Double tap to trigger the text field.
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
});
testWidgets
(
'Double tap in empty text field shows toolbar but not handles'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Double tap to trigger the text field.
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
},
);
testWidgets
(
'Mouse tap does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Long press to trigger the text field.
final
Offset
textFieldPos
=
tester
.
getCenter
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldPos
,
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
},
);
testWidgets
(
'Mouse long press does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Long press to trigger the text field.
final
Offset
textFieldPos
=
tester
.
getCenter
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldPos
,
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
(
const
Duration
(
seconds:
2
));
await
gesture
.
up
();
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
},
);
testWidgets
(
'Mouse double tap does not show handles nor toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Double tap to trigger the text field.
final
Offset
textFieldPos
=
tester
.
getCenter
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textFieldPos
,
pointer:
7
,
kind:
PointerDeviceKind
.
mouse
,
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
up
();
await
tester
.
pump
();
await
gesture
.
down
(
textFieldPos
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
},
);
testWidgets
(
'Tapping selection handles toggles the toolbar'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'abc def ghi'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
),
),
),
);
// Tap to position the cursor and show the selection handles.
final
Offset
ePos
=
textOffsetToPosition
(
tester
,
5
);
// Index of 'e'.
await
tester
.
tapAt
(
ePos
,
pointer:
7
);
final
EditableTextState
editableText
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
expect
(
editableText
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
final
List
<
TextSelectionPoint
>
endpoints
=
globalize
(
renderEditable
.
getEndpointsForSelection
(
controller
.
selection
),
renderEditable
,
);
expect
(
endpoints
.
length
,
1
);
// Tap the handle to show the toolbar.
final
Offset
handlePos
=
endpoints
[
0
].
point
+
const
Offset
(
0.0
,
1.0
);
await
tester
.
tapAt
(
handlePos
,
pointer:
7
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isTrue
);
// Tap the handle again to hide the toolbar.
await
tester
.
tapAt
(
handlePos
,
pointer:
7
);
expect
(
editableText
.
selectionOverlay
.
toolbarIsVisible
,
isFalse
);
});
}
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
18ca3754
...
@@ -898,51 +898,6 @@ void main() {
...
@@ -898,51 +898,6 @@ void main() {
semantics
.
dispose
();
semantics
.
dispose
();
});
});
testWidgets
(
'changing selection with keyboard does not show handles'
,
(
WidgetTester
tester
)
async
{
const
String
value1
=
'Hello World'
;
controller
.
text
=
value1
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
EditableText
(
backgroundCursorColor:
Colors
.
grey
,
controller:
controller
,
selectionControls:
materialTextSelectionControls
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
cursorColor
,
),
),
);
// Simulate selection change via tap to show handles.
final
RenderEditable
render
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
o
)
=>
o
.
runtimeType
==
RenderEditable
);
render
.
onSelectionChanged
(
const
TextSelection
.
collapsed
(
offset:
4
),
render
,
SelectionChangedCause
.
tap
);
await
tester
.
pumpAndSettle
();
final
EditableTextState
textState
=
tester
.
state
(
find
.
byType
(
EditableText
));
expect
(
textState
.
selectionOverlay
.
handlesAreVisible
,
isTrue
);
expect
(
textState
.
selectionOverlay
.
selectionDelegate
.
textEditingValue
.
selection
,
const
TextSelection
.
collapsed
(
offset:
4
),
);
// Simulate selection change via keyboard and expect handles to disappear.
render
.
onSelectionChanged
(
const
TextSelection
.
collapsed
(
offset:
10
),
render
,
SelectionChangedCause
.
keyboard
);
await
tester
.
pumpAndSettle
();
expect
(
textState
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
textState
.
selectionOverlay
.
selectionDelegate
.
textEditingValue
.
selection
,
const
TextSelection
.
collapsed
(
offset:
10
),
);
});
testWidgets
(
'exposes correct cursor movement semantics'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'exposes correct cursor movement semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
...
@@ -2034,6 +1989,7 @@ void main() {
...
@@ -2034,6 +1989,7 @@ void main() {
// Select the first word. Both handles should be visible.
// Select the first word. Both handles should be visible.
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
showHandles
();
await
tester
.
pump
();
await
tester
.
pump
();
await
verifyVisibility
(
HandlePositionInViewport
.
leftEdge
,
true
,
HandlePositionInViewport
.
within
,
true
);
await
verifyVisibility
(
HandlePositionInViewport
.
leftEdge
,
true
,
HandlePositionInViewport
.
within
,
true
);
...
@@ -2055,6 +2011,7 @@ void main() {
...
@@ -2055,6 +2011,7 @@ void main() {
// Now that the second word has been dragged fully into view, select it.
// Now that the second word has been dragged fully into view, select it.
await
tester
.
tapAt
(
const
Offset
(
80
,
10
));
await
tester
.
tapAt
(
const
Offset
(
80
,
10
));
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
showHandles
();
await
tester
.
pump
();
await
tester
.
pump
();
await
verifyVisibility
(
HandlePositionInViewport
.
within
,
true
,
HandlePositionInViewport
.
within
,
true
);
await
verifyVisibility
(
HandlePositionInViewport
.
within
,
true
,
HandlePositionInViewport
.
within
,
true
);
...
@@ -2099,6 +2056,7 @@ void main() {
...
@@ -2099,6 +2056,7 @@ void main() {
// Select the first word. Both handles should be visible.
// Select the first word. Both handles should be visible.
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
state
.
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
showHandles
();
await
tester
.
pump
();
await
tester
.
pump
();
final
List
<
Positioned
>
positioned
=
final
List
<
Positioned
>
positioned
=
find
.
byType
(
Positioned
).
evaluate
().
map
((
Element
e
)
=>
e
.
widget
).
cast
<
Positioned
>().
toList
();
find
.
byType
(
Positioned
).
evaluate
().
map
((
Element
e
)
=>
e
.
widget
).
cast
<
Positioned
>().
toList
();
...
@@ -2218,6 +2176,7 @@ void main() {
...
@@ -2218,6 +2176,7 @@ void main() {
// Select the first word. Both handles should be visible.
// Select the first word. Both handles should be visible.
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
await
tester
.
tapAt
(
const
Offset
(
20
,
10
));
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
showHandles
();
await
tester
.
pump
();
await
tester
.
pump
();
await
verifyVisibility
(
HandlePositionInViewport
.
leftEdge
,
true
,
HandlePositionInViewport
.
within
,
true
);
await
verifyVisibility
(
HandlePositionInViewport
.
leftEdge
,
true
,
HandlePositionInViewport
.
within
,
true
);
...
@@ -2239,6 +2198,7 @@ void main() {
...
@@ -2239,6 +2198,7 @@ void main() {
// Now that the second word has been dragged fully into view, select it.
// Now that the second word has been dragged fully into view, select it.
await
tester
.
tapAt
(
const
Offset
(
80
,
10
));
await
tester
.
tapAt
(
const
Offset
(
80
,
10
));
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
state
.
showHandles
();
await
tester
.
pump
();
await
tester
.
pump
();
await
verifyVisibility
(
HandlePositionInViewport
.
within
,
true
,
HandlePositionInViewport
.
within
,
true
);
await
verifyVisibility
(
HandlePositionInViewport
.
within
,
true
,
HandlePositionInViewport
.
within
,
true
);
...
...
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