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
43770838
Unverified
Commit
43770838
authored
Jan 14, 2019
by
jslavitz
Committed by
GitHub
Jan 14, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Selects a word on force tap (#25683)
* adds force press select word functionality
parent
35a7fd12
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
230 additions
and
3 deletions
+230
-3
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+14
-0
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+16
-0
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+20
-3
gesture_detector.dart
packages/flutter/lib/src/widgets/gesture_detector.dart
+1
-0
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+24
-0
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+32
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+65
-0
text_selection_test.dart
packages/flutter/test/widgets/text_selection_test.dart
+58
-0
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
43770838
...
...
@@ -451,6 +451,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
_renderEditable
.
handleTapDown
(
details
);
}
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
// The cause is not keyboard press but we would still like to just
// highlight the word without showing any handles or toolbar.
_renderEditable
.
selectWordsInRange
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
keyboard
);
}
void
_handleForcePressEnded
(
ForcePressDetails
details
)
{
// The cause is not technically double tap, but we would still like to show
// the toolbar and handles.
_renderEditable
.
selectWordsInRange
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
doubleTap
);
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
_renderEditable
.
selectWordEdge
(
cause:
SelectionChangedCause
.
tap
);
_requestKeyboard
();
...
...
@@ -648,6 +660,8 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
:
CupertinoColors
.
darkBackgroundGray
,
child:
TextSelectionGestureDetector
(
onTapDown:
_handleTapDown
,
onForcePressStart:
_handleForcePressStarted
,
onForcePressEnd:
_handleForcePressEnded
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onDoubleTapDown:
_handleDoubleTapDown
,
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
43770838
...
...
@@ -543,6 +543,21 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_startSplash
(
details
);
}
void
_handleForcePressStarted
(
ForcePressDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
context
).
platform
)
{
case
TargetPlatform
.
iOS
:
// The cause is not technically double tap, but we would like to show
// the toolbar.
_renderEditable
.
selectWordsInRange
(
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
doubleTap
);
break
;
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
break
;
}
}
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
if
(
widget
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
context
).
platform
)
{
...
...
@@ -706,6 +721,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
ignoring:
!(
widget
.
enabled
??
widget
.
decoration
?.
enabled
??
true
),
child:
TextSelectionGestureDetector
(
onTapDown:
_handleTapDown
,
onForcePressStart:
_handleForcePressStarted
,
onSingleTapUp:
_handleSingleTapUp
,
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
43770838
...
...
@@ -1184,12 +1184,29 @@ class RenderEditable extends RenderBox {
/// Select a word around the location of the last tap down.
void
selectWord
({
@required
SelectionChangedCause
cause
})
{
selectWordsInRange
(
from:
_lastTapDownPosition
,
cause:
cause
);
}
/// Selects the set words of a paragraph in a given range of global positions.
///
/// The first and last endpoints of the selection will always be at the
/// beginning and end of a word respectively.
void
selectWordsInRange
({
@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
(
_selectWordAtOffset
(
position
),
this
,
cause
);
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
)));
onSelectionChanged
(
TextSelection
(
baseOffset:
firstWord
.
base
.
offset
,
extentOffset:
lastWord
.
extent
.
offset
,
affinity:
firstWord
.
affinity
,
),
this
,
cause
,
);
}
}
...
...
packages/flutter/lib/src/widgets/gesture_detector.dart
View file @
43770838
...
...
@@ -36,6 +36,7 @@ export 'package:flutter/gestures.dart' show
ScaleEndDetails
,
TapDownDetails
,
TapUpDetails
,
ForcePressDetails
,
Velocity
;
// Examples can assume:
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
43770838
...
...
@@ -612,6 +612,8 @@ class TextSelectionGestureDetector extends StatefulWidget {
const
TextSelectionGestureDetector
({
Key
key
,
this
.
onTapDown
,
this
.
onForcePressStart
,
this
.
onForcePressEnd
,
this
.
onSingleTapUp
,
this
.
onSingleTapCancel
,
this
.
onSingleLongTapDown
,
...
...
@@ -626,6 +628,14 @@ class TextSelectionGestureDetector extends StatefulWidget {
/// to not qualify as taps (e.g. pans and flings).
final
GestureTapDownCallback
onTapDown
;
/// Called when a pointer has tapped down and the force of the pointer has
/// just become greater than [ForcePressGestureDetector.startPressure].
final
GestureForcePressStartCallback
onForcePressStart
;
/// Called when a pointer that had previously triggered [onForcePressStart] is
/// lifted off the screen.
final
GestureForcePressEndCallback
onForcePressEnd
;
/// Called for each distinct tap except for every second tap of a double tap.
/// For example, if the detector was configured [onSingleTapDown] and
/// [onDoubleTapDown], three quick taps would be recognized as a single tap
...
...
@@ -712,6 +722,18 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
}
}
void
_forcePressStarted
(
ForcePressDetails
details
)
{
_doubleTapTimer
?.
cancel
();
_doubleTapTimer
=
null
;
if
(
widget
.
onForcePressStart
!=
null
)
widget
.
onForcePressStart
(
details
);
}
void
_forcePressEnded
(
ForcePressDetails
details
)
{
if
(
widget
.
onForcePressEnd
!=
null
)
widget
.
onForcePressEnd
(
details
);
}
void
_handleLongPress
()
{
if
(!
_isDoubleTap
&&
widget
.
onSingleLongTapDown
!=
null
)
{
widget
.
onSingleLongTapDown
();
...
...
@@ -739,6 +761,8 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec
return
GestureDetector
(
onTapDown:
_handleTapDown
,
onTapUp:
_handleTapUp
,
onForcePressStart:
_forcePressStarted
,
onForcePressEnd:
_forcePressEnded
,
onTapCancel:
_handleTapCancel
,
onLongPress:
_handleLongPress
,
excludeFromSemantics:
true
,
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
43770838
...
...
@@ -1099,6 +1099,38 @@ void main() {
},
);
testWidgets
(
'force press selects word'
,
(
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
));
const
int
pointerValue
=
1
;
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
gesture
.
updateWithCustomEvent
(
PointerMoveEvent
(
pointer:
pointerValue
,
position:
textfieldStart
+
const
Offset
(
150.0
,
5.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
// We expect the force press to select a word at the given location.
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
},
);
testWidgets
(
'text field respects theme'
,
(
WidgetTester
tester
)
async
{
...
...
packages/flutter/test/material/text_field_test.dart
View file @
43770838
...
...
@@ -12,6 +12,7 @@ import 'package:flutter/material.dart';
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
show
DragStartBehavior
;
import
'../widgets/semantics_tester.dart'
;
...
...
@@ -4204,6 +4205,70 @@ void main() {
},
);
testWidgets
(
'force press does not select a word on (android)'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
android
;
final
TextEditingController
controller
=
TextEditingController
(
text:
'Atwater Peel Sherbrooke Bonaventure'
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
TextField
(
controller:
controller
,
),
),
),
);
final
Offset
textfieldStart
=
tester
.
getTopLeft
(
find
.
byType
(
TextField
));
const
int
pointerValue
=
1
;
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
gesture
.
updateWithCustomEvent
(
PointerMoveEvent
(
pointer:
pointerValue
,
position:
textfieldStart
+
const
Offset
(
150.0
,
5.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
// We don't want this gesture to select any word on Android.
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
-
1
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
FlatButton
),
findsNothing
);
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'force press selects word (iOS)'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
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
));
const
int
pointerValue
=
1
;
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textfieldStart
+
const
Offset
(
150.0
,
5.0
));
await
gesture
.
updateWithCustomEvent
(
PointerMoveEvent
(
pointer:
pointerValue
,
position:
textfieldStart
+
const
Offset
(
150.0
,
5.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
// We expect the force press to select a word at the given location.
expect
(
controller
.
selection
,
const
TextSelection
(
baseOffset:
8
,
extentOffset:
12
),
);
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
CupertinoButton
),
findsNWidgets
(
3
));
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'default TextField debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
...
...
packages/flutter/test/widgets/text_selection_test.dart
View file @
43770838
...
...
@@ -11,12 +11,16 @@ void main() {
int
singleTapCancelCount
;
int
singleLongTapDownCount
;
int
doubleTapDownCount
;
int
forcePressStartCount
;
int
forcePressEndCount
;
void
_handleTapDown
(
TapDownDetails
details
)
{
tapCount
++;
}
void
_handleSingleTapUp
(
TapUpDetails
details
)
{
singleTapUpCount
++;
}
void
_handleSingleTapCancel
()
{
singleTapCancelCount
++;
}
void
_handleSingleLongTapDown
()
{
singleLongTapDownCount
++;
}
void
_handleDoubleTapDown
(
TapDownDetails
details
)
{
doubleTapDownCount
++;
}
void
_handleForcePressStart
(
ForcePressDetails
details
)
{
forcePressStartCount
++;
}
void
_handleForcePressEnd
(
ForcePressDetails
details
)
{
forcePressEndCount
++;
}
setUp
(()
{
tapCount
=
0
;
...
...
@@ -24,6 +28,8 @@ void main() {
singleTapCancelCount
=
0
;
singleLongTapDownCount
=
0
;
doubleTapDownCount
=
0
;
forcePressStartCount
=
0
;
forcePressEndCount
=
0
;
});
Future
<
void
>
pumpGestureDetector
(
WidgetTester
tester
)
async
{
...
...
@@ -35,6 +41,8 @@ void main() {
onSingleTapCancel:
_handleSingleTapCancel
,
onSingleLongTapDown:
_handleSingleLongTapDown
,
onDoubleTapDown:
_handleDoubleTapDown
,
onForcePressStart:
_handleForcePressStart
,
onForcePressEnd:
_handleForcePressEnd
,
child:
Container
(),
),
);
...
...
@@ -142,4 +150,54 @@ void main() {
expect
(
doubleTapDownCount
,
0
);
expect
(
singleLongTapDownCount
,
0
);
});
testWidgets
(
'a force press intiates a force press'
,
(
WidgetTester
tester
)
async
{
await
pumpGestureDetector
(
tester
);
const
int
pointerValue
=
1
;
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
await
gesture
.
up
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
20
));
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
await
gesture
.
up
();
expect
(
forcePressStartCount
,
4
);
});
testWidgets
(
'a tap and then force press intiates a force press and not a double tap'
,
(
WidgetTester
tester
)
async
{
await
pumpGestureDetector
(
tester
);
const
int
pointerValue
=
1
;
TestGesture
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
// Initiate a quick tap.
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.0
,
pressureMin:
0
,
pressureMax:
1
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
up
();
// Initiate a force tap.
gesture
=
await
tester
.
startGesture
(
const
Offset
(
400.0
,
50.0
),
pointer:
pointerValue
);
await
gesture
.
updateWithCustomEvent
(
const
PointerMoveEvent
(
pointer:
pointerValue
,
position:
Offset
(
0.0
,
0.0
),
pressure:
0.5
,
pressureMin:
0
,
pressureMax:
1
));
expect
(
forcePressStartCount
,
1
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
forcePressEndCount
,
1
);
expect
(
doubleTapDownCount
,
0
);
});
}
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