mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit 18ca37542d
.
This commit is contained in:
parent
7ab111ea61
commit
ef5abcc6bf
@ -469,12 +469,6 @@ 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();
|
||||||
@ -507,26 +501,14 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTextState get _editableText => _editableTextKey.currentState;
|
|
||||||
|
|
||||||
void _requestKeyboard() {
|
void _requestKeyboard() {
|
||||||
_editableText?.requestKeyboard();
|
_editableTextKey.currentState?.requestKeyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderEditable get _renderEditable => _editableText.renderEditable;
|
RenderEditable get _renderEditable => _editableTextKey.currentState.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) {
|
||||||
@ -541,8 +523,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
|||||||
from: details.globalPosition,
|
from: details.globalPosition,
|
||||||
cause: SelectionChangedCause.forcePress,
|
cause: SelectionChangedCause.forcePress,
|
||||||
);
|
);
|
||||||
if (_shouldShowSelectionToolbar)
|
_editableTextKey.currentState.showToolbar();
|
||||||
_editableText.showToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSingleTapUp(TapUpDetails details) {
|
void _handleSingleTapUp(TapUpDetails details) {
|
||||||
@ -565,33 +546,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
||||||
if (_shouldShowSelectionToolbar)
|
_editableTextKey.currentState.showToolbar();
|
||||||
_editableText.showToolbar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDoubleTapDown(TapDownDetails details) {
|
void _handleDoubleTapDown(TapDownDetails details) {
|
||||||
_renderEditable.selectWord(cause: SelectionChangedCause.tap);
|
_renderEditable.selectWord(cause: SelectionChangedCause.tap);
|
||||||
if (_shouldShowSelectionToolbar)
|
_editableTextKey.currentState.showToolbar();
|
||||||
_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) {
|
||||||
@ -618,10 +578,7 @@ 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) {
|
||||||
_editableText?.bringIntoView(selection.base);
|
_editableTextKey.currentState?.bringIntoView(selection.base);
|
||||||
}
|
|
||||||
if (_shouldShowSelectionHandles(cause)) {
|
|
||||||
_editableText?.showHandles();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,12 +397,7 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
|
|||||||
longTapDelay: longTapDelay,
|
longTapDelay: longTapDelay,
|
||||||
);
|
);
|
||||||
if (onTapDown != null)
|
if (onTapDown != null)
|
||||||
invokeCallback<void>('onTapDown', () {
|
invokeCallback<void>('onTapDown', () => onTapDown(event.pointer, TapDownDetails(globalPosition: event.position)));
|
||||||
onTapDown(event.pointer, TapDownDetails(
|
|
||||||
globalPosition: event.position,
|
|
||||||
kind: event.kind,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -437,15 +432,7 @@ 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', () {
|
invokeCallback<void>('onLongTapDown', () => onLongTapDown(pointer, TapDownDetails(globalPosition: lastPosition)));
|
||||||
onLongTapDown(
|
|
||||||
pointer,
|
|
||||||
TapDownDetails(
|
|
||||||
globalPosition: lastPosition,
|
|
||||||
kind: getKindForPointer(pointer),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -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 }) : _kindFilter = kind;
|
GestureRecognizer({ this.debugOwner, PointerDeviceKind kind }) : _kind = kind;
|
||||||
|
|
||||||
/// The recognizer's owner.
|
/// The recognizer's owner.
|
||||||
///
|
///
|
||||||
@ -74,11 +74,7 @@ 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 _kindFilter;
|
final PointerDeviceKind _kind;
|
||||||
|
|
||||||
/// 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.
|
||||||
@ -96,7 +92,6 @@ 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 {
|
||||||
@ -128,17 +123,7 @@ 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 _kindFilter == null || _kindFilter == event.kind;
|
return _kind == null || _kind == 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.
|
||||||
@ -426,7 +411,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, () => didExceedDeadlineWithEvent(event));
|
_timer = Timer(deadline, didExceedDeadline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,23 +444,12 @@ 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 or [didExceedDeadlineWithEvent] if you
|
/// You must override this method if you supply a [deadline].
|
||||||
/// 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;
|
||||||
|
@ -19,16 +19,11 @@ 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({
|
TapDownDetails({ this.globalPosition = Offset.zero })
|
||||||
this.globalPosition = Offset.zero,
|
: assert(globalPosition != null);
|
||||||
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
|
||||||
@ -203,15 +198,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didExceedDeadlineWithEvent(PointerDownEvent event) {
|
void didExceedDeadline() {
|
||||||
_checkDown(event.pointer);
|
_checkDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void acceptGesture(int pointer) {
|
void acceptGesture(int pointer) {
|
||||||
super.acceptGesture(pointer);
|
super.acceptGesture(pointer);
|
||||||
if (pointer == primaryPointer) {
|
if (pointer == primaryPointer) {
|
||||||
_checkDown(pointer);
|
_checkDown();
|
||||||
_wonArenaForPrimaryPointer = true;
|
_wonArenaForPrimaryPointer = true;
|
||||||
_checkUp();
|
_checkUp();
|
||||||
}
|
}
|
||||||
@ -229,15 +224,10 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkDown(int pointer) {
|
void _checkDown() {
|
||||||
if (!_sentTapDown) {
|
if (!_sentTapDown) {
|
||||||
if (onTapDown != null)
|
if (onTapDown != null)
|
||||||
invokeCallback<void>('onTapDown', () {
|
invokeCallback<void>('onTapDown', () { onTapDown(TapDownDetails(globalPosition: initialPosition)); });
|
||||||
onTapDown(TapDownDetails(
|
|
||||||
globalPosition: initialPosition,
|
|
||||||
kind: getKindForPointer(pointer),
|
|
||||||
));
|
|
||||||
});
|
|
||||||
_sentTapDown = true;
|
_sentTapDown = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -513,8 +513,6 @@ 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);
|
||||||
@ -607,41 +605,17 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTextState get _editableText => _editableTextKey.currentState;
|
|
||||||
|
|
||||||
void _requestKeyboard() {
|
void _requestKeyboard() {
|
||||||
_editableText?.requestKeyboard();
|
_editableTextKey.currentState?.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?.bringIntoView(selection.base);
|
_editableTextKey.currentState?.bringIntoView(selection.base);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case TargetPlatform.android:
|
case TargetPlatform.android:
|
||||||
@ -650,13 +624,6 @@ 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);
|
||||||
@ -696,16 +663,6 @@ 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) {
|
||||||
@ -714,7 +671,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
from: details.globalPosition,
|
from: details.globalPosition,
|
||||||
cause: SelectionChangedCause.forcePress,
|
cause: SelectionChangedCause.forcePress,
|
||||||
);
|
);
|
||||||
if (_shouldShowSelectionToolbar)
|
|
||||||
_editableTextKey.currentState.showToolbar();
|
_editableTextKey.currentState.showToolbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -782,15 +738,12 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
||||||
print('long tap end');
|
|
||||||
if (_shouldShowSelectionToolbar)
|
|
||||||
_editableTextKey.currentState.showToolbar();
|
_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);
|
||||||
if (_shouldShowSelectionToolbar)
|
|
||||||
_editableTextKey.currentState.showToolbar();
|
_editableTextKey.currentState.showToolbar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -931,7 +884,6 @@ 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,
|
||||||
|
@ -290,7 +290,6 @@ 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,
|
||||||
@ -646,9 +645,6 @@ 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.
|
||||||
///
|
///
|
||||||
@ -1139,9 +1135,10 @@ 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);
|
||||||
}
|
}
|
||||||
@ -1384,22 +1381,6 @@ 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)
|
||||||
|
@ -263,7 +263,6 @@ 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 {
|
||||||
@ -317,14 +316,6 @@ 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);
|
||||||
|
|
||||||
@ -402,26 +393,17 @@ 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 entire overlay including the toolbar and the handles.
|
/// Hides the overlay.
|
||||||
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;
|
||||||
}
|
}
|
||||||
if (_toolbar != null) {
|
_toolbar?.remove();
|
||||||
hideToolbar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hides the toolbar part of the overlay.
|
|
||||||
///
|
|
||||||
/// To hide the whole overlay, see [hide].
|
|
||||||
void hideToolbar() {
|
|
||||||
assert(_toolbar != null);
|
|
||||||
_toolbarController.stop();
|
|
||||||
_toolbar.remove();
|
|
||||||
_toolbar = null;
|
_toolbar = null;
|
||||||
|
|
||||||
|
_toolbarController.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Final cleanup.
|
/// Final cleanup.
|
||||||
@ -436,7 +418,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: onSelectionHandleTapped,
|
onSelectionHandleTapped: _handleSelectionHandleTapped,
|
||||||
layerLink: layerLink,
|
layerLink: layerLink,
|
||||||
renderObject: renderObject,
|
renderObject: renderObject,
|
||||||
selection: _selection,
|
selection: _selection,
|
||||||
@ -494,6 +476,17 @@ 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.
|
||||||
@ -609,7 +602,6 @@ class _TextSelectionHandleOverlayState
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleTap() {
|
void _handleTap() {
|
||||||
if (widget.onSelectionHandleTapped != null)
|
|
||||||
widget.onSelectionHandleTapped();
|
widget.onSelectionHandleTapped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2008,195 +2008,6 @@ 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 {
|
||||||
|
@ -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.didExceedDeadlineWithEvent(event);
|
tap.didExceedDeadline();
|
||||||
expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: possible, sent tap down)'));
|
expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: possible, sent tap down)'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5909,9 +5909,8 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final EditableTextState state =
|
final RenderEditable renderEditable =
|
||||||
tester.state<EditableTextState>(find.byType(EditableText));
|
tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
|
||||||
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);
|
||||||
@ -5962,281 +5961,4 @@ 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -898,6 +898,51 @@ 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);
|
||||||
|
|
||||||
@ -1989,7 +2034,6 @@ 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);
|
||||||
|
|
||||||
@ -2011,7 +2055,6 @@ 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);
|
||||||
|
|
||||||
@ -2056,7 +2099,6 @@ 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();
|
||||||
@ -2176,7 +2218,6 @@ 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);
|
||||||
|
|
||||||
@ -2198,7 +2239,6 @@ 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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user