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
2fd78dcc
Commit
2fd78dcc
authored
Oct 05, 2016
by
Matt Perry
Committed by
GitHub
Oct 05, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add multiline support to Input and friends. (#6155)
Fixes
https://github.com/flutter/flutter/issues/6154
parent
73ff4198
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
256 additions
and
96 deletions
+256
-96
text_field_demo.dart
examples/flutter_gallery/lib/demo/text_field_demo.dart
+6
-0
input.dart
packages/flutter/lib/src/material/input.dart
+7
-2
text_selection.dart
packages/flutter/lib/src/material/text_selection.dart
+50
-42
text_painter.dart
packages/flutter/lib/src/painting/text_painter.dart
+2
-2
editable_line.dart
packages/flutter/lib/src/rendering/editable_line.dart
+18
-7
editable.dart
packages/flutter/lib/src/widgets/editable.dart
+16
-12
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+29
-31
input_test.dart
packages/flutter/test/widget/input_test.dart
+128
-0
No files found.
examples/flutter_gallery/lib/demo/text_field_demo.dart
View file @
2fd78dcc
...
...
@@ -94,6 +94,12 @@ class TextFieldDemoState extends State<TextFieldDemo> {
validator:
_validatePhoneNumber
)
),
new
Input
(
hintText:
'Tell us about yourself (optional)'
,
labelText:
'Life story'
,
multiline:
true
,
formField:
new
FormField
<
String
>()
),
new
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
...
...
packages/flutter/lib/src/material/input.dart
View file @
2fd78dcc
...
...
@@ -43,6 +43,7 @@ class Input extends StatefulWidget {
this
.
hideText
:
false
,
this
.
isDense
:
false
,
this
.
autofocus
:
false
,
this
.
multiline
:
false
,
this
.
formField
,
this
.
onChanged
,
this
.
onSubmitted
...
...
@@ -84,6 +85,10 @@ class Input extends StatefulWidget {
/// Whether this input field should focus itself is nothing else is already focused.
final
bool
autofocus
;
/// True if the text should wrap and span multiple lines, false if it should
/// stay on a single line and scroll when overflowed.
final
bool
multiline
;
/// Form-specific data, required if this Input is part of a Form.
final
FormField
<
String
>
formField
;
...
...
@@ -205,10 +210,10 @@ class _InputState extends State<Input> {
focusKey:
focusKey
,
style:
textStyle
,
hideText:
config
.
hideText
,
multiline:
config
.
multiline
,
cursorColor:
themeData
.
textSelectionColor
,
selectionColor:
themeData
.
textSelectionColor
,
selectionHandleBuilder:
buildTextSelectionHandle
,
selectionToolbarBuilder:
buildTextSelectionToolbar
,
selectionControls:
materialTextSelectionControls
,
platform:
Theme
.
of
(
context
).
platform
,
keyboardType:
config
.
keyboardType
,
onChanged:
onChanged
,
...
...
packages/flutter/lib/src/material/text_selection.dart
View file @
2fd78dcc
...
...
@@ -143,49 +143,57 @@ class _TextSelectionHandlePainter extends CustomPainter {
}
}
/// Builder for material-style copy/paste text selection toolbar.
Widget
buildTextSelectionToolbar
(
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
)
{
final
Size
screenSize
=
MediaQuery
.
of
(
context
).
size
;
return
new
ConstrainedBox
(
constraints:
new
BoxConstraints
.
loose
(
screenSize
),
child:
new
CustomSingleChildLayout
(
delegate:
new
_TextSelectionToolbarLayout
(
position
),
child:
new
_TextSelectionToolbar
(
delegate
)
)
);
}
class
_MaterialTextSelectionControls
extends
TextSelectionControls
{
@override
Size
handleSize
=
const
Size
(
_kHandleSize
,
_kHandleSize
);
/// Builder for material-style text selection handles.
Widget
buildTextSelectionHandle
(
BuildContext
context
,
TextSelectionHandleType
type
)
{
Widget
handle
=
new
SizedBox
(
width:
_kHandleSize
,
height:
_kHandleSize
,
child:
new
CustomPaint
(
painter:
new
_TextSelectionHandlePainter
(
color:
Theme
.
of
(
context
).
textSelectionHandleColor
/// Builder for material-style copy/paste text selection toolbar.
@override
Widget
buildToolbar
(
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
)
{
final
Size
screenSize
=
MediaQuery
.
of
(
context
).
size
;
return
new
ConstrainedBox
(
constraints:
new
BoxConstraints
.
loose
(
screenSize
),
child:
new
CustomSingleChildLayout
(
delegate:
new
_TextSelectionToolbarLayout
(
position
),
child:
new
_TextSelectionToolbar
(
delegate
)
)
)
);
// [handle] is a circle, with a rectangle in the top left quadrant of that
// circle (an onion pointing to 10:30). We rotate [handle] to point
// straight up or up-right depending on the handle type.
switch
(
type
)
{
case
TextSelectionHandleType
.
left
:
// points up-right
return
new
Transform
(
transform:
new
Matrix4
.
rotationZ
(
math
.
PI
/
2.0
),
child:
handle
);
case
TextSelectionHandleType
.
right
:
// points up-left
return
handle
;
case
TextSelectionHandleType
.
collapsed
:
// points up
return
new
Transform
(
transform:
new
Matrix4
.
rotationZ
(
math
.
PI
/
4.0
),
child:
handle
);
);
}
/// Builder for material-style text selection handles.
@override
Widget
buildHandle
(
BuildContext
context
,
TextSelectionHandleType
type
)
{
Widget
handle
=
new
SizedBox
(
width:
_kHandleSize
,
height:
_kHandleSize
,
child:
new
CustomPaint
(
painter:
new
_TextSelectionHandlePainter
(
color:
Theme
.
of
(
context
).
textSelectionHandleColor
)
)
);
// [handle] is a circle, with a rectangle in the top left quadrant of that
// circle (an onion pointing to 10:30). We rotate [handle] to point
// straight up or up-right depending on the handle type.
switch
(
type
)
{
case
TextSelectionHandleType
.
left
:
// points up-right
return
new
Transform
(
transform:
new
Matrix4
.
rotationZ
(
math
.
PI
/
2.0
),
child:
handle
);
case
TextSelectionHandleType
.
right
:
// points up-left
return
handle
;
case
TextSelectionHandleType
.
collapsed
:
// points up
return
new
Transform
(
transform:
new
Matrix4
.
rotationZ
(
math
.
PI
/
4.0
),
child:
handle
);
}
assert
(
type
!=
null
);
return
null
;
}
assert
(
type
!=
null
);
return
null
;
}
final
_MaterialTextSelectionControls
materialTextSelectionControls
=
new
_MaterialTextSelectionControls
();
packages/flutter/lib/src/painting/text_painter.dart
View file @
2fd78dcc
...
...
@@ -219,7 +219,7 @@ class TextPainter {
ui
.
TextBox
box
=
boxes
[
0
];
double
caretEnd
=
box
.
end
;
double
dx
=
box
.
direction
==
TextDirection
.
rtl
?
caretEnd
:
caretEnd
-
caretPrototype
.
width
;
return
new
Offset
(
dx
,
0.0
);
return
new
Offset
(
dx
,
box
.
top
);
}
Offset
_getOffsetFromDownstream
(
int
offset
,
Rect
caretPrototype
)
{
...
...
@@ -229,7 +229,7 @@ class TextPainter {
ui
.
TextBox
box
=
boxes
[
0
];
double
caretStart
=
box
.
start
;
double
dx
=
box
.
direction
==
TextDirection
.
rtl
?
caretStart
-
caretPrototype
.
width
:
caretStart
;
return
new
Offset
(
dx
,
0.0
);
return
new
Offset
(
dx
,
box
.
top
);
}
/// Returns the offset at which to paint the caret.
...
...
packages/flutter/lib/src/rendering/editable_line.dart
View file @
2fd78dcc
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:ui'
as
ui
show
Paragraph
,
ParagraphBuilder
,
ParagraphConstraints
,
ParagraphStyle
,
TextBox
;
import
'dart:math'
as
math
;
import
'package:flutter/gestures.dart'
;
...
...
@@ -43,15 +44,17 @@ class RenderEditableLine extends RenderBox {
TextSpan
text
,
Color
cursorColor
,
bool
showCursor:
false
,
bool
multiline:
false
,
Color
selectionColor
,
double
textScaleFactor:
1.0
,
TextSelection
selection
,
this
.
onSelectionChanged
,
Offset
paintOffset:
Offset
.
zero
,
this
.
onPaintOffsetUpdateNeeded
this
.
onPaintOffsetUpdateNeeded
,
})
:
_textPainter
=
new
TextPainter
(
text:
text
,
textScaleFactor:
textScaleFactor
),
_cursorColor
=
cursorColor
,
_showCursor
=
showCursor
,
_multiline
=
multiline
,
_selection
=
selection
,
_paintOffset
=
paintOffset
{
assert
(!
showCursor
||
cursorColor
!=
null
);
...
...
@@ -163,14 +166,14 @@ class RenderEditableLine extends RenderBox {
// TODO(mpcomplete): We should be more disciplined about when we dirty the
// layout state of the text painter so that we can know that the layout is
// clean at this point.
_textPainter
.
layout
();
_textPainter
.
layout
(
maxWidth:
_maxContentWidth
);
Offset
offset
=
_paintOffset
;
if
(
selection
.
isCollapsed
)
{
// TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary.
Offset
caretOffset
=
_textPainter
.
getOffsetForCaret
(
selection
.
extent
,
_caretPrototype
);
Point
start
=
new
Point
(
caretOffset
.
dx
,
size
.
height
)
+
offset
;
Point
start
=
new
Point
(
0.0
,
constraints
.
constrainHeight
(
_preferredHeight
))
+
caretOffset
+
offset
;
return
<
TextSelectionPoint
>[
new
TextSelectionPoint
(
localToGlobal
(
start
),
null
)];
}
else
{
List
<
ui
.
TextBox
>
boxes
=
_textPainter
.
getBoxesForSelection
(
selection
);
...
...
@@ -200,11 +203,18 @@ class RenderEditableLine extends RenderBox {
// TODO(abarth): ParagraphBuilder#build's argument should be optional.
// TODO(abarth): These min/max values should be the default for ui.Paragraph.
_layoutTemplate
=
builder
.
build
(
new
ui
.
ParagraphStyle
())
..
layout
(
new
ui
.
ParagraphConstraints
(
width:
double
.
INFINITY
));
..
layout
(
new
ui
.
ParagraphConstraints
(
width:
_maxContentWidth
));
}
return
_layoutTemplate
.
height
;
}
bool
_multiline
;
double
get
_maxContentWidth
{
return
_multiline
?
constraints
.
maxWidth
-
(
_kCaretGap
+
_kCaretWidth
)
:
double
.
INFINITY
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
return
_preferredHeight
;
...
...
@@ -274,10 +284,11 @@ class RenderEditableLine extends RenderBox {
@override
void
performLayout
()
{
Size
oldSize
=
hasSize
?
size
:
null
;
size
=
new
Size
(
constraints
.
maxWidth
,
constraints
.
constrainHeight
(
_preferredHeight
)
);
_caretPrototype
=
new
Rect
.
fromLTWH
(
0.0
,
_kCaretHeightOffset
,
_kCaretWidth
,
size
.
h
eight
-
2.0
*
_kCaretHeightOffset
);
double
lineHeight
=
constraints
.
constrainHeight
(
_preferredHeight
);
_caretPrototype
=
new
Rect
.
fromLTWH
(
0.0
,
_kCaretHeightOffset
,
_kCaretWidth
,
lineH
eight
-
2.0
*
_kCaretHeightOffset
);
_selectionRects
=
null
;
_textPainter
.
layout
();
_textPainter
.
layout
(
maxWidth:
_maxContentWidth
);
size
=
new
Size
(
constraints
.
maxWidth
,
constraints
.
constrainHeight
(
math
.
max
(
lineHeight
,
_textPainter
.
height
)));
Size
contentSize
=
new
Size
(
_textPainter
.
width
+
_kCaretGap
+
_kCaretWidth
,
_textPainter
.
height
);
if
(
onPaintOffsetUpdateNeeded
!=
null
&&
(
size
!=
oldSize
||
contentSize
!=
_contentSize
))
onPaintOffsetUpdateNeeded
(
new
ViewportDimensions
(
containerSize:
size
,
contentSize:
contentSize
));
...
...
packages/flutter/lib/src/widgets/editable.dart
View file @
2fd78dcc
...
...
@@ -156,6 +156,8 @@ class InputValue {
///
/// This control is not intended to be used directly. Instead, consider using
/// [Input], which provides focus management and material design.
//
// TODO(mpcomplete): rename RawInput since it can span multiple lines.
class
RawInputLine
extends
Scrollable
{
/// Creates a basic single-line input control.
///
...
...
@@ -168,9 +170,9 @@ class RawInputLine extends Scrollable {
this
.
style
,
this
.
cursorColor
,
this
.
textScaleFactor
,
this
.
multiline
,
this
.
selectionColor
,
this
.
selectionHandleBuilder
,
this
.
selectionToolbarBuilder
,
this
.
selectionControls
,
@required
this
.
platform
,
this
.
keyboardType
,
this
.
onChanged
,
...
...
@@ -206,16 +208,15 @@ class RawInputLine extends Scrollable {
/// The color to use when painting the cursor.
final
Color
cursorColor
;
/// True if the text should wrap and span multiple lines, false if it should
/// stay on a single line and scroll when overflowed.
final
bool
multiline
;
/// The color to use when painting the selection.
final
Color
selectionColor
;
/// Optional builder function for a widget that controls the boundary of a
/// text selection.
final
TextSelectionHandleBuilder
selectionHandleBuilder
;
/// Optional builder function for a set of controls for working with a
/// text selection (e.g. copy and paste).
final
TextSelectionToolbarBuilder
selectionToolbarBuilder
;
/// Optional delegate for building the text selection handles and toolbar.
final
TextSelectionControls
selectionControls
;
/// The platform whose behavior should be approximated, in particular
/// for scroll physics. (See [ScrollBehavior.platform].)
...
...
@@ -356,15 +357,14 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
_selectionOverlay
=
null
;
}
if
(
config
.
selection
HandleBuilder
!=
null
)
{
if
(
config
.
selection
Controls
!=
null
)
{
_selectionOverlay
=
new
TextSelectionOverlay
(
input:
newInput
,
context:
context
,
debugRequiredFor:
config
,
renderObject:
renderObject
,
onSelectionOverlayChanged:
_handleSelectionOverlayChanged
,
handleBuilder:
config
.
selectionHandleBuilder
,
toolbarBuilder:
config
.
selectionToolbarBuilder
selectionControls:
config
.
selectionControls
,
);
if
(
newInput
.
text
.
isNotEmpty
||
longPress
)
_selectionOverlay
.
showHandles
();
...
...
@@ -443,6 +443,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
style:
config
.
style
,
cursorColor:
config
.
cursorColor
,
showCursor:
_showCursor
,
multiline:
config
.
multiline
,
selectionColor:
config
.
selectionColor
,
textScaleFactor:
config
.
textScaleFactor
??
MediaQuery
.
of
(
context
).
textScaleFactor
,
hideText:
config
.
hideText
,
...
...
@@ -460,6 +461,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this
.
style
,
this
.
cursorColor
,
this
.
showCursor
,
this
.
multiline
,
this
.
selectionColor
,
this
.
textScaleFactor
,
this
.
hideText
,
...
...
@@ -472,6 +474,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
final
TextStyle
style
;
final
Color
cursorColor
;
final
bool
showCursor
;
final
bool
multiline
;
final
Color
selectionColor
;
final
double
textScaleFactor
;
final
bool
hideText
;
...
...
@@ -485,6 +488,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
text:
_styledTextSpan
,
cursorColor:
cursorColor
,
showCursor:
showCursor
,
multiline:
multiline
,
selectionColor:
selectionColor
,
textScaleFactor:
textScaleFactor
,
selection:
value
.
selection
,
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
2fd78dcc
...
...
@@ -39,15 +39,6 @@ enum TextSelectionHandleType {
collapsed
,
}
/// Builds a selection handle of the given type.
typedef
Widget
TextSelectionHandleBuilder
(
BuildContext
context
,
TextSelectionHandleType
type
);
/// Builds a toolbar near a text selection.
///
/// Typically displays buttons for copying and pasting text.
// TODO(mpcomplete): A single position is probably insufficient.
typedef
Widget
TextSelectionToolbarBuilder
(
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
);
/// The text position that a give selection handle manipulates. Dragging the
/// [start] handle always moves the [start]/[baseOffset] of the selection.
enum
_TextSelectionHandlePosition
{
start
,
end
}
...
...
@@ -65,6 +56,22 @@ abstract class TextSelectionDelegate {
void
hideToolbar
();
}
// An interface for building the selection UI, to be provided by the
// implementor of the toolbar widget.
abstract
class
TextSelectionControls
{
/// Builds a selection handle of the given type.
Widget
buildHandle
(
BuildContext
context
,
TextSelectionHandleType
type
);
/// Builds a toolbar near a text selection.
///
/// Typically displays buttons for copying and pasting text.
// TODO(mpcomplete): A single position is probably insufficient.
Widget
buildToolbar
(
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
);
/// Returns the size of the selection handle.
Size
get
handleSize
;
}
/// An object that manages a pair of text selection handles.
///
/// The selection handles are displayed in the [Overlay] that most closely
...
...
@@ -79,8 +86,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
this
.
debugRequiredFor
,
this
.
renderObject
,
this
.
onSelectionOverlayChanged
,
this
.
handleBuilder
,
this
.
toolbarBuilder
this
.
selectionControls
,
}):
_input
=
input
{
assert
(
context
!=
null
);
final
OverlayState
overlay
=
Overlay
.
of
(
context
);
...
...
@@ -109,16 +115,8 @@ class TextSelectionOverlay implements TextSelectionDelegate {
/// will be called with a new input value with an updated selection.
final
ValueChanged
<
InputValue
>
onSelectionOverlayChanged
;
/// Builds the selection handles.
///
/// The selection handles let the user adjust which portion of the text is
/// selected.
final
TextSelectionHandleBuilder
handleBuilder
;
/// Builds a toolbar to display near the selection.
///
/// The toolbar typically contains buttons for copying and pasting text.
final
TextSelectionToolbarBuilder
toolbarBuilder
;
/// Builds text selection handles and toolbar.
final
TextSelectionControls
selectionControls
;
/// Controls the fade-in animations.
static
const
Duration
_kFadeDuration
=
const
Duration
(
milliseconds:
150
);
...
...
@@ -208,7 +206,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
Widget
_buildHandle
(
BuildContext
context
,
_TextSelectionHandlePosition
position
)
{
if
((
_selection
.
isCollapsed
&&
position
==
_TextSelectionHandlePosition
.
end
)
||
handleBuilder
==
null
)
selectionControls
==
null
)
return
new
Container
();
// hide the second handle when collapsed
return
new
FadeTransition
(
...
...
@@ -218,14 +216,14 @@ class TextSelectionOverlay implements TextSelectionDelegate {
onSelectionHandleTapped:
_handleSelectionHandleTapped
,
renderObject:
renderObject
,
selection:
_selection
,
builder:
handleBuilder
,
selectionControls:
selectionControls
,
position:
position
)
);
}
Widget
_buildToolbar
(
BuildContext
context
)
{
if
(
toolbarBuilder
==
null
)
if
(
selectionControls
==
null
)
return
new
Container
();
// Find the horizontal midpoint, just above the selected text.
...
...
@@ -239,7 +237,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
return
new
FadeTransition
(
opacity:
_toolbarOpacity
,
child:
toolbarBuilde
r
(
context
,
midpoint
,
this
)
child:
selectionControls
.
buildToolba
r
(
context
,
midpoint
,
this
)
);
}
...
...
@@ -283,7 +281,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
this
.
renderObject
,
this
.
onSelectionHandleChanged
,
this
.
onSelectionHandleTapped
,
this
.
builder
this
.
selectionControls
})
:
super
(
key:
key
);
final
TextSelection
selection
;
...
...
@@ -291,7 +289,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
final
RenderEditableLine
renderObject
;
final
ValueChanged
<
TextSelection
>
onSelectionHandleChanged
;
final
VoidCallback
onSelectionHandleTapped
;
final
TextSelection
HandleBuilder
builder
;
final
TextSelection
Controls
selectionControls
;
@override
_TextSelectionHandleOverlayState
createState
()
=>
new
_TextSelectionHandleOverlayState
();
...
...
@@ -301,7 +299,7 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
Point
_dragPosition
;
void
_handleDragStart
(
DragStartDetails
details
)
{
_dragPosition
=
details
.
globalPosition
;
_dragPosition
=
details
.
globalPosition
+
new
Offset
(
0.0
,
-
config
.
selectionControls
.
handleSize
.
height
)
;
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
...
...
@@ -360,15 +358,15 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
}
return
new
GestureDetector
(
on
HorizontalDrag
Start:
_handleDragStart
,
on
HorizontalDrag
Update:
_handleDragUpdate
,
on
Pan
Start:
_handleDragStart
,
on
Pan
Update:
_handleDragUpdate
,
onTap:
_handleTap
,
child:
new
Stack
(
children:
<
Widget
>[
new
Positioned
(
left:
point
.
x
,
top:
point
.
y
,
child:
config
.
builder
(
context
,
type
)
child:
config
.
selectionControls
.
buildHandle
(
context
,
type
)
)
]
)
...
...
packages/flutter/test/widget/input_test.dart
View file @
2fd78dcc
...
...
@@ -436,4 +436,132 @@ void main() {
// End the test here to ensure the animation is properly disposed of.
});
testWidgets
(
'Multiline text will wrap'
,
(
WidgetTester
tester
)
async
{
GlobalKey
inputKey
=
new
GlobalKey
();
InputValue
inputValue
=
InputValue
.
empty
;
Widget
builder
()
{
return
new
Center
(
child:
new
Material
(
child:
new
Input
(
value:
inputValue
,
key:
inputKey
,
style:
const
TextStyle
(
color:
Colors
.
black
,
fontSize:
34.0
),
multiline:
true
,
hintText:
'Placeholder'
,
onChanged:
(
InputValue
value
)
{
inputValue
=
value
;
}
)
)
);
}
await
tester
.
pumpWidget
(
builder
());
RenderBox
findInputBox
()
=>
tester
.
renderObject
(
find
.
byKey
(
inputKey
));
RenderBox
inputBox
=
findInputBox
();
Size
emptyInputSize
=
inputBox
.
size
;
enterText
(
'This is a long line of text that will wrap to multiple lines.'
);
await
tester
.
pumpWidget
(
builder
());
expect
(
findInputBox
(),
equals
(
inputBox
));
expect
(
inputBox
.
size
,
greaterThan
(
emptyInputSize
));
enterText
(
'No wrapping here.'
);
await
tester
.
pumpWidget
(
builder
());
expect
(
findInputBox
(),
equals
(
inputBox
));
expect
(
inputBox
.
size
,
equals
(
emptyInputSize
));
});
testWidgets
(
'Can drag handles to change selection in multiline'
,
(
WidgetTester
tester
)
async
{
GlobalKey
inputKey
=
new
GlobalKey
();
InputValue
inputValue
=
InputValue
.
empty
;
Widget
builder
()
{
return
new
Overlay
(
initialEntries:
<
OverlayEntry
>[
new
OverlayEntry
(
builder:
(
BuildContext
context
)
{
return
new
Center
(
child:
new
Material
(
child:
new
Input
(
value:
inputValue
,
key:
inputKey
,
style:
const
TextStyle
(
color:
Colors
.
black
,
fontSize:
34.0
),
multiline:
true
,
onChanged:
(
InputValue
value
)
{
inputValue
=
value
;
}
)
)
);
}
)
]
);
}
await
tester
.
pumpWidget
(
builder
());
String
testValue
=
'First line of text is here abcdef ghijkl mnopqrst. Second line of text goes until abcdef ghijkl mnopq. Third line of stuff.'
;
String
cutValue
=
'First line of stuff.'
;
enterText
(
testValue
);
await
tester
.
pumpWidget
(
builder
());
// Check that the text spans multiple lines.
Point
firstPos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'First'
));
Point
secondPos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'Second'
));
Point
thirdPos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'Third'
));
expect
(
firstPos
.
x
,
secondPos
.
x
);
expect
(
firstPos
.
x
,
thirdPos
.
x
);
expect
(
firstPos
.
y
,
lessThan
(
secondPos
.
y
));
expect
(
secondPos
.
y
,
lessThan
(
thirdPos
.
y
));
// Long press the 'n' in 'until' to select the word.
Point
untilPos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'until'
)+
1
);
TestGesture
gesture
=
await
tester
.
startGesture
(
untilPos
,
pointer:
7
);
await
tester
.
pump
(
const
Duration
(
seconds:
2
));
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
inputValue
.
selection
.
baseOffset
,
76
);
expect
(
inputValue
.
selection
.
extentOffset
,
81
);
RenderEditableLine
renderLine
=
findRenderEditableLine
(
tester
);
List
<
TextSelectionPoint
>
endpoints
=
renderLine
.
getEndpointsForSelection
(
inputValue
.
selection
);
expect
(
endpoints
.
length
,
2
);
// Drag the right handle to the third line, just after 'Third'.
Point
handlePos
=
endpoints
[
1
].
point
+
new
Offset
(
1.0
,
1.0
);
Point
newHandlePos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'Third'
)
+
5
);
gesture
=
await
tester
.
startGesture
(
handlePos
,
pointer:
7
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
newHandlePos
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pump
();
expect
(
inputValue
.
selection
.
baseOffset
,
76
);
expect
(
inputValue
.
selection
.
extentOffset
,
108
);
// Drag the left handle to the first line, just after 'First'.
handlePos
=
endpoints
[
0
].
point
+
new
Offset
(-
1.0
,
1.0
);
newHandlePos
=
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'First'
)
+
5
);
gesture
=
await
tester
.
startGesture
(
handlePos
,
pointer:
7
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
newHandlePos
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpWidget
(
builder
());
expect
(
inputValue
.
selection
.
baseOffset
,
5
);
expect
(
inputValue
.
selection
.
extentOffset
,
108
);
await
tester
.
tap
(
find
.
text
(
'CUT'
));
await
tester
.
pumpWidget
(
builder
());
expect
(
inputValue
.
selection
.
isCollapsed
,
true
);
expect
(
inputValue
.
text
,
cutValue
);
});
}
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