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
892c8919
Unverified
Commit
892c8919
authored
Feb 12, 2019
by
xster
Committed by
GitHub
Feb 12, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add long-press-drag cursor move support on iOS (#26001)
parent
e44fc135
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1046 additions
and
178 deletions
+1046
-178
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+29
-3
long_press.dart
packages/flutter/lib/src/gestures/long_press.dart
+215
-2
recognizer.dart
packages/flutter/lib/src/gestures/recognizer.dart
+57
-10
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+43
-4
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+21
-6
gesture_detector.dart
packages/flutter/lib/src/widgets/gesture_detector.dart
+64
-1
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+28
-6
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+153
-2
long_press_test.dart
packages/flutter/test/gestures/long_press_test.dart
+256
-134
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+156
-1
gesture_detector_test.dart
packages/flutter/test/widgets/gesture_detector_test.dart
+15
-0
text_selection_test.dart
packages/flutter/test/widgets/text_selection_test.dart
+9
-9
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
892c8919
...
...
@@ -478,8 +478,25 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
_requestKeyboard
();
}
void
_handleSingleLongTapDown
()
{
_renderEditable
.
selectPosition
(
cause:
SelectionChangedCause
.
longPress
);
void
_handleSingleLongTapStart
(
GestureLongPressDragStartDetails
details
)
{
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
}
void
_handleSingleLongTapDragUpdate
(
GestureLongPressDragUpdateDetails
details
)
{
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
}
void
_handleSingleLongTapUp
(
GestureLongPressDragUpDetails
details
)
{
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
);
_editableTextKey
.
currentState
.
showToolbar
();
}
...
...
@@ -488,6 +505,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
_editableTextKey
.
currentState
.
showToolbar
();
}
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
_editableTextKey
.
currentState
?.
bringIntoView
(
selection
.
base
);
}
}
@override
bool
get
wantKeepAlive
=>
_controller
?.
text
?.
isNotEmpty
==
true
;
...
...
@@ -641,6 +664,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
selectionColor:
_kSelectionHighlightColor
,
selectionControls:
cupertinoTextSelectionControls
,
onChanged:
widget
.
onChanged
,
onSelectionChanged:
_handleSelectionChanged
,
onEditingComplete:
widget
.
onEditingComplete
,
onSubmitted:
widget
.
onSubmitted
,
inputFormatters:
formatters
,
...
...
@@ -681,7 +705,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
onForcePressStart:
_handleForcePressStarted
,
onForcePressEnd:
_handleForcePressEnded
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onSingleLongTapStart:
_handleSingleLongTapStart
,
onSingleLongTapDragUpdate:
_handleSingleLongTapDragUpdate
,
onSingleLongTapUp:
_handleSingleLongTapUp
,
onDoubleTapDown:
_handleDoubleTapDown
,
behavior:
HitTestBehavior
.
translucent
,
child:
_addTextDependentAttachments
(
paddedEditable
,
textStyle
),
...
...
packages/flutter/lib/src/gestures/long_press.dart
View file @
892c8919
...
...
@@ -11,9 +11,117 @@ import 'recognizer.dart';
/// same location for a long period of time.
typedef
GestureLongPressCallback
=
void
Function
();
/// Signature for when a pointer stops contacting the screen after a long press gesture was detected.
/// Signature for when a pointer stops contacting the screen after a long press
/// gesture was detected.
typedef
GestureLongPressUpCallback
=
void
Function
();
/// Signature from a [LongPressDragGestureRecognizer] when a pointer has remained
/// in contact with the screen at the same location for a long period of time.
typedef
GestureLongPressDragStartCallback
=
void
Function
(
GestureLongPressDragStartDetails
details
);
/// Signature from a [LongPressDragGestureRecognizer] when a pointer is moving
/// after being held in contact at the same location for a long period of time.
typedef
GestureLongPressDragUpdateCallback
=
void
Function
(
GestureLongPressDragUpdateDetails
details
);
/// Signature from a [LongPressDragGestureRecognizer] after a pointer stops
/// contacting the screen.
///
/// The contact stop position may be different from the contact start position.
typedef
GestureLongPressDragUpCallback
=
void
Function
(
GestureLongPressDragUpDetails
details
);
/// Details for callbacks that use [GestureLongPressDragStartCallback].
///
/// See also:
///
/// * [LongPressDragGestureRecognizer.onLongPressStart], which uses [GestureLongPressDragStartCallback].
/// * [GestureLongPressDragUpdateDetails], the details for [GestureLongPressDragUpdateCallback]
/// * [GestureLongPressDragUpDetails], the details for [GestureLongPressDragUpCallback].
class
GestureLongPressDragStartDetails
{
/// Creates the details for a [GestureLongPressDragStartCallback].
///
/// The [globalPosition] argument must not be null.
const
GestureLongPressDragStartDetails
({
this
.
sourceTimeStamp
,
this
.
globalPosition
=
Offset
.
zero
})
:
assert
(
globalPosition
!=
null
);
/// Recorded timestamp of the source pointer event that triggered the press
/// event.
///
/// Could be null if triggered by proxied events such as accessibility.
///
/// See also:
///
/// * [PointerEvent.synthesized] for details on synthesized pointer events.
final
Duration
sourceTimeStamp
;
/// The global position at which the pointer contacted the screen.
final
Offset
globalPosition
;
}
/// Details for callbacks that use [GestureLongPressDragUpdateCallback].
///
/// See also:
///
/// * [LongPressDragGestureRecognizer.onLongPressDragUpdate], which uses [GestureLongPressDragUpdateCallback].
/// * [GestureLongPressDragUpDetails], the details for [GestureLongPressDragUpCallback]
/// * [GestureLongPressDragStartDetails], the details for [GestureLongPressDragStartCallback].
class
GestureLongPressDragUpdateDetails
{
/// Creates the details for a [GestureLongPressDragUpdateCallback].
///
/// The [globalPosition] and [offsetFromOrigin] arguments must not be null.
const
GestureLongPressDragUpdateDetails
({
this
.
sourceTimeStamp
,
this
.
globalPosition
=
Offset
.
zero
,
this
.
offsetFromOrigin
=
Offset
.
zero
,
})
:
assert
(
globalPosition
!=
null
),
assert
(
offsetFromOrigin
!=
null
);
/// Recorded timestamp of the source pointer event that triggered the press
/// event.
///
/// Could be null if triggered by proxied events such as accessibility.
///
/// See also:
///
/// * [PointerEvent.synthesized] for details on synthesized pointer events.
final
Duration
sourceTimeStamp
;
/// The global position of the pointer when it triggered this update.
final
Offset
globalPosition
;
/// A delta offset from the point where the long press drag initially contacted
/// the screen to the point where the pointer is currently located when
/// this callback is triggered.
final
Offset
offsetFromOrigin
;
}
/// Details for callbacks that use [GestureLongPressDragUpCallback].
///
/// See also:
///
/// * [LongPressDragGestureRecognizer.onLongPressUp], which uses [GestureLongPressDragUpCallback].
/// * [GestureLongPressDragUpdateDetails], the details for [GestureLongPressDragUpdateCallback]
/// * [GestureLongPressDragStartDetails], the details for [GestureLongPressDragStartCallback].
class
GestureLongPressDragUpDetails
{
/// Creates the details for a [GestureLongPressDragUpCallback].
///
/// The [globalPosition] argument must not be null.
const
GestureLongPressDragUpDetails
({
this
.
sourceTimeStamp
,
this
.
globalPosition
=
Offset
.
zero
})
:
assert
(
globalPosition
!=
null
);
/// Recorded timestamp of the source pointer event that triggered the press
/// event.
///
/// Could be null if triggered by proxied events such as accessibility.
///
/// See also:
///
/// * [PointerEvent.synthesized] for details on synthesized pointer events.
final
Duration
sourceTimeStamp
;
/// The global position at which the pointer lifted from the screen.
final
Offset
globalPosition
;
}
/// Recognizes when the user has pressed down at the same location for a long
/// period of time.
class
LongPressGestureRecognizer
extends
PrimaryPointerGestureRecognizer
{
...
...
@@ -28,7 +136,8 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Called when a long press gesture has been recognized.
GestureLongPressCallback
onLongPress
;
/// Called when the pointer stops contacting the screen after the long-press gesture has been recognized.
/// Called when the pointer stops contacting the screen after the long-press
/// gesture has been recognized.
GestureLongPressUpCallback
onLongPressUp
;
@override
...
...
@@ -58,3 +167,107 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
@override
String
get
debugDescription
=>
'long press'
;
}
/// Recognizes long presses that can be subsequently dragged around.
///
/// Similar to a [LongPressGestureRecognizer] where a press has to be held down
/// at the same location for a long period of time. However drag events that
/// occur after the long-press hold threshold has past will not cancel the
/// gesture. The [onLongPressDragUpdate] callback will be called until an up
/// event occurs.
///
/// See also:
///
/// * [LongPressGestureRecognizer], which cancels its gesture if a drag event
/// occurs at any point during the long-press.
class
LongPressDragGestureRecognizer
extends
PrimaryPointerGestureRecognizer
{
/// Creates a long-press-drag gesture recognizer.
///
/// Consider assigning the [onLongPressStart], [onLongPressDragUpdate] and
/// the [onLongPressUp] callbacks after creating this object.
LongPressDragGestureRecognizer
({
Object
debugOwner
})
:
super
(
deadline:
kLongPressTimeout
,
// Since it's a drag gesture, no travel distance will cause it to get
// rejected after the long-press is accepted.
postAcceptSlopTolerance:
null
,
debugOwner:
debugOwner
,
);
bool
_longPressAccepted
=
false
;
Offset
_longPressOrigin
;
Duration
_longPressStartTimestamp
;
/// Called when a long press gesture has been recognized.
GestureLongPressDragStartCallback
onLongPressStart
;
/// Called as the primary pointer is dragged after the long press.
GestureLongPressDragUpdateCallback
onLongPressDragUpdate
;
/// Called when the pointer stops contacting the screen after the
/// long-press gesture has been recognized.
GestureLongPressDragUpCallback
onLongPressUp
;
@override
void
didExceedDeadline
()
{
resolve
(
GestureDisposition
.
accepted
);
_longPressAccepted
=
true
;
super
.
acceptGesture
(
primaryPointer
);
if
(
onLongPressStart
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressStart'
,
()
{
onLongPressStart
(
GestureLongPressDragStartDetails
(
sourceTimeStamp:
_longPressStartTimestamp
,
globalPosition:
_longPressOrigin
,
));
});
}
}
@override
void
handlePrimaryPointer
(
PointerEvent
event
)
{
if
(
event
is
PointerUpEvent
)
{
if
(
_longPressAccepted
==
true
&&
onLongPressUp
!=
null
)
{
_longPressAccepted
=
false
;
invokeCallback
<
void
>(
'onLongPressUp'
,
()
{
onLongPressUp
(
GestureLongPressDragUpDetails
(
sourceTimeStamp:
event
.
timeStamp
,
globalPosition:
event
.
position
,
));
});
}
else
{
resolve
(
GestureDisposition
.
rejected
);
}
}
else
if
(
event
is
PointerDownEvent
)
{
// The first touch.
_longPressAccepted
=
false
;
_longPressStartTimestamp
=
event
.
timeStamp
;
_longPressOrigin
=
event
.
position
;
}
else
if
(
event
is
PointerMoveEvent
&&
_longPressAccepted
&&
onLongPressDragUpdate
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressDrag'
,
()
{
onLongPressDragUpdate
(
GestureLongPressDragUpdateDetails
(
sourceTimeStamp:
event
.
timeStamp
,
globalPosition:
event
.
position
,
offsetFromOrigin:
event
.
position
-
_longPressOrigin
,
));
});
}
}
@override
void
acceptGesture
(
int
pointer
)
{
// Winning the arena isn't important here since it may happen from a sweep.
// Explicitly exceeding the deadline puts the gesture in accepted state.
}
@override
void
didStopTrackingLastPointer
(
int
pointer
)
{
_longPressAccepted
=
false
;
_longPressOrigin
=
null
;
_longPressStartTimestamp
=
null
;
super
.
didStopTrackingLastPointer
(
pointer
);
}
@override
String
get
debugDescription
=>
'long press drag'
;
}
packages/flutter/lib/src/gestures/recognizer.dart
View file @
892c8919
...
...
@@ -262,10 +262,10 @@ abstract class OneSequenceGestureRecognizer extends GestureRecognizer {
/// The possible states of a [PrimaryPointerGestureRecognizer].
///
/// The recognizer advances from [ready] to [possible] when starts tracking a
/// primary pointer.
When the primary pointer is resolve (either accepted or
///
or rejected), the recognizers advances to [defunct]. Once the recognizer
///
has stopped tracking any remaining pointers, the recognizer returns to
/// [ready].
/// primary pointer.
The recognizer moves to [accepted] when resolved to win the
///
gesture arena. It may then move to [defunct] from [accepted] or go to
///
[defunct] directly when rejected. Once the recognizer has stopped tracking
///
any remaining pointers, the recognizer returns to
[ready].
enum
GestureRecognizerState
{
/// The recognizer is ready to start recognizing a gesture.
ready
,
...
...
@@ -275,6 +275,9 @@ enum GestureRecognizerState {
/// been accepted definitively.
possible
,
/// The gesture has been definitively accepted by the recognizer.
accepted
,
/// Further pointer events cannot cause this recognizer to recognize the
/// gesture until the recognizer returns to the [ready] state (typically when
/// all the pointers the recognizer is tracking are removed from the screen).
...
...
@@ -284,18 +287,43 @@ enum GestureRecognizerState {
/// A base class for gesture recognizers that track a single primary pointer.
///
/// Gestures based on this class will reject the gesture if the primary pointer
/// travels beyond [kTouchSlop] pixels from the original contact point.
/// travels beyond [preAcceptSlopTolerance] pixels from the original contact
/// point before the gesture is accepted or beyond [postAcceptSlopTolerance]
/// from where the pointer was after the gesture was accepted.
abstract
class
PrimaryPointerGestureRecognizer
extends
OneSequenceGestureRecognizer
{
/// Initializes the [deadline] field during construction of subclasses.
PrimaryPointerGestureRecognizer
({
this
.
deadline
,
this
.
preAcceptSlopTolerance
=
kTouchSlop
,
this
.
postAcceptSlopTolerance
=
kTouchSlop
,
Object
debugOwner
,
})
:
super
(
debugOwner:
debugOwner
);
})
:
assert
(
preAcceptSlopTolerance
==
null
||
preAcceptSlopTolerance
>=
0
,
'The preAcceptSlopTolerance must be positive or null'
,
),
assert
(
postAcceptSlopTolerance
==
null
||
postAcceptSlopTolerance
>=
0
,
'The postAcceptSlopTolerance must be positive or null'
,
),
super
(
debugOwner:
debugOwner
);
/// If non-null, the recognizer will call [didExceedDeadline] after this
/// amount of time has elapsed since starting to track the primary pointer.
final
Duration
deadline
;
/// The maximum distance in logical pixels the gesture is allowed to drift
/// from the initial touch down position before the gesture is accepted.
///
/// Drifting past the allowed slop amount causes the gesture to be rejected.
final
double
preAcceptSlopTolerance
;
/// The maximum distance in logical pixels the gesture is allowed to drift
/// after the gesture has been accepted.
///
/// Drifting past the allowed slop amount causes the gesture to be rejected,
/// even after being accepted.
final
double
postAcceptSlopTolerance
;
/// The current state of the recognizer.
///
/// See [GestureRecognizerState] for a description of the states.
...
...
@@ -324,9 +352,17 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
@override
void
handleEvent
(
PointerEvent
event
)
{
assert
(
state
!=
GestureRecognizerState
.
ready
);
if
(
state
==
GestureRecognizerState
.
possible
&&
event
.
pointer
==
primaryPointer
)
{
// TODO(abarth): Maybe factor the slop handling out into a separate class?
if
(
event
is
PointerMoveEvent
&&
_getDistance
(
event
)
>
kTouchSlop
)
{
if
(
event
.
pointer
==
primaryPointer
)
{
final
bool
isPreAcceptSlopPastTolerance
=
state
==
GestureRecognizerState
.
possible
&&
preAcceptSlopTolerance
!=
null
&&
_getDistance
(
event
)
>
preAcceptSlopTolerance
;
final
bool
isPostAcceptSlopPastTolerance
=
state
==
GestureRecognizerState
.
accepted
&&
postAcceptSlopTolerance
!=
null
&&
_getDistance
(
event
)
>
postAcceptSlopTolerance
;
if
(
event
is
PointerMoveEvent
&&
(
isPreAcceptSlopPastTolerance
||
isPostAcceptSlopPastTolerance
))
{
resolve
(
GestureDisposition
.
rejected
);
stopTrackingPointer
(
primaryPointer
);
}
else
{
...
...
@@ -349,8 +385,19 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
}
@override
void
rejectGesture
(
int
pointer
)
{
void
acceptGesture
(
int
pointer
)
{
// Ignore state 'ready' here because that would happen if this recognizer
// won by a sweep.
if
(
pointer
==
primaryPointer
&&
state
==
GestureRecognizerState
.
possible
)
{
state
=
GestureRecognizerState
.
accepted
;
}
}
@override
void
rejectGesture
(
int
pointer
)
{
if
(
pointer
==
primaryPointer
&&
(
state
==
GestureRecognizerState
.
possible
||
state
==
GestureRecognizerState
.
accepted
))
{
_stopTimer
();
state
=
GestureRecognizerState
.
defunct
;
}
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
892c8919
...
...
@@ -566,6 +566,15 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_editableTextKey
.
currentState
?.
requestKeyboard
();
}
void
_handleSelectionChanged
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
// iOS cursor doesn't move via a selection handle. The scroll happens
// directly from new text selection changes.
if
(
Theme
.
of
(
context
).
platform
==
TargetPlatform
.
iOS
&&
cause
==
SelectionChangedCause
.
longPress
)
{
_editableTextKey
.
currentState
?.
bringIntoView
(
selection
.
base
);
}
}
InteractiveInkFeature
_createInkFeature
(
TapDownDetails
details
)
{
final
MaterialInkController
inkController
=
Material
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
...
...
@@ -639,11 +648,14 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_cancelCurrentSplash
();
}
void
_handleSingleLongTap
Down
(
)
{
void
_handleSingleLongTap
Start
(
GestureLongPressDragStartDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
_renderEditable
.
selectPosition
(
cause:
SelectionChangedCause
.
longPress
);
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
...
...
@@ -651,11 +663,35 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
Feedback
.
forLongPress
(
context
);
break
;
}
_editableTextKey
.
currentState
.
showToolbar
();
}
_confirmCurrentSplash
();
}
void
_handleSingleLongTapDragUpdate
(
GestureLongPressDragUpdateDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
_renderEditable
.
selectWordsInRange
(
from:
details
.
globalPosition
-
details
.
offsetFromOrigin
,
to:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
break
;
}
}
}
void
_handleSingleLongTapUp
(
GestureLongPressDragUpDetails
details
)
{
_editableTextKey
.
currentState
.
showToolbar
();
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
...
...
@@ -774,6 +810,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
selectionColor:
themeData
.
textSelectionColor
,
selectionControls:
widget
.
selectionEnabled
?
textSelectionControls
:
null
,
onChanged:
widget
.
onChanged
,
onSelectionChanged:
_handleSelectionChanged
,
onEditingComplete:
widget
.
onEditingComplete
,
onSubmitted:
widget
.
onSubmitted
,
inputFormatters:
formatters
,
...
...
@@ -822,7 +859,9 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onForcePressStart:
forcePressEnabled
?
_handleForcePressStarted
:
null
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onSingleLongTapStart:
_handleSingleLongTapStart
,
onSingleLongTapDragUpdate:
_handleSingleLongTapDragUpdate
,
onSingleLongTapUp:
_handleSingleLongTapUp
,
onDoubleTapDown:
_handleDoubleTapDown
,
behavior:
HitTestBehavior
.
translucent
,
child:
child
,
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
892c8919
...
...
@@ -1191,7 +1191,7 @@ class RenderEditable extends RenderBox {
/// When [ignorePointer] is true, an ancestor widget must respond to tap
/// down events by calling this method.
void
handleTapDown
(
TapDownDetails
details
)
{
_lastTapDownPosition
=
details
.
globalPosition
+
-
_paintOffset
;
_lastTapDownPosition
=
details
.
globalPosition
-
_paintOffset
;
}
void
_handleTapDown
(
TapDownDetails
details
)
{
assert
(!
ignorePointer
);
...
...
@@ -1247,12 +1247,27 @@ class RenderEditable extends RenderBox {
/// programmatically manipulate its `value` or `selection` directly.
/// {@endtemplate}
void
selectPosition
({
@required
SelectionChangedCause
cause
})
{
selectPositionAt
(
from:
_lastTapDownPosition
,
cause:
cause
);
}
/// Select text between the global positions [from] and [to].
void
selectPositionAt
({
@required
Offset
from
,
Offset
to
,
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
TextSelection
.
fromPosition
(
position
),
this
,
cause
);
final
TextPosition
fromPosition
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
from
-
_paintOffset
));
final
TextPosition
toPosition
=
to
==
null
?
null
:
_textPainter
.
getPositionForOffset
(
globalToLocal
(
to
-
_paintOffset
));
onSelectionChanged
(
TextSelection
(
baseOffset:
fromPosition
.
offset
,
extentOffset:
toPosition
?.
offset
??
fromPosition
.
offset
,
affinity:
fromPosition
.
affinity
,
),
this
,
cause
,
);
}
}
...
...
@@ -1273,10 +1288,10 @@ class RenderEditable extends RenderBox {
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
firstPosition
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
from
+
-
_paintOffset
));
final
TextPosition
firstPosition
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
from
-
_paintOffset
));
final
TextSelection
firstWord
=
_selectWordAtOffset
(
firstPosition
);
final
TextSelection
lastWord
=
to
==
null
?
firstWord
:
_selectWordAtOffset
(
_textPainter
.
getPositionForOffset
(
globalToLocal
(
to
+
-
_paintOffset
)));
firstWord
:
_selectWordAtOffset
(
_textPainter
.
getPositionForOffset
(
globalToLocal
(
to
-
_paintOffset
)));
onSelectionChanged
(
TextSelection
(
...
...
packages/flutter/lib/src/widgets/gesture_detector.dart
View file @
892c8919
...
...
@@ -19,6 +19,10 @@ export 'package:flutter/gestures.dart' show
GestureTapCallback
,
GestureTapCancelCallback
,
GestureLongPressCallback
,
GestureLongPressUpCallback
,
GestureLongPressDragStartCallback
,
GestureLongPressDragUpdateCallback
,
GestureLongPressDragUpCallback
,
GestureDragDownCallback
,
GestureDragStartCallback
,
GestureDragUpdateCallback
,
...
...
@@ -31,6 +35,9 @@ export 'package:flutter/gestures.dart' show
GestureForcePressPeakCallback
,
GestureForcePressEndCallback
,
GestureForcePressUpdateCallback
,
GestureLongPressDragStartDetails
,
GestureLongPressDragUpdateDetails
,
GestureLongPressDragUpDetails
,
ScaleStartDetails
,
ScaleUpdateDetails
,
ScaleEndDetails
,
...
...
@@ -150,6 +157,10 @@ class GestureDetector extends StatelessWidget {
/// because a combination of a horizontal and vertical drag is a pan. Simply
/// use the pan callbacks instead.
///
/// Long press and long press drag callbacks cannot be used simultaneously
/// since they overlap. A long press cannot be subsequently dragged while a
/// long press drag can be dragged after a long press.
///
/// By default, gesture detectors contribute semantic information to the tree
/// that is used by assistive technology.
GestureDetector
({
...
...
@@ -162,6 +173,9 @@ class GestureDetector extends StatelessWidget {
this
.
onDoubleTap
,
this
.
onLongPress
,
this
.
onLongPressUp
,
this
.
onLongPressDragStart
,
this
.
onLongPressDragUpdate
,
this
.
onLongPressDragUp
,
this
.
onVerticalDragDown
,
this
.
onVerticalDragStart
,
this
.
onVerticalDragUpdate
,
...
...
@@ -194,6 +208,8 @@ class GestureDetector extends StatelessWidget {
final
bool
haveHorizontalDrag
=
onHorizontalDragStart
!=
null
||
onHorizontalDragUpdate
!=
null
||
onHorizontalDragEnd
!=
null
;
final
bool
havePan
=
onPanStart
!=
null
||
onPanUpdate
!=
null
||
onPanEnd
!=
null
;
final
bool
haveScale
=
onScaleStart
!=
null
||
onScaleUpdate
!=
null
||
onScaleEnd
!=
null
;
final
bool
haveLongPress
=
onLongPress
!=
null
||
onLongPressUp
!=
null
;
final
bool
haveLongPressDrag
=
onLongPressDragStart
!=
null
||
onLongPressDragUpdate
!=
null
||
onLongPressDragUp
!=
null
;
if
(
havePan
||
haveScale
)
{
if
(
havePan
&&
haveScale
)
{
throw
FlutterError
(
...
...
@@ -210,6 +226,15 @@ class GestureDetector extends StatelessWidget {
);
}
}
if
(
haveLongPress
&&
haveLongPressDrag
)
{
throw
FlutterError
(
'Incorrect GestureDetector arguments.
\n
'
'Having both a long press and a long press drag recognizer is '
'redundant as the long press drag is a superset of long press. '
'Except long press drag allows for drags after the long press is '
'triggered.'
);
}
return
true
;
}()),
super
(
key:
key
);
...
...
@@ -258,11 +283,37 @@ class GestureDetector extends StatelessWidget {
/// A pointer has remained in contact with the screen at the same location for
/// a long period of time.
///
/// The long press drag callbacks [onLongPressDragStart], [onLongPressDragUpdate]
/// and [onLongPressDragUp] cannot be set while this callback is set.
final
GestureLongPressCallback
onLongPress
;
/// A pointer that has triggered a long-press has stopped contacting the screen.
///
/// The long press drag callbacks [onLongPressDragStart], [onLongPressDragUpdate]
/// and [onLongPressDragUp] cannot be set while this callback is set.
final
GestureLongPressUpCallback
onLongPressUp
;
/// A pointer has remained in contact with the screen at the same location for
/// a long period of time and can subsequently be dragged.
///
/// The non-drag long press callbacks [onLongPress] and [onLongPressUp] cannot
/// be set while this callback is set.
final
GestureLongPressDragStartCallback
onLongPressDragStart
;
/// A pointer has been drag-moved after a long press.
///
/// The non-drag long press callbacks [onLongPress] and [onLongPressUp] cannot
/// be set while this callback is set.
final
GestureLongPressDragUpdateCallback
onLongPressDragUpdate
;
/// A pointer that has triggered a long press has stopped contacting the screen
/// regardless of whether the pointer is dragged after the long press.
///
/// The non-drag long press callbacks [onLongPress] and [onLongPressUp] cannot
/// be set while this callback is set.
final
GestureLongPressDragUpCallback
onLongPressDragUp
;
/// A pointer has contacted the screen and might begin to move vertically.
final
GestureDragDownCallback
onVerticalDragDown
;
...
...
@@ -422,7 +473,7 @@ class GestureDetector extends StatelessWidget {
);
}
if
(
onLongPress
!=
null
||
onLongPressUp
!=
null
)
{
if
(
onLongPress
!=
null
||
onLongPressUp
!=
null
)
{
gestures
[
LongPressGestureRecognizer
]
=
GestureRecognizerFactoryWithHandlers
<
LongPressGestureRecognizer
>(
()
=>
LongPressGestureRecognizer
(
debugOwner:
this
),
(
LongPressGestureRecognizer
instance
)
{
...
...
@@ -433,6 +484,18 @@ class GestureDetector extends StatelessWidget {
);
}
if
(
onLongPressDragStart
!=
null
||
onLongPressDragUpdate
!=
null
||
onLongPressDragUp
!=
null
)
{
gestures
[
LongPressDragGestureRecognizer
]
=
GestureRecognizerFactoryWithHandlers
<
LongPressDragGestureRecognizer
>(
()
=>
LongPressDragGestureRecognizer
(
debugOwner:
this
),
(
LongPressDragGestureRecognizer
instance
)
{
instance
..
onLongPressStart
=
onLongPressDragStart
..
onLongPressDragUpdate
=
onLongPressDragUpdate
..
onLongPressUp
=
onLongPressDragUp
;
},
);
}
if
(
onVerticalDragDown
!=
null
||
onVerticalDragStart
!=
null
||
onVerticalDragUpdate
!=
null
||
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
892c8919
...
...
@@ -617,7 +617,9 @@ class TextSelectionGestureDetector extends StatefulWidget {
this
.
onForcePressEnd
,
this
.
onSingleTapUp
,
this
.
onSingleTapCancel
,
this
.
onSingleLongTapDown
,
this
.
onSingleLongTapStart
,
this
.
onSingleLongTapDragUpdate
,
this
.
onSingleLongTapUp
,
this
.
onDoubleTapDown
,
this
.
behavior
,
@required
this
.
child
,
...
...
@@ -651,7 +653,13 @@ class TextSelectionGestureDetector extends StatefulWidget {
/// Called for a single long tap that's sustained for longer than
/// [kLongPressTimeout] but not necessarily lifted. Not called for a
/// double-tap-hold, which calls [onDoubleTapDown] instead.
final
GestureLongPressCallback
onSingleLongTapDown
;
final
GestureLongPressDragStartCallback
onSingleLongTapStart
;
/// Called after [onSingleLongTapStart] when the pointer is dragged.
final
GestureLongPressDragUpdateCallback
onSingleLongTapDragUpdate
;
/// Called after [onSingleLongTapStart] when the pointer is lifted.
final
GestureLongPressDragUpCallback
onSingleLongTapUp
;
/// Called after a momentary hold or a short tap that is close in space and
/// time (within [kDoubleTapTimeout]) to a previous short tap.
...
...
@@ -735,9 +743,21 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
widget
.
onForcePressEnd
(
details
);
}
void
_handleLongPress
()
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapDown
!=
null
)
{
widget
.
onSingleLongTapDown
();
void
_handleLongDragStart
(
GestureLongPressDragStartDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapStart
!=
null
)
{
widget
.
onSingleLongTapStart
(
details
);
}
}
void
_handleLongDragUpdate
(
GestureLongPressDragUpdateDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapDragUpdate
!=
null
)
{
widget
.
onSingleLongTapDragUpdate
(
details
);
}
}
void
_handleLongDragUp
(
GestureLongPressDragUpDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapUp
!=
null
)
{
widget
.
onSingleLongTapUp
(
details
);
}
_isDoubleTap
=
false
;
}
...
...
@@ -765,7 +785,9 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
onForcePressStart:
widget
.
onForcePressStart
!=
null
?
_forcePressStarted
:
null
,
onForcePressEnd:
widget
.
onForcePressEnd
!=
null
?
_forcePressEnded
:
null
,
onTapCancel:
_handleTapCancel
,
onLongPress:
_handleLongPress
,
onLongPressDragStart:
_handleLongDragStart
,
onLongPressDragUpdate:
_handleLongDragUpdate
,
onLongPressDragUp:
_handleLongDragUp
,
excludeFromSemantics:
true
,
behavior:
widget
.
behavior
,
child:
widget
.
child
,
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
892c8919
...
...
@@ -1046,7 +1046,7 @@ void main() {
);
testWidgets
(
'long press tap
is not
a double tap'
,
'long press tap
cannot initiate
a double tap'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
...
...
@@ -1076,11 +1076,162 @@ void main() {
const
TextSelection
.
collapsed
(
offset:
7
,
affinity:
TextAffinity
.
upstream
),
);
//
Collapsed toolbar shows 2 buttons
.
//
The toolbar from the long press is now dismissed by the second tap
.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
},
);
testWidgets
(
'long press drag moves the cursor under the drag and shows toolbar on lift'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Long press on iOS shows collapsed selection cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
upstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
50
,
0
));
await
tester
.
pump
();
// The selection position is now moved with the drag.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
6
,
affinity:
TextAffinity
.
upstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
50
,
0
));
await
tester
.
pump
();
// The selection position is now moved with the drag.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
upstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
// The selection isn't affected by the gesture lift.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
upstream
),
);
// The toolbar now shows up.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
},
);
testWidgets
(
'long press drag can edge scroll'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure Angrignon Peel Côte-des-Neiges'
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
maxLines:
1
,
),
),
),
);
final
RenderEditable
renderEditable
=
tester
.
renderObject
<
RenderEditable
>(
find
.
byElementPredicate
((
Element
element
)
=>
element
.
renderObject
is
RenderEditable
)
);
List
<
TextSelectionPoint
>
lastCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
66
),
// Last character's position.
);
expect
(
lastCharEndpoint
.
length
,
1
);
// Just testing the test and making sure that the last character is off
// the right side of the screen.
expect
(
lastCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(
1094.73486328125
));
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
300
,
5
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
18
,
affinity:
TextAffinity
.
upstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
600
,
0
));
// To the edge of the screen basically.
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
54
,
affinity:
TextAffinity
.
upstream
),
);
// Keep moving out.
await
gesture
.
moveBy
(
const
Offset
(
1
,
0
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
61
,
affinity:
TextAffinity
.
upstream
),
);
await
gesture
.
moveBy
(
const
Offset
(
1
,
0
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
66
,
affinity:
TextAffinity
.
upstream
),
);
// We're at the edge now.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
// The selection isn't affected by the gesture lift.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
66
,
affinity:
TextAffinity
.
upstream
),
);
// The toolbar now shows up.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
lastCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
66
),
// Last character's position.
);
expect
(
lastCharEndpoint
.
length
,
1
);
// The last character is now on screen.
expect
(
lastCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(
786.73486328125
));
final
List
<
TextSelectionPoint
>
firstCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
0
),
// First character's position.
);
expect
(
firstCharEndpoint
.
length
,
1
);
// The first character is now offscreen to the left.
expect
(
firstCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(-
308.20499999821186
));
});
testWidgets
(
'long tap after a double tap select is not affected'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/gestures/long_press_test.dart
View file @
892c8919
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/text_field_test.dart
View file @
892c8919
...
...
@@ -4143,7 +4143,7 @@ void main() {
);
testWidgets
(
'long press tap
is not
a double tap (iOS)'
,
'long press tap
cannot initiate
a double tap (iOS)'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
...
...
@@ -4181,6 +4181,161 @@ void main() {
},
);
testWidgets
(
'long press drag moves the cursor under the drag and shows toolbar on lift (iOS)'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
platform:
TargetPlatform
.
iOS
),
home:
Material
(
child:
Center
(
child:
TextField
(
controller:
controller
,
),
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
50.0
,
5.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
// Long press on iOS shows collapsed selection cursor.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
3
,
affinity:
TextAffinity
.
downstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
50
,
0
));
await
tester
.
pump
();
// The selection position is now moved with the drag.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
6
,
affinity:
TextAffinity
.
downstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
50
,
0
));
await
tester
.
pump
();
// The selection position is now moved with the drag.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
downstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
// The selection isn't affected by the gesture lift.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
,
affinity:
TextAffinity
.
downstream
),
);
// The toolbar now shows up.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
},
);
testWidgets
(
'long press drag can edge scroll (iOS)'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure Angrignon Peel Côte-des-Neiges'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
(
platform:
TargetPlatform
.
iOS
),
home:
Material
(
child:
Center
(
child:
TextField
(
controller:
controller
,
maxLines:
1
,
),
),
),
),
);
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
List
<
TextSelectionPoint
>
lastCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
66
),
// Last character's position.
);
expect
(
lastCharEndpoint
.
length
,
1
);
// Just testing the test and making sure that the last character is off
// the right side of the screen.
expect
(
lastCharEndpoint
[
0
].
point
.
dx
,
1056
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
TextField
));
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
300
,
5
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
500
));
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
19
,
affinity:
TextAffinity
.
upstream
),
);
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
moveBy
(
const
Offset
(
600
,
0
));
// To the edge of the screen basically.
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
56
,
affinity:
TextAffinity
.
downstream
),
);
// Keep moving out.
await
gesture
.
moveBy
(
const
Offset
(
1
,
0
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
62
,
affinity:
TextAffinity
.
downstream
),
);
await
gesture
.
moveBy
(
const
Offset
(
1
,
0
));
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
66
,
affinity:
TextAffinity
.
upstream
),
);
// We're at the edge now.
expect
(
find
.
byType
(
CupertinoButton
),
findsNothing
);
await
gesture
.
up
();
await
tester
.
pump
();
// The selection isn't affected by the gesture lift.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
66
,
affinity:
TextAffinity
.
upstream
),
);
// The toolbar now shows up.
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
2
));
lastCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
66
),
// Last character's position.
);
expect
(
lastCharEndpoint
.
length
,
1
);
// The last character is now on screen.
expect
(
lastCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(
797.3333129882812
));
final
List
<
TextSelectionPoint
>
firstCharEndpoint
=
renderEditable
.
getEndpointsForSelection
(
const
TextSelection
.
collapsed
(
offset:
0
),
// First character's position.
);
expect
(
firstCharEndpoint
.
length
,
1
);
// The first character is now offscreen to the left.
expect
(
firstCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(-
258.66668701171875
));
});
testWidgets
(
'long tap after a double tap select is not affected (iOS)'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/widgets/gesture_detector_test.dart
View file @
892c8919
...
...
@@ -486,4 +486,19 @@ void main() {
expect
(
horizontalDragStart
,
1
);
expect
(
forcePressStart
,
0
);
});
testWidgets
(
'Cannot set both a long press and a long press drag callback'
,
(
WidgetTester
tester
)
async
{
try
{
GestureDetector
(
onLongPress:
()
{},
onLongPressDragUpdate:
(
GestureLongPressDragUpdateDetails
details
)
{},
child:
Container
(
color:
const
Color
(
0xFF00FF00
),
),
);
throw
'setting long press and long press drag should throw'
;
}
on
FlutterError
catch
(
_
)
{
// Should throw.
}
});
}
packages/flutter/test/widgets/text_selection_test.dart
View file @
892c8919
...
...
@@ -9,7 +9,7 @@ void main() {
int
tapCount
;
int
singleTapUpCount
;
int
singleTapCancelCount
;
int
singleLongTap
Down
Count
;
int
singleLongTap
Start
Count
;
int
doubleTapDownCount
;
int
forcePressStartCount
;
int
forcePressEndCount
;
...
...
@@ -17,7 +17,7 @@ void main() {
void
_handleTapDown
(
TapDownDetails
details
)
{
tapCount
++;
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
singleTapUpCount
++;
}
void
_handleSingleTapCancel
()
{
singleTapCancelCount
++;
}
void
_handleSingleLongTap
Down
()
{
singleLongTapDown
Count
++;
}
void
_handleSingleLongTap
Start
(
GestureLongPressDragStartDetails
details
)
{
singleLongTapStart
Count
++;
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
doubleTapDownCount
++;
}
void
_handleForcePressStart
(
ForcePressDetails
details
)
{
forcePressStartCount
++;
}
void
_handleForcePressEnd
(
ForcePressDetails
details
)
{
forcePressEndCount
++;
}
...
...
@@ -26,7 +26,7 @@ void main() {
tapCount
=
0
;
singleTapUpCount
=
0
;
singleTapCancelCount
=
0
;
singleLongTap
Down
Count
=
0
;
singleLongTap
Start
Count
=
0
;
doubleTapDownCount
=
0
;
forcePressStartCount
=
0
;
forcePressEndCount
=
0
;
...
...
@@ -39,7 +39,7 @@ void main() {
onTapDown:
_handleTapDown
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTap
Down:
_handleSingleLongTapDown
,
onSingleLongTap
Start:
_handleSingleLongTapStart
,
onDoubleTapDown:
_handleDoubleTapDown
,
onForcePressStart:
_handleForcePressStart
,
onForcePressEnd:
_handleForcePressEnd
,
...
...
@@ -106,7 +106,7 @@ void main() {
expect
(
singleTapCancelCount
,
0
);
expect
(
doubleTapDownCount
,
1
);
// The double tap down hold supersedes the single tap down.
expect
(
singleLongTap
Down
Count
,
0
);
expect
(
singleLongTap
Start
Count
,
0
);
await
gesture
.
up
();
// Nothing else happens on up.
...
...
@@ -114,7 +114,7 @@ void main() {
expect
(
tapCount
,
2
);
expect
(
singleTapCancelCount
,
0
);
expect
(
doubleTapDownCount
,
1
);
expect
(
singleLongTap
Down
Count
,
0
);
expect
(
singleLongTap
Start
Count
,
0
);
});
testWidgets
(
'a very quick swipe is just a canceled tap'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -127,7 +127,7 @@ void main() {
expect
(
tapCount
,
0
);
expect
(
singleTapCancelCount
,
1
);
expect
(
doubleTapDownCount
,
0
);
expect
(
singleLongTap
Down
Count
,
0
);
expect
(
singleLongTap
Start
Count
,
0
);
await
gesture
.
up
();
// Nothing else happens on up.
...
...
@@ -135,7 +135,7 @@ void main() {
expect
(
tapCount
,
0
);
expect
(
singleTapCancelCount
,
1
);
expect
(
doubleTapDownCount
,
0
);
expect
(
singleLongTap
Down
Count
,
0
);
expect
(
singleLongTap
Start
Count
,
0
);
});
testWidgets
(
'a slower swipe has a tap down and a canceled tap'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -148,7 +148,7 @@ void main() {
expect
(
tapCount
,
1
);
expect
(
singleTapCancelCount
,
1
);
expect
(
doubleTapDownCount
,
0
);
expect
(
singleLongTap
Down
Count
,
0
);
expect
(
singleLongTap
Start
Count
,
0
);
});
testWidgets
(
'a force press intiates a force press'
,
(
WidgetTester
tester
)
async
{
...
...
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