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
Show 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> {
...
@@ -94,6 +94,12 @@ class TextFieldDemoState extends State<TextFieldDemo> {
validator:
_validatePhoneNumber
validator:
_validatePhoneNumber
)
)
),
),
new
Input
(
hintText:
'Tell us about yourself (optional)'
,
labelText:
'Life story'
,
multiline:
true
,
formField:
new
FormField
<
String
>()
),
new
Row
(
new
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
children:
<
Widget
>[
...
...
packages/flutter/lib/src/material/input.dart
View file @
2fd78dcc
...
@@ -43,6 +43,7 @@ class Input extends StatefulWidget {
...
@@ -43,6 +43,7 @@ class Input extends StatefulWidget {
this
.
hideText
:
false
,
this
.
hideText
:
false
,
this
.
isDense
:
false
,
this
.
isDense
:
false
,
this
.
autofocus
:
false
,
this
.
autofocus
:
false
,
this
.
multiline
:
false
,
this
.
formField
,
this
.
formField
,
this
.
onChanged
,
this
.
onChanged
,
this
.
onSubmitted
this
.
onSubmitted
...
@@ -84,6 +85,10 @@ class Input extends StatefulWidget {
...
@@ -84,6 +85,10 @@ class Input extends StatefulWidget {
/// Whether this input field should focus itself is nothing else is already focused.
/// Whether this input field should focus itself is nothing else is already focused.
final
bool
autofocus
;
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.
/// Form-specific data, required if this Input is part of a Form.
final
FormField
<
String
>
formField
;
final
FormField
<
String
>
formField
;
...
@@ -205,10 +210,10 @@ class _InputState extends State<Input> {
...
@@ -205,10 +210,10 @@ class _InputState extends State<Input> {
focusKey:
focusKey
,
focusKey:
focusKey
,
style:
textStyle
,
style:
textStyle
,
hideText:
config
.
hideText
,
hideText:
config
.
hideText
,
multiline:
config
.
multiline
,
cursorColor:
themeData
.
textSelectionColor
,
cursorColor:
themeData
.
textSelectionColor
,
selectionColor:
themeData
.
textSelectionColor
,
selectionColor:
themeData
.
textSelectionColor
,
selectionHandleBuilder:
buildTextSelectionHandle
,
selectionControls:
materialTextSelectionControls
,
selectionToolbarBuilder:
buildTextSelectionToolbar
,
platform:
Theme
.
of
(
context
).
platform
,
platform:
Theme
.
of
(
context
).
platform
,
keyboardType:
config
.
keyboardType
,
keyboardType:
config
.
keyboardType
,
onChanged:
onChanged
,
onChanged:
onChanged
,
...
...
packages/flutter/lib/src/material/text_selection.dart
View file @
2fd78dcc
...
@@ -143,8 +143,13 @@ class _TextSelectionHandlePainter extends CustomPainter {
...
@@ -143,8 +143,13 @@ class _TextSelectionHandlePainter extends CustomPainter {
}
}
}
}
/// Builder for material-style copy/paste text selection toolbar.
class
_MaterialTextSelectionControls
extends
TextSelectionControls
{
Widget
buildTextSelectionToolbar
(
@override
Size
handleSize
=
const
Size
(
_kHandleSize
,
_kHandleSize
);
/// Builder for material-style copy/paste text selection toolbar.
@override
Widget
buildToolbar
(
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
)
{
BuildContext
context
,
Point
position
,
TextSelectionDelegate
delegate
)
{
final
Size
screenSize
=
MediaQuery
.
of
(
context
).
size
;
final
Size
screenSize
=
MediaQuery
.
of
(
context
).
size
;
return
new
ConstrainedBox
(
return
new
ConstrainedBox
(
...
@@ -154,11 +159,11 @@ Widget buildTextSelectionToolbar(
...
@@ -154,11 +159,11 @@ Widget buildTextSelectionToolbar(
child:
new
_TextSelectionToolbar
(
delegate
)
child:
new
_TextSelectionToolbar
(
delegate
)
)
)
);
);
}
}
/// Builder for material-style text selection handles.
/// Builder for material-style text selection handles.
Widget
buildTextSelectionHandle
(
@override
BuildContext
context
,
TextSelectionHandleType
type
)
{
Widget
buildHandle
(
BuildContext
context
,
TextSelectionHandleType
type
)
{
Widget
handle
=
new
SizedBox
(
Widget
handle
=
new
SizedBox
(
width:
_kHandleSize
,
width:
_kHandleSize
,
height:
_kHandleSize
,
height:
_kHandleSize
,
...
@@ -188,4 +193,7 @@ Widget buildTextSelectionHandle(
...
@@ -188,4 +193,7 @@ Widget buildTextSelectionHandle(
}
}
assert
(
type
!=
null
);
assert
(
type
!=
null
);
return
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 {
...
@@ -219,7 +219,7 @@ class TextPainter {
ui
.
TextBox
box
=
boxes
[
0
];
ui
.
TextBox
box
=
boxes
[
0
];
double
caretEnd
=
box
.
end
;
double
caretEnd
=
box
.
end
;
double
dx
=
box
.
direction
==
TextDirection
.
rtl
?
caretEnd
:
caretEnd
-
caretPrototype
.
width
;
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
)
{
Offset
_getOffsetFromDownstream
(
int
offset
,
Rect
caretPrototype
)
{
...
@@ -229,7 +229,7 @@ class TextPainter {
...
@@ -229,7 +229,7 @@ class TextPainter {
ui
.
TextBox
box
=
boxes
[
0
];
ui
.
TextBox
box
=
boxes
[
0
];
double
caretStart
=
box
.
start
;
double
caretStart
=
box
.
start
;
double
dx
=
box
.
direction
==
TextDirection
.
rtl
?
caretStart
-
caretPrototype
.
width
:
caretStart
;
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.
/// Returns the offset at which to paint the caret.
...
...
packages/flutter/lib/src/rendering/editable_line.dart
View file @
2fd78dcc
...
@@ -3,6 +3,7 @@
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:ui'
as
ui
show
Paragraph
,
ParagraphBuilder
,
ParagraphConstraints
,
ParagraphStyle
,
TextBox
;
import
'dart:ui'
as
ui
show
Paragraph
,
ParagraphBuilder
,
ParagraphConstraints
,
ParagraphStyle
,
TextBox
;
import
'dart:math'
as
math
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/gestures.dart'
;
...
@@ -43,15 +44,17 @@ class RenderEditableLine extends RenderBox {
...
@@ -43,15 +44,17 @@ class RenderEditableLine extends RenderBox {
TextSpan
text
,
TextSpan
text
,
Color
cursorColor
,
Color
cursorColor
,
bool
showCursor:
false
,
bool
showCursor:
false
,
bool
multiline:
false
,
Color
selectionColor
,
Color
selectionColor
,
double
textScaleFactor:
1.0
,
double
textScaleFactor:
1.0
,
TextSelection
selection
,
TextSelection
selection
,
this
.
onSelectionChanged
,
this
.
onSelectionChanged
,
Offset
paintOffset:
Offset
.
zero
,
Offset
paintOffset:
Offset
.
zero
,
this
.
onPaintOffsetUpdateNeeded
this
.
onPaintOffsetUpdateNeeded
,
})
:
_textPainter
=
new
TextPainter
(
text:
text
,
textScaleFactor:
textScaleFactor
),
})
:
_textPainter
=
new
TextPainter
(
text:
text
,
textScaleFactor:
textScaleFactor
),
_cursorColor
=
cursorColor
,
_cursorColor
=
cursorColor
,
_showCursor
=
showCursor
,
_showCursor
=
showCursor
,
_multiline
=
multiline
,
_selection
=
selection
,
_selection
=
selection
,
_paintOffset
=
paintOffset
{
_paintOffset
=
paintOffset
{
assert
(!
showCursor
||
cursorColor
!=
null
);
assert
(!
showCursor
||
cursorColor
!=
null
);
...
@@ -163,14 +166,14 @@ class RenderEditableLine extends RenderBox {
...
@@ -163,14 +166,14 @@ class RenderEditableLine extends RenderBox {
// TODO(mpcomplete): We should be more disciplined about when we dirty the
// 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
// layout state of the text painter so that we can know that the layout is
// clean at this point.
// clean at this point.
_textPainter
.
layout
();
_textPainter
.
layout
(
maxWidth:
_maxContentWidth
);
Offset
offset
=
_paintOffset
;
Offset
offset
=
_paintOffset
;
if
(
selection
.
isCollapsed
)
{
if
(
selection
.
isCollapsed
)
{
// TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary.
// TODO(mpcomplete): This doesn't work well at an RTL/LTR boundary.
Offset
caretOffset
=
_textPainter
.
getOffsetForCaret
(
selection
.
extent
,
_caretPrototype
);
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
)];
return
<
TextSelectionPoint
>[
new
TextSelectionPoint
(
localToGlobal
(
start
),
null
)];
}
else
{
}
else
{
List
<
ui
.
TextBox
>
boxes
=
_textPainter
.
getBoxesForSelection
(
selection
);
List
<
ui
.
TextBox
>
boxes
=
_textPainter
.
getBoxesForSelection
(
selection
);
...
@@ -200,11 +203,18 @@ class RenderEditableLine extends RenderBox {
...
@@ -200,11 +203,18 @@ class RenderEditableLine extends RenderBox {
// TODO(abarth): ParagraphBuilder#build's argument should be optional.
// TODO(abarth): ParagraphBuilder#build's argument should be optional.
// TODO(abarth): These min/max values should be the default for ui.Paragraph.
// TODO(abarth): These min/max values should be the default for ui.Paragraph.
_layoutTemplate
=
builder
.
build
(
new
ui
.
ParagraphStyle
())
_layoutTemplate
=
builder
.
build
(
new
ui
.
ParagraphStyle
())
..
layout
(
new
ui
.
ParagraphConstraints
(
width:
double
.
INFINITY
));
..
layout
(
new
ui
.
ParagraphConstraints
(
width:
_maxContentWidth
));
}
}
return
_layoutTemplate
.
height
;
return
_layoutTemplate
.
height
;
}
}
bool
_multiline
;
double
get
_maxContentWidth
{
return
_multiline
?
constraints
.
maxWidth
-
(
_kCaretGap
+
_kCaretWidth
)
:
double
.
INFINITY
;
}
@override
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
double
computeMinIntrinsicHeight
(
double
width
)
{
return
_preferredHeight
;
return
_preferredHeight
;
...
@@ -274,10 +284,11 @@ class RenderEditableLine extends RenderBox {
...
@@ -274,10 +284,11 @@ class RenderEditableLine extends RenderBox {
@override
@override
void
performLayout
()
{
void
performLayout
()
{
Size
oldSize
=
hasSize
?
size
:
null
;
Size
oldSize
=
hasSize
?
size
:
null
;
size
=
new
Size
(
constraints
.
maxWidth
,
constraints
.
constrainHeight
(
_preferredHeight
)
);
double
lineHeight
=
constraints
.
constrainHeight
(
_preferredHeight
);
_caretPrototype
=
new
Rect
.
fromLTWH
(
0.0
,
_kCaretHeightOffset
,
_kCaretWidth
,
size
.
h
eight
-
2.0
*
_kCaretHeightOffset
);
_caretPrototype
=
new
Rect
.
fromLTWH
(
0.0
,
_kCaretHeightOffset
,
_kCaretWidth
,
lineH
eight
-
2.0
*
_kCaretHeightOffset
);
_selectionRects
=
null
;
_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
);
Size
contentSize
=
new
Size
(
_textPainter
.
width
+
_kCaretGap
+
_kCaretWidth
,
_textPainter
.
height
);
if
(
onPaintOffsetUpdateNeeded
!=
null
&&
(
size
!=
oldSize
||
contentSize
!=
_contentSize
))
if
(
onPaintOffsetUpdateNeeded
!=
null
&&
(
size
!=
oldSize
||
contentSize
!=
_contentSize
))
onPaintOffsetUpdateNeeded
(
new
ViewportDimensions
(
containerSize:
size
,
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 {
...
@@ -156,6 +156,8 @@ class InputValue {
///
///
/// This control is not intended to be used directly. Instead, consider using
/// This control is not intended to be used directly. Instead, consider using
/// [Input], which provides focus management and material design.
/// [Input], which provides focus management and material design.
//
// TODO(mpcomplete): rename RawInput since it can span multiple lines.
class
RawInputLine
extends
Scrollable
{
class
RawInputLine
extends
Scrollable
{
/// Creates a basic single-line input control.
/// Creates a basic single-line input control.
///
///
...
@@ -168,9 +170,9 @@ class RawInputLine extends Scrollable {
...
@@ -168,9 +170,9 @@ class RawInputLine extends Scrollable {
this
.
style
,
this
.
style
,
this
.
cursorColor
,
this
.
cursorColor
,
this
.
textScaleFactor
,
this
.
textScaleFactor
,
this
.
multiline
,
this
.
selectionColor
,
this
.
selectionColor
,
this
.
selectionHandleBuilder
,
this
.
selectionControls
,
this
.
selectionToolbarBuilder
,
@required
this
.
platform
,
@required
this
.
platform
,
this
.
keyboardType
,
this
.
keyboardType
,
this
.
onChanged
,
this
.
onChanged
,
...
@@ -206,16 +208,15 @@ class RawInputLine extends Scrollable {
...
@@ -206,16 +208,15 @@ class RawInputLine extends Scrollable {
/// The color to use when painting the cursor.
/// The color to use when painting the cursor.
final
Color
cursorColor
;
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.
/// The color to use when painting the selection.
final
Color
selectionColor
;
final
Color
selectionColor
;
/// Optional builder function for a widget that controls the boundary of a
/// Optional delegate for building the text selection handles and toolbar.
/// text selection.
final
TextSelectionControls
selectionControls
;
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
;
/// The platform whose behavior should be approximated, in particular
/// The platform whose behavior should be approximated, in particular
/// for scroll physics. (See [ScrollBehavior.platform].)
/// for scroll physics. (See [ScrollBehavior.platform].)
...
@@ -356,15 +357,14 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
...
@@ -356,15 +357,14 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
_selectionOverlay
=
null
;
_selectionOverlay
=
null
;
}
}
if
(
config
.
selection
HandleBuilder
!=
null
)
{
if
(
config
.
selection
Controls
!=
null
)
{
_selectionOverlay
=
new
TextSelectionOverlay
(
_selectionOverlay
=
new
TextSelectionOverlay
(
input:
newInput
,
input:
newInput
,
context:
context
,
context:
context
,
debugRequiredFor:
config
,
debugRequiredFor:
config
,
renderObject:
renderObject
,
renderObject:
renderObject
,
onSelectionOverlayChanged:
_handleSelectionOverlayChanged
,
onSelectionOverlayChanged:
_handleSelectionOverlayChanged
,
handleBuilder:
config
.
selectionHandleBuilder
,
selectionControls:
config
.
selectionControls
,
toolbarBuilder:
config
.
selectionToolbarBuilder
);
);
if
(
newInput
.
text
.
isNotEmpty
||
longPress
)
if
(
newInput
.
text
.
isNotEmpty
||
longPress
)
_selectionOverlay
.
showHandles
();
_selectionOverlay
.
showHandles
();
...
@@ -443,6 +443,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
...
@@ -443,6 +443,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
style:
config
.
style
,
style:
config
.
style
,
cursorColor:
config
.
cursorColor
,
cursorColor:
config
.
cursorColor
,
showCursor:
_showCursor
,
showCursor:
_showCursor
,
multiline:
config
.
multiline
,
selectionColor:
config
.
selectionColor
,
selectionColor:
config
.
selectionColor
,
textScaleFactor:
config
.
textScaleFactor
??
MediaQuery
.
of
(
context
).
textScaleFactor
,
textScaleFactor:
config
.
textScaleFactor
??
MediaQuery
.
of
(
context
).
textScaleFactor
,
hideText:
config
.
hideText
,
hideText:
config
.
hideText
,
...
@@ -460,6 +461,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
...
@@ -460,6 +461,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this
.
style
,
this
.
style
,
this
.
cursorColor
,
this
.
cursorColor
,
this
.
showCursor
,
this
.
showCursor
,
this
.
multiline
,
this
.
selectionColor
,
this
.
selectionColor
,
this
.
textScaleFactor
,
this
.
textScaleFactor
,
this
.
hideText
,
this
.
hideText
,
...
@@ -472,6 +474,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
...
@@ -472,6 +474,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
final
TextStyle
style
;
final
TextStyle
style
;
final
Color
cursorColor
;
final
Color
cursorColor
;
final
bool
showCursor
;
final
bool
showCursor
;
final
bool
multiline
;
final
Color
selectionColor
;
final
Color
selectionColor
;
final
double
textScaleFactor
;
final
double
textScaleFactor
;
final
bool
hideText
;
final
bool
hideText
;
...
@@ -485,6 +488,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
...
@@ -485,6 +488,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
text:
_styledTextSpan
,
text:
_styledTextSpan
,
cursorColor:
cursorColor
,
cursorColor:
cursorColor
,
showCursor:
showCursor
,
showCursor:
showCursor
,
multiline:
multiline
,
selectionColor:
selectionColor
,
selectionColor:
selectionColor
,
textScaleFactor:
textScaleFactor
,
textScaleFactor:
textScaleFactor
,
selection:
value
.
selection
,
selection:
value
.
selection
,
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
2fd78dcc
...
@@ -39,15 +39,6 @@ enum TextSelectionHandleType {
...
@@ -39,15 +39,6 @@ enum TextSelectionHandleType {
collapsed
,
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
/// The text position that a give selection handle manipulates. Dragging the
/// [start] handle always moves the [start]/[baseOffset] of the selection.
/// [start] handle always moves the [start]/[baseOffset] of the selection.
enum
_TextSelectionHandlePosition
{
start
,
end
}
enum
_TextSelectionHandlePosition
{
start
,
end
}
...
@@ -65,6 +56,22 @@ abstract class TextSelectionDelegate {
...
@@ -65,6 +56,22 @@ abstract class TextSelectionDelegate {
void
hideToolbar
();
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.
/// An object that manages a pair of text selection handles.
///
///
/// The selection handles are displayed in the [Overlay] that most closely
/// The selection handles are displayed in the [Overlay] that most closely
...
@@ -79,8 +86,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
...
@@ -79,8 +86,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
this
.
debugRequiredFor
,
this
.
debugRequiredFor
,
this
.
renderObject
,
this
.
renderObject
,
this
.
onSelectionOverlayChanged
,
this
.
onSelectionOverlayChanged
,
this
.
handleBuilder
,
this
.
selectionControls
,
this
.
toolbarBuilder
}):
_input
=
input
{
}):
_input
=
input
{
assert
(
context
!=
null
);
assert
(
context
!=
null
);
final
OverlayState
overlay
=
Overlay
.
of
(
context
);
final
OverlayState
overlay
=
Overlay
.
of
(
context
);
...
@@ -109,16 +115,8 @@ class TextSelectionOverlay implements TextSelectionDelegate {
...
@@ -109,16 +115,8 @@ class TextSelectionOverlay implements TextSelectionDelegate {
/// will be called with a new input value with an updated selection.
/// will be called with a new input value with an updated selection.
final
ValueChanged
<
InputValue
>
onSelectionOverlayChanged
;
final
ValueChanged
<
InputValue
>
onSelectionOverlayChanged
;
/// Builds the selection handles.
/// Builds text selection handles and toolbar.
///
final
TextSelectionControls
selectionControls
;
/// 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
;
/// Controls the fade-in animations.
/// Controls the fade-in animations.
static
const
Duration
_kFadeDuration
=
const
Duration
(
milliseconds:
150
);
static
const
Duration
_kFadeDuration
=
const
Duration
(
milliseconds:
150
);
...
@@ -208,7 +206,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
...
@@ -208,7 +206,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
Widget
_buildHandle
(
BuildContext
context
,
_TextSelectionHandlePosition
position
)
{
Widget
_buildHandle
(
BuildContext
context
,
_TextSelectionHandlePosition
position
)
{
if
((
_selection
.
isCollapsed
&&
position
==
_TextSelectionHandlePosition
.
end
)
||
if
((
_selection
.
isCollapsed
&&
position
==
_TextSelectionHandlePosition
.
end
)
||
handleBuilder
==
null
)
selectionControls
==
null
)
return
new
Container
();
// hide the second handle when collapsed
return
new
Container
();
// hide the second handle when collapsed
return
new
FadeTransition
(
return
new
FadeTransition
(
...
@@ -218,14 +216,14 @@ class TextSelectionOverlay implements TextSelectionDelegate {
...
@@ -218,14 +216,14 @@ class TextSelectionOverlay implements TextSelectionDelegate {
onSelectionHandleTapped:
_handleSelectionHandleTapped
,
onSelectionHandleTapped:
_handleSelectionHandleTapped
,
renderObject:
renderObject
,
renderObject:
renderObject
,
selection:
_selection
,
selection:
_selection
,
builder:
handleBuilder
,
selectionControls:
selectionControls
,
position:
position
position:
position
)
)
);
);
}
}
Widget
_buildToolbar
(
BuildContext
context
)
{
Widget
_buildToolbar
(
BuildContext
context
)
{
if
(
toolbarBuilder
==
null
)
if
(
selectionControls
==
null
)
return
new
Container
();
return
new
Container
();
// Find the horizontal midpoint, just above the selected text.
// Find the horizontal midpoint, just above the selected text.
...
@@ -239,7 +237,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
...
@@ -239,7 +237,7 @@ class TextSelectionOverlay implements TextSelectionDelegate {
return
new
FadeTransition
(
return
new
FadeTransition
(
opacity:
_toolbarOpacity
,
opacity:
_toolbarOpacity
,
child:
toolbarBuilde
r
(
context
,
midpoint
,
this
)
child:
selectionControls
.
buildToolba
r
(
context
,
midpoint
,
this
)
);
);
}
}
...
@@ -283,7 +281,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
...
@@ -283,7 +281,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
this
.
renderObject
,
this
.
renderObject
,
this
.
onSelectionHandleChanged
,
this
.
onSelectionHandleChanged
,
this
.
onSelectionHandleTapped
,
this
.
onSelectionHandleTapped
,
this
.
builder
this
.
selectionControls
})
:
super
(
key:
key
);
})
:
super
(
key:
key
);
final
TextSelection
selection
;
final
TextSelection
selection
;
...
@@ -291,7 +289,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
...
@@ -291,7 +289,7 @@ class _TextSelectionHandleOverlay extends StatefulWidget {
final
RenderEditableLine
renderObject
;
final
RenderEditableLine
renderObject
;
final
ValueChanged
<
TextSelection
>
onSelectionHandleChanged
;
final
ValueChanged
<
TextSelection
>
onSelectionHandleChanged
;
final
VoidCallback
onSelectionHandleTapped
;
final
VoidCallback
onSelectionHandleTapped
;
final
TextSelection
HandleBuilder
builder
;
final
TextSelection
Controls
selectionControls
;
@override
@override
_TextSelectionHandleOverlayState
createState
()
=>
new
_TextSelectionHandleOverlayState
();
_TextSelectionHandleOverlayState
createState
()
=>
new
_TextSelectionHandleOverlayState
();
...
@@ -301,7 +299,7 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
...
@@ -301,7 +299,7 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
Point
_dragPosition
;
Point
_dragPosition
;
void
_handleDragStart
(
DragStartDetails
details
)
{
void
_handleDragStart
(
DragStartDetails
details
)
{
_dragPosition
=
details
.
globalPosition
;
_dragPosition
=
details
.
globalPosition
+
new
Offset
(
0.0
,
-
config
.
selectionControls
.
handleSize
.
height
)
;
}
}
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
void
_handleDragUpdate
(
DragUpdateDetails
details
)
{
...
@@ -360,15 +358,15 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
...
@@ -360,15 +358,15 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
}
}
return
new
GestureDetector
(
return
new
GestureDetector
(
on
HorizontalDrag
Start:
_handleDragStart
,
on
Pan
Start:
_handleDragStart
,
on
HorizontalDrag
Update:
_handleDragUpdate
,
on
Pan
Update:
_handleDragUpdate
,
onTap:
_handleTap
,
onTap:
_handleTap
,
child:
new
Stack
(
child:
new
Stack
(
children:
<
Widget
>[
children:
<
Widget
>[
new
Positioned
(
new
Positioned
(
left:
point
.
x
,
left:
point
.
x
,
top:
point
.
y
,
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() {
...
@@ -436,4 +436,132 @@ void main() {
// End the test here to ensure the animation is properly disposed of.
// 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