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
ec00e974
Unverified
Commit
ec00e974
authored
Feb 25, 2019
by
xster
Committed by
GitHub
Feb 25, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add long-press-move support for text fields 2 (#28242)
parent
47724f97
Changes
12
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1075 additions
and
192 deletions
+1075
-192
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+25
-3
long_press.dart
packages/flutter/lib/src/gestures/long_press.dart
+176
-9
recognizer.dart
packages/flutter/lib/src/gestures/recognizer.dart
+59
-10
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+49
-4
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+24
-7
gesture_detector.dart
packages/flutter/lib/src/widgets/gesture_detector.dart
+52
-3
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
+154
-2
long_press_test.dart
packages/flutter/test/gestures/long_press_test.dart
+254
-134
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+165
-5
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+80
-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 @
ec00e974
...
...
@@ -482,8 +482,21 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
_requestKeyboard
();
}
void
_handleSingleLongTapDown
()
{
_renderEditable
.
selectPosition
(
cause:
SelectionChangedCause
.
longPress
);
void
_handleSingleLongTapStart
(
LongPressStartDetails
details
)
{
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
}
void
_handleSingleLongTapMoveUpdate
(
LongPressMoveUpdateDetails
details
)
{
_renderEditable
.
selectPositionAt
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
}
void
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
_editableTextKey
.
currentState
.
showToolbar
();
}
...
...
@@ -492,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
;
...
...
@@ -646,6 +665,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
,
...
...
@@ -686,7 +706,9 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
onForcePressStart:
_handleForcePressStarted
,
onForcePressEnd:
_handleForcePressEnded
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onSingleLongTapStart:
_handleSingleLongTapStart
,
onSingleLongTapMoveUpdate:
_handleSingleLongTapMoveUpdate
,
onSingleLongTapEnd:
_handleSingleLongTapEnd
,
onDoubleTapDown:
_handleDoubleTapDown
,
behavior:
HitTestBehavior
.
translucent
,
child:
_addTextDependentAttachments
(
paddedEditable
,
textStyle
),
...
...
packages/flutter/lib/src/gestures/long_press.dart
View file @
ec00e974
...
...
@@ -7,54 +7,221 @@ import 'constants.dart';
import
'events.dart'
;
import
'recognizer.dart'
;
/// Signature for when a pointer has remained in contact with the screen at the
/// Callback signature for [LongPressGestureRecognizer.onLongPress].
///
/// Called when a pointer has remained in contact with the screen at the
/// 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.
/// Callback signature for [LongPressGestureRecognizer.onLongPressUp].
///
/// Called when a pointer stops contacting the screen after a long press
/// gesture was detected.
typedef
GestureLongPressUpCallback
=
void
Function
();
/// Callback signature for [LongPressGestureRecognizer.onLongPressStart].
///
/// Called when a pointer has remained in contact with the screen at the
/// same location for a long period of time. Also reports the long press down
/// position.
typedef
GestureLongPressStartCallback
=
void
Function
(
LongPressStartDetails
details
);
/// Callback signature for [LongPressGestureRecognizer.onLongPressMoveUpdate].
///
/// Called when a pointer is moving after being held in contact at the same
/// location for a long period of time. Reports the new position and its offset
/// from the original down position.
typedef
GestureLongPressMoveUpdateCallback
=
void
Function
(
LongPressMoveUpdateDetails
details
);
/// Callback signature for [LongPressGestureRecognizer.onLongPressEnd].
///
/// Called when a pointer stops contacting the screen after a long press
/// gesture was detected. Also reports the position where the pointer stopped
/// contacting the screen.
typedef
GestureLongPressEndCallback
=
void
Function
(
LongPressEndDetails
details
);
/// Details for callbacks that use [GestureLongPressStartCallback].
///
/// See also:
///
/// * [LongPressGestureRecognizer.onLongPressStart], which uses [GestureLongPressStartCallback].
/// * [LongPressMoveUpdateDetails], the details for [GestureLongPressMoveUpdateCallback]
/// * [LongPressEndDetails], the details for [GestureLongPressEndCallback].
class
LongPressStartDetails
{
/// Creates the details for a [GestureLongPressStartCallback].
///
/// The [globalPosition] argument must not be null.
const
LongPressStartDetails
({
this
.
globalPosition
=
Offset
.
zero
})
:
assert
(
globalPosition
!=
null
);
/// The global position at which the pointer contacted the screen.
final
Offset
globalPosition
;
}
/// Details for callbacks that use [GestureLongPressMoveUpdateCallback].
///
/// See also:
///
/// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which uses [GestureLongPressMoveUpdateCallback].
/// * [LongPressEndDetails], the details for [GestureLongPressEndCallback]
/// * [LongPressStartDetails], the details for [GestureLongPressStartCallback].
class
LongPressMoveUpdateDetails
{
/// Creates the details for a [GestureLongPressMoveUpdateCallback].
///
/// The [globalPosition] and [offsetFromOrigin] arguments must not be null.
const
LongPressMoveUpdateDetails
({
this
.
globalPosition
=
Offset
.
zero
,
this
.
offsetFromOrigin
=
Offset
.
zero
,
})
:
assert
(
globalPosition
!=
null
),
assert
(
offsetFromOrigin
!=
null
);
/// 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 (the
/// present [globalPosition]) when this callback is triggered.
final
Offset
offsetFromOrigin
;
}
/// Details for callbacks that use [GestureLongPressEndCallback].
///
/// See also:
///
/// * [LongPressGestureRecognizer.onLongPressEnd], which uses [GestureLongPressEndCallback].
/// * [LongPressMoveUpdateDetails], the details for [GestureLongPressMoveUpdateCallback]
/// * [LongPressStartDetails], the details for [GestureLongPressStartCallback].
class
LongPressEndDetails
{
/// Creates the details for a [GestureLongPressEndCallback].
///
/// The [globalPosition] argument must not be null.
const
LongPressEndDetails
({
this
.
globalPosition
=
Offset
.
zero
})
:
assert
(
globalPosition
!=
null
);
/// 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.
///
/// The gesture must not deviate in position from its touch down point for 500ms
/// until it's recognized. Once the gesture is accepted, the finger can be
/// moved, triggering [onLongPressMoveUpdate] callbacks, unless the
/// [postAcceptSlopTolerance] constructor argument is specified.
class
LongPressGestureRecognizer
extends
PrimaryPointerGestureRecognizer
{
/// Creates a long-press gesture recognizer.
///
/// Consider assigning the [onLongPress] callback after creating this object.
LongPressGestureRecognizer
({
Object
debugOwner
})
:
super
(
deadline:
kLongPressTimeout
,
debugOwner:
debugOwner
);
/// Consider assigning the [onLongPressStart] callback after creating this
/// object.
///
/// The [postAcceptSlopTolerance] argument can be used to specify a maximum
/// allowed distance for the gesture to deviate from the starting point once
/// the long press has triggered. If the gesture deviates past that point,
/// subsequent callbacks ([onLongPressMoveUpdate], [onLongPressUp],
/// [onLongPressEnd]) will stop. Defaults to null, which means the gesture
/// can be moved without limit once the long press is accepted.
LongPressGestureRecognizer
({
double
postAcceptSlopTolerance
,
Object
debugOwner
,
})
:
super
(
deadline:
kLongPressTimeout
,
postAcceptSlopTolerance:
postAcceptSlopTolerance
,
debugOwner:
debugOwner
,
);
bool
_longPressAccepted
=
false
;
Offset
_longPressOrigin
;
/// Called when a long press gesture has been recognized.
///
/// See also:
///
/// * [onLongPressStart], which has the same timing but has data for the
/// press location.
GestureLongPressCallback
onLongPress
;
/// Called when the pointer stops contacting the screen after the long-press gesture has been recognized.
/// Callback for long press start with gesture location.
///
/// See also:
///
/// * [onLongPress], which has the same timing but without the location data.
GestureLongPressStartCallback
onLongPressStart
;
/// Callback for moving the gesture after the lang press is recognized.
GestureLongPressMoveUpdateCallback
onLongPressMoveUpdate
;
/// Called when the pointer stops contacting the screen after the long-press.
///
/// See also:
///
/// * [onLongPressEnd], which has the same timing but has data for the up
/// gesture location.
GestureLongPressUpCallback
onLongPressUp
;
/// Callback for long press end with gesture location.
///
/// See also:
///
/// * [onLongPressUp], which has the same timing but without the location data.
GestureLongPressEndCallback
onLongPressEnd
;
@override
void
didExceedDeadline
()
{
resolve
(
GestureDisposition
.
accepted
);
_longPressAccepted
=
true
;
super
.
acceptGesture
(
primaryPointer
);
if
(
onLongPress
!=
null
)
{
invokeCallback
<
void
>(
'onLongPress'
,
onLongPress
);
}
if
(
onLongPressStart
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressStart'
,
()
{
onLongPressStart
(
LongPressStartDetails
(
globalPosition:
_longPressOrigin
,
));
});
}
}
@override
void
handlePrimaryPointer
(
PointerEvent
event
)
{
if
(
event
is
PointerUpEvent
)
{
if
(
_longPressAccepted
==
true
&&
onLongPressUp
!=
null
)
{
if
(
_longPressAccepted
==
true
)
{
if
(
onLongPressUp
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressUp'
,
onLongPressUp
);
}
if
(
onLongPressEnd
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressEnd'
,
()
{
onLongPressEnd
(
LongPressEndDetails
(
globalPosition:
event
.
position
,
));
});
}
_longPressAccepted
=
false
;
invokeCallback
<
void
>(
'onLongPressUp'
,
onLongPressUp
);
}
else
{
resolve
(
GestureDisposition
.
rejected
);
}
}
else
if
(
event
is
PointerDownEvent
||
event
is
PointerCancelEvent
)
{
//
the first touch, initialize the flag with false
//
The first touch.
_longPressAccepted
=
false
;
_longPressOrigin
=
event
.
position
;
}
else
if
(
event
is
PointerMoveEvent
&&
_longPressAccepted
&&
onLongPressMoveUpdate
!=
null
)
{
invokeCallback
<
void
>(
'onLongPressMoveUpdate'
,
()
{
onLongPressMoveUpdate
(
LongPressMoveUpdateDetails
(
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
String
get
debugDescription
=>
'long press'
;
}
packages/flutter/lib/src/gestures/recognizer.dart
View file @
ec00e974
...
...
@@ -261,11 +261,11 @@ 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].
/// The recognizer advances from [ready] to [possible] when
it
starts tracking a
/// primary pointer. When the primary pointer is resolve
d in the gesture
///
arena (either accepted or rejected), the recognizers advances to [defunct].
///
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
,
...
...
@@ -283,19 +283,52 @@ 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.
/// Gestures based on this class will stop tracking the gesture if the primary
/// pointer travels beyond [preAcceptSlopTolerance] or [postAcceptSlopTolerance]
/// pixels from the original contact point of the gesture.
///
/// If the [preAcceptSlopTolerance] was breached before the gesture was accepted
/// in the gesture arena, the gesture will be rejected.
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.
///
/// Can be null to indicate that the gesture can drift for any distance.
/// Defaults to 18 logical pixels.
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 stop tracking
/// and signaling subsequent callbacks.
///
/// Can be null to indicate that the gesture can drift for any distance.
/// Defaults to 18 logical pixels.
final
double
postAcceptSlopTolerance
;
/// The current state of the recognizer.
///
/// See [GestureRecognizerState] for a description of the states.
...
...
@@ -307,6 +340,9 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
/// The global location at which the primary pointer contacted the screen.
Offset
initialPosition
;
// Whether this pointer is accepted by winning the arena or as defined by
// a subclass calling acceptGesture.
bool
_gestureAccepted
=
false
;
Timer
_timer
;
@override
...
...
@@ -325,8 +361,16 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
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
)
{
final
bool
isPreAcceptSlopPastTolerance
=
!
_gestureAccepted
&&
preAcceptSlopTolerance
!=
null
&&
_getDistance
(
event
)
>
preAcceptSlopTolerance
;
final
bool
isPostAcceptSlopPastTolerance
=
_gestureAccepted
&&
postAcceptSlopTolerance
!=
null
&&
_getDistance
(
event
)
>
postAcceptSlopTolerance
;
if
(
event
is
PointerMoveEvent
&&
(
isPreAcceptSlopPastTolerance
||
isPostAcceptSlopPastTolerance
))
{
resolve
(
GestureDisposition
.
rejected
);
stopTrackingPointer
(
primaryPointer
);
}
else
{
...
...
@@ -348,6 +392,11 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
assert
(
deadline
==
null
);
}
@override
void
acceptGesture
(
int
pointer
)
{
_gestureAccepted
=
true
;
}
@override
void
rejectGesture
(
int
pointer
)
{
if
(
pointer
==
primaryPointer
&&
state
==
GestureRecognizerState
.
possible
)
{
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
ec00e974
...
...
@@ -568,6 +568,21 @@ 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.
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
if
(
cause
==
SelectionChangedCause
.
longPress
)
{
_editableTextKey
.
currentState
?.
bringIntoView
(
selection
.
base
);
}
return
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
// Do nothing.
}
}
InteractiveInkFeature
_createInkFeature
(
TapDownDetails
details
)
{
final
MaterialInkController
inkController
=
Material
.
of
(
context
);
final
ThemeData
themeData
=
Theme
.
of
(
context
);
...
...
@@ -641,11 +656,14 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_cancelCurrentSplash
();
}
void
_handleSingleLongTap
Down
(
)
{
void
_handleSingleLongTap
Start
(
LongPressStartDetails
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
:
...
...
@@ -653,11 +671,35 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
Feedback
.
forLongPress
(
context
);
break
;
}
_editableTextKey
.
currentState
.
showToolbar
();
}
_confirmCurrentSplash
();
}
void
_handleSingleLongTapMoveUpdate
(
LongPressMoveUpdateDetails
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
_handleSingleLongTapEnd
(
LongPressEndDetails
details
)
{
_editableTextKey
.
currentState
.
showToolbar
();
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
...
...
@@ -777,6 +819,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
,
...
...
@@ -825,7 +868,9 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onForcePressStart:
forcePressEnabled
?
_handleForcePressStarted
:
null
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onSingleLongTapStart:
_handleSingleLongTapStart
,
onSingleLongTapMoveUpdate:
_handleSingleLongTapMoveUpdate
,
onSingleLongTapEnd:
_handleSingleLongTapEnd
,
onDoubleTapDown:
_handleDoubleTapDown
,
behavior:
HitTestBehavior
.
translucent
,
child:
child
,
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
ec00e974
...
...
@@ -1203,7 +1203,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
;
}
void
_handleTapDown
(
TapDownDetails
details
)
{
assert
(!
ignorePointer
);
...
...
@@ -1259,12 +1259,28 @@ 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
);
assert
(
from
!=
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
,
);
}
}
...
...
@@ -1283,12 +1299,13 @@ class RenderEditable extends RenderBox {
/// {@macro flutter.rendering.editable.select}
void
selectWordsInRange
({
@required
Offset
from
,
Offset
to
,
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
assert
(
from
!=
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
(
...
...
@@ -1308,7 +1325,7 @@ class RenderEditable extends RenderBox {
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
-
_paintOffset
));
final
TextRange
word
=
_textPainter
.
getWordBoundary
(
position
);
if
(
position
.
offset
-
word
.
start
<=
1
)
{
onSelectionChanged
(
...
...
packages/flutter/lib/src/widgets/gesture_detector.dart
View file @
ec00e974
...
...
@@ -19,6 +19,10 @@ export 'package:flutter/gestures.dart' show
GestureTapCallback
,
GestureTapCancelCallback
,
GestureLongPressCallback
,
GestureLongPressStartCallback
,
GestureLongPressMoveUpdateCallback
,
GestureLongPressUpCallback
,
GestureLongPressEndCallback
,
GestureDragDownCallback
,
GestureDragStartCallback
,
GestureDragUpdateCallback
,
...
...
@@ -31,6 +35,9 @@ export 'package:flutter/gestures.dart' show
GestureForcePressPeakCallback
,
GestureForcePressEndCallback
,
GestureForcePressUpdateCallback
,
LongPressStartDetails
,
LongPressMoveUpdateDetails
,
LongPressEndDetails
,
ScaleStartDetails
,
ScaleUpdateDetails
,
ScaleEndDetails
,
...
...
@@ -161,7 +168,10 @@ class GestureDetector extends StatelessWidget {
this
.
onTapCancel
,
this
.
onDoubleTap
,
this
.
onLongPress
,
this
.
onLongPressStart
,
this
.
onLongPressMoveUpdate
,
this
.
onLongPressUp
,
this
.
onLongPressEnd
,
this
.
onVerticalDragDown
,
this
.
onVerticalDragStart
,
this
.
onVerticalDragUpdate
,
...
...
@@ -256,13 +266,45 @@ class GestureDetector extends StatelessWidget {
/// succession.
final
GestureTapCallback
onDoubleTap
;
/// A pointer has remained in contact with the screen at the same location for
/// a long period of time.
/// Called when a long press gesture has been recognized.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// See also:
///
/// * [onLongPressStart], which has the same timing but has data for the
/// press location.
final
GestureLongPressCallback
onLongPress
;
/// Callback for long press start with gesture location.
///
/// Triggered when a pointer has remained in contact with the screen at the
/// same location for a long period of time.
///
/// See also:
///
/// * [onLongPress], which has the same timing but without the location data.
final
GestureLongPressStartCallback
onLongPressStart
;
/// A pointer has been drag-moved after a long press.
final
GestureLongPressMoveUpdateCallback
onLongPressMoveUpdate
;
/// A pointer that has triggered a long-press has stopped contacting the screen.
///
/// See also:
///
/// * [onLongPressEnd], which has the same timing but has data for the up
/// gesture location.
final
GestureLongPressUpCallback
onLongPressUp
;
/// A pointer that has triggered a long-press has stopped contacting the screen.
///
/// See also:
///
/// * [onLongPressUp], which has the same timing but without the location data.
final
GestureLongPressEndCallback
onLongPressEnd
;
/// A pointer has contacted the screen and might begin to move vertically.
final
GestureDragDownCallback
onVerticalDragDown
;
...
...
@@ -421,12 +463,19 @@ class GestureDetector extends StatelessWidget {
);
}
if
(
onLongPress
!=
null
||
onLongPressUp
!=
null
)
{
if
(
onLongPress
!=
null
||
onLongPressUp
!=
null
||
onLongPressStart
!=
null
||
onLongPressMoveUpdate
!=
null
||
onLongPressEnd
!=
null
)
{
gestures
[
LongPressGestureRecognizer
]
=
GestureRecognizerFactoryWithHandlers
<
LongPressGestureRecognizer
>(
()
=>
LongPressGestureRecognizer
(
debugOwner:
this
),
(
LongPressGestureRecognizer
instance
)
{
instance
..
onLongPress
=
onLongPress
..
onLongPressStart
=
onLongPressStart
..
onLongPressMoveUpdate
=
onLongPressMoveUpdate
..
onLongPressEnd
=
onLongPressEnd
..
onLongPressUp
=
onLongPressUp
;
},
);
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
ec00e974
...
...
@@ -616,7 +616,9 @@ class TextSelectionGestureDetector extends StatefulWidget {
this
.
onForcePressEnd
,
this
.
onSingleTapUp
,
this
.
onSingleTapCancel
,
this
.
onSingleLongTapDown
,
this
.
onSingleLongTapStart
,
this
.
onSingleLongTapMoveUpdate
,
this
.
onSingleLongTapEnd
,
this
.
onDoubleTapDown
,
this
.
behavior
,
@required
this
.
child
,
...
...
@@ -650,7 +652,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
GestureLongPressStartCallback
onSingleLongTapStart
;
/// Called after [onSingleLongTapStart] when the pointer is dragged.
final
GestureLongPressMoveUpdateCallback
onSingleLongTapMoveUpdate
;
/// Called after [onSingleLongTapStart] when the pointer is lifted.
final
GestureLongPressEndCallback
onSingleLongTapEnd
;
/// Called after a momentary hold or a short tap that is close in space and
/// time (within [kDoubleTapTimeout]) to a previous short tap.
...
...
@@ -734,9 +742,21 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
widget
.
onForcePressEnd
(
details
);
}
void
_handleLongPress
()
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapDown
!=
null
)
{
widget
.
onSingleLongTapDown
();
void
_handleLongPressStart
(
LongPressStartDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapStart
!=
null
)
{
widget
.
onSingleLongTapStart
(
details
);
}
}
void
_handleLongPressMoveUpdate
(
LongPressMoveUpdateDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapMoveUpdate
!=
null
)
{
widget
.
onSingleLongTapMoveUpdate
(
details
);
}
}
void
_handleLongPressUp
(
LongPressEndDetails
details
)
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapEnd
!=
null
)
{
widget
.
onSingleLongTapEnd
(
details
);
}
_isDoubleTap
=
false
;
}
...
...
@@ -764,7 +784,9 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
onForcePressStart:
widget
.
onForcePressStart
!=
null
?
_forcePressStarted
:
null
,
onForcePressEnd:
widget
.
onForcePressEnd
!=
null
?
_forcePressEnded
:
null
,
onTapCancel:
_handleTapCancel
,
onLongPress:
_handleLongPress
,
onLongPressStart:
_handleLongPressStart
,
onLongPressMoveUpdate:
_handleLongPressMoveUpdate
,
onLongPressEnd:
_handleLongPressUp
,
excludeFromSemantics:
true
,
behavior:
widget
.
behavior
,
child:
widget
.
child
,
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
ec00e974
...
...
@@ -1272,7 +1272,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'
,
...
...
@@ -1302,11 +1302,163 @@ 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
),
);
// Toolbar only shows up on long press up.
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 @
ec00e974
This diff is collapsed.
Click to expand it.
packages/flutter/test/material/text_field_test.dart
View file @
ec00e974
...
...
@@ -1026,10 +1026,10 @@ void main() {
expect
(
inputBox
.
hitTest
(
HitTestResult
(),
position:
inputBox
.
globalToLocal
(
newFourthPos
)),
isTrue
);
// Now try scrolling by dragging the selection handle.
// Long press the
'i' in 'Fourth line' to select the word
.
// Long press the
middle of the word "won't" in the fourth line
.
final
Offset
selectedWordPos
=
textOffsetToPosition
(
tester
,
kMoreThanFourLines
.
indexOf
(
'Fourth line'
)
+
8
,
kMoreThanFourLines
.
indexOf
(
'Fourth line'
)
+
14
,
);
gesture
=
await
tester
.
startGesture
(
selectedWordPos
,
pointer:
7
);
...
...
@@ -1038,8 +1038,13 @@ void main() {
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
controller
.
selection
.
base
.
offset
,
91
);
expect
(
controller
.
selection
.
extent
.
offset
,
94
);
expect
(
controller
.
selection
.
base
.
offset
,
77
);
expect
(
controller
.
selection
.
extent
.
offset
,
82
);
// Sanity check for the word selected is the intended one.
expect
(
controller
.
text
.
substring
(
controller
.
selection
.
baseOffset
,
controller
.
selection
.
extentOffset
),
"won't"
,
);
final
RenderEditable
renderEditable
=
findRenderEditable
(
tester
);
final
List
<
TextSelectionPoint
>
endpoints
=
globalize
(
...
...
@@ -4199,7 +4204,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'
,
...
...
@@ -4237,6 +4242,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 near the right edge.
expect
(
lastCharEndpoint
[
0
].
point
.
dx
,
moreOrLessEquals
(
798
,
epsilon:
1
));
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
(-
257
,
epsilon:
1
));
});
testWidgets
(
'long tap after a double tap select is not affected (iOS)'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
ec00e974
...
...
@@ -254,4 +254,84 @@ void main() {
);
expect
(
editable
,
paintsExactlyCountTimes
(
#drawRect
,
1
));
});
test
(
'selects correct place with offsets'
,
()
{
final
TextSelectionDelegate
delegate
=
FakeEditableTextState
();
final
ViewportOffset
viewportOffset
=
ViewportOffset
.
zero
();
TextSelection
currentSelection
;
final
RenderEditable
editable
=
RenderEditable
(
backgroundCursorColor:
Colors
.
grey
,
selectionColor:
Colors
.
black
,
textDirection:
TextDirection
.
ltr
,
cursorColor:
Colors
.
red
,
offset:
viewportOffset
,
// This makes the scroll axis vertical.
maxLines:
2
,
textSelectionDelegate:
delegate
,
onSelectionChanged:
(
TextSelection
selection
,
RenderEditable
renderObject
,
SelectionChangedCause
cause
)
{
currentSelection
=
selection
;
},
text:
const
TextSpan
(
text:
'test
\n
test'
,
style:
TextStyle
(
height:
1.0
,
fontSize:
10.0
,
fontFamily:
'Ahem'
,
),
),
);
layout
(
editable
);
expect
(
editable
,
paints
..
paragraph
(
offset:
Offset
.
zero
),
);
editable
.
selectPositionAt
(
from:
const
Offset
(
0
,
2
),
cause:
SelectionChangedCause
.
tap
);
pumpFrame
();
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
0
);
viewportOffset
.
correctBy
(
10
);
pumpFrame
();
expect
(
editable
,
paints
..
paragraph
(
offset:
const
Offset
(
0
,
-
10
)),
);
// Tap the same place. But because the offset is scrolled up, the second line
// gets tapped instead.
editable
.
selectPositionAt
(
from:
const
Offset
(
0
,
2
),
cause:
SelectionChangedCause
.
tap
);
pumpFrame
();
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
5
);
// Test the other selection methods.
// Move over by one character.
editable
.
handleTapDown
(
TapDownDetails
(
globalPosition:
const
Offset
(
10
,
2
)));
pumpFrame
();
editable
.
selectPosition
(
cause:
SelectionChangedCause
.
tap
);
pumpFrame
();
expect
(
currentSelection
.
isCollapsed
,
true
);
expect
(
currentSelection
.
baseOffset
,
6
);
editable
.
handleTapDown
(
TapDownDetails
(
globalPosition:
const
Offset
(
20
,
2
)));
pumpFrame
();
editable
.
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
pumpFrame
();
expect
(
currentSelection
.
isCollapsed
,
false
);
expect
(
currentSelection
.
baseOffset
,
5
);
expect
(
currentSelection
.
extentOffset
,
9
);
// Select one more character down but since it's still part of the same
// word, the same word is selected.
editable
.
selectWordsInRange
(
from:
const
Offset
(
30
,
2
),
cause:
SelectionChangedCause
.
longPress
);
pumpFrame
();
expect
(
currentSelection
.
isCollapsed
,
false
);
expect
(
currentSelection
.
baseOffset
,
5
);
expect
(
currentSelection
.
extentOffset
,
9
);
});
}
packages/flutter/test/widgets/text_selection_test.dart
View file @
ec00e974
...
...
@@ -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
;
...
...
@@ -19,7 +19,7 @@ void main() {
void
_handleTapDown
(
TapDownDetails
details
)
{
tapCount
++;
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
singleTapUpCount
++;
}
void
_handleSingleTapCancel
()
{
singleTapCancelCount
++;
}
void
_handleSingleLongTap
Down
()
{
singleLongTapDown
Count
++;
}
void
_handleSingleLongTap
Start
(
LongPressStartDetails
details
)
{
singleLongTapStart
Count
++;
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
doubleTapDownCount
++;
}
void
_handleForcePressStart
(
ForcePressDetails
details
)
{
forcePressStartCount
++;
}
void
_handleForcePressEnd
(
ForcePressDetails
details
)
{
forcePressEndCount
++;
}
...
...
@@ -28,7 +28,7 @@ void main() {
tapCount
=
0
;
singleTapUpCount
=
0
;
singleTapCancelCount
=
0
;
singleLongTap
Down
Count
=
0
;
singleLongTap
Start
Count
=
0
;
doubleTapDownCount
=
0
;
forcePressStartCount
=
0
;
forcePressEndCount
=
0
;
...
...
@@ -41,7 +41,7 @@ void main() {
onTapDown:
_handleTapDown
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTap
Down:
_handleSingleLongTapDown
,
onSingleLongTap
Start:
_handleSingleLongTapStart
,
onDoubleTapDown:
_handleDoubleTapDown
,
onForcePressStart:
_handleForcePressStart
,
onForcePressEnd:
_handleForcePressEnd
,
...
...
@@ -108,7 +108,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.
...
...
@@ -116,7 +116,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
{
...
...
@@ -129,7 +129,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.
...
...
@@ -137,7 +137,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
{
...
...
@@ -150,7 +150,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