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
52c55344
Commit
52c55344
authored
Feb 09, 2016
by
Jason Simmons
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Change the text/selection value API of the input field
(see
https://github.com/flutter/flutter/issues/1586
)
parent
c0ef05c3
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
297 additions
and
233 deletions
+297
-233
meal.dart
examples/fitness/lib/meal.dart
+3
-3
measurement.dart
examples/fitness/lib/measurement.dart
+3
-3
settings.dart
examples/fitness/lib/settings.dart
+59
-43
stock_home.dart
examples/stocks/lib/stock_home.dart
+6
-5
card_collection.dart
examples/widgets/card_collection.dart
+8
-6
input.dart
packages/flutter/lib/src/material/input.dart
+16
-94
text_editing.dart
packages/flutter/lib/src/painting/text_editing.dart
+36
-1
editable.dart
packages/flutter/lib/src/widgets/editable.dart
+161
-74
input_test.dart
packages/flutter/test/widget/input_test.dart
+5
-4
No files found.
examples/fitness/lib/meal.dart
View file @
52c55344
...
...
@@ -51,10 +51,10 @@ class MealFragment extends StatefulComponent {
}
class
MealFragmentState
extends
State
<
MealFragment
>
{
String
_description
=
""
;
InputValue
_description
=
InputValue
.
empty
;
void
_handleSave
()
{
config
.
onCreated
(
new
Meal
(
when:
new
DateTime
.
now
(),
description:
_description
));
config
.
onCreated
(
new
Meal
(
when:
new
DateTime
.
now
(),
description:
_description
.
text
));
Navigator
.
pop
(
context
);
}
...
...
@@ -75,7 +75,7 @@ class MealFragmentState extends State<MealFragment> {
);
}
void
_handleDescriptionChanged
(
String
description
)
{
void
_handleDescriptionChanged
(
InputValue
description
)
{
setState
(()
{
_description
=
description
;
});
...
...
examples/fitness/lib/measurement.dart
View file @
52c55344
...
...
@@ -63,13 +63,13 @@ class MeasurementFragment extends StatefulComponent {
}
class
MeasurementFragmentState
extends
State
<
MeasurementFragment
>
{
String
_weight
=
""
;
InputValue
_weight
=
InputValue
.
empty
;
DateTime
_when
=
new
DateTime
.
now
();
void
_handleSave
()
{
double
parsedWeight
;
try
{
parsedWeight
=
double
.
parse
(
_weight
);
parsedWeight
=
double
.
parse
(
_weight
.
text
);
}
on
FormatException
catch
(
e
)
{
print
(
"Exception
$e
"
);
Scaffold
.
of
(
context
).
showSnackBar
(
new
SnackBar
(
...
...
@@ -97,7 +97,7 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
);
}
void
_handleWeightChanged
(
String
weight
)
{
void
_handleWeightChanged
(
InputValue
weight
)
{
setState
(()
{
_weight
=
weight
;
});
...
...
examples/fitness/lib/settings.dart
View file @
52c55344
...
...
@@ -4,6 +4,64 @@
part of
fitness
;
class
_SettingsDialog
extends
StatefulComponent
{
_SettingsDialogState
createState
()
=>
new
_SettingsDialogState
();
}
class
_SettingsDialogState
extends
State
<
_SettingsDialog
>
{
final
GlobalKey
weightGoalKey
=
new
GlobalKey
();
InputValue
_goalWeight
=
InputValue
.
empty
;
void
_handleGoalWeightChanged
(
InputValue
goalWeight
)
{
setState
(()
{
_goalWeight
=
goalWeight
;
});
}
void
_handleGoalWeightSubmitted
(
InputValue
goalWeight
)
{
_goalWeight
=
goalWeight
;
_handleSavePressed
();
}
void
_handleSavePressed
()
{
double
goalWeight
;
try
{
goalWeight
=
double
.
parse
(
_goalWeight
.
text
);
}
on
FormatException
{
goalWeight
=
0.0
;
}
Navigator
.
pop
(
context
,
goalWeight
);
}
Widget
build
(
BuildContext
context
)
{
return
new
Dialog
(
title:
new
Text
(
"Goal Weight"
),
content:
new
Input
(
key:
weightGoalKey
,
value:
_goalWeight
,
autofocus:
true
,
hintText:
'Goal weight in lbs'
,
keyboardType:
KeyboardType
.
number
,
onChanged:
_handleGoalWeightChanged
,
onSubmitted:
_handleGoalWeightSubmitted
),
actions:
<
Widget
>[
new
FlatButton
(
child:
new
Text
(
'CANCEL'
),
onPressed:
()
{
Navigator
.
pop
(
context
);
}
),
new
FlatButton
(
child:
new
Text
(
'SAVE'
),
onPressed:
_handleSavePressed
),
]
);
}
}
typedef
void
SettingsUpdater
(
{
BackupMode
backup
,
double
goalWeight
...
...
@@ -36,52 +94,10 @@ class SettingsFragmentState extends State<SettingsFragment> {
return
"
${config.userData.goalWeight}
"
;
}
static
final
GlobalKey
weightGoalKey
=
new
GlobalKey
();
double
_goalWeight
;
void
_handleGoalWeightChanged
(
String
goalWeight
)
{
// TODO(jackson): Looking for null characters to detect enter key is a hack
if
(
goalWeight
.
endsWith
(
"
\
u{0}"
))
{
Navigator
.
pop
(
context
,
double
.
parse
(
goalWeight
.
replaceAll
(
"
\
u{0}"
,
""
)));
}
else
{
setState
(()
{
try
{
_goalWeight
=
double
.
parse
(
goalWeight
);
}
on
FormatException
{
_goalWeight
=
0.0
;
}
});
}
}
Future
_handleGoalWeightPressed
()
async
{
double
goalWeight
=
await
showDialog
(
context:
context
,
child:
new
Dialog
(
title:
new
Text
(
"Goal Weight"
),
content:
new
Input
(
key:
weightGoalKey
,
autofocus:
true
,
hintText:
'Goal weight in lbs'
,
keyboardType:
KeyboardType
.
number
,
onChanged:
_handleGoalWeightChanged
),
actions:
<
Widget
>[
new
FlatButton
(
child:
new
Text
(
'CANCEL'
),
onPressed:
()
{
Navigator
.
pop
(
context
);
}
),
new
FlatButton
(
child:
new
Text
(
'SAVE'
),
onPressed:
()
{
Navigator
.
pop
(
context
,
_goalWeight
);
}
),
]
)
child:
new
_SettingsDialog
()
);
config
.
updater
(
goalWeight:
goalWeight
);
}
...
...
examples/stocks/lib/stock_home.dart
View file @
52c55344
...
...
@@ -33,14 +33,14 @@ class StockHomeState extends State<StockHome> {
final
GlobalKey
<
ScaffoldState
>
_scaffoldKey
=
new
GlobalKey
<
ScaffoldState
>();
bool
_isSearching
=
false
;
String
_searchQuer
y
;
InputValue
_searchQuery
=
InputValue
.
empt
y
;
void
_handleSearchBegin
()
{
ModalRoute
.
of
(
context
).
addLocalHistoryEntry
(
new
LocalHistoryEntry
(
onRemove:
()
{
setState
(()
{
_isSearching
=
false
;
_searchQuery
=
null
;
_searchQuery
=
InputValue
.
empty
;
});
}
));
...
...
@@ -53,7 +53,7 @@ class StockHomeState extends State<StockHome> {
Navigator
.
pop
(
context
);
}
void
_handleSearchQueryChanged
(
String
query
)
{
void
_handleSearchQueryChanged
(
InputValue
query
)
{
setState
(()
{
_searchQuery
=
query
;
});
...
...
@@ -197,9 +197,9 @@ class StockHomeState extends State<StockHome> {
}
Iterable
<
Stock
>
_filterBySearchQuery
(
Iterable
<
Stock
>
stocks
)
{
if
(
_searchQuery
==
null
)
if
(
_searchQuery
.
text
.
isEmpty
)
return
stocks
;
RegExp
regexp
=
new
RegExp
(
_searchQuery
,
caseSensitive:
false
);
RegExp
regexp
=
new
RegExp
(
_searchQuery
.
text
,
caseSensitive:
false
);
return
stocks
.
where
((
Stock
stock
)
=>
stock
.
symbol
.
contains
(
regexp
));
}
...
...
@@ -254,6 +254,7 @@ class StockHomeState extends State<StockHome> {
tooltip:
'Back'
),
center:
new
Input
(
value:
_searchQuery
,
key:
searchFieldKey
,
autofocus:
true
,
hintText:
'Search stocks'
,
...
...
examples/widgets/card_collection.dart
View file @
52c55344
...
...
@@ -7,12 +7,12 @@ import 'package:flutter/rendering.dart' show debugDumpRenderTree;
class
CardModel
{
CardModel
(
this
.
value
,
this
.
height
)
{
label
=
"Item
$value
"
;
inputValue
=
new
InputValue
(
text:
"Item
$value
"
)
;
}
int
value
;
double
height
;
int
get
color
=>
((
value
%
9
)
+
1
)
*
100
;
String
label
;
InputValue
inputValue
;
Key
get
key
=>
new
ObjectKey
(
this
);
}
...
...
@@ -305,9 +305,11 @@ class CardCollectionState extends State<CardCollection> {
new
Center
(
child:
new
Input
(
key:
new
GlobalObjectKey
(
cardModel
),
initialValue:
cardModel
.
label
,
onChanged:
(
String
value
)
{
cardModel
.
label
=
value
;
value:
cardModel
.
inputValue
,
onChanged:
(
InputValue
value
)
{
setState
(()
{
cardModel
.
inputValue
=
value
;
});
}
)
)
...
...
@@ -317,7 +319,7 @@ class CardCollectionState extends State<CardCollection> {
),
child:
new
Column
(
children:
<
Widget
>[
new
Text
(
cardModel
.
label
)
new
Text
(
cardModel
.
inputValue
.
text
)
],
alignItems:
FlexAlignItems
.
stretch
,
justifyContent:
FlexJustifyContent
.
center
...
...
packages/flutter/lib/src/material/input.dart
View file @
52c55344
...
...
@@ -4,7 +4,6 @@
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:sky_services/editing/editing.mojom.dart'
as
mojom
;
import
'colors.dart'
;
import
'debug.dart'
;
...
...
@@ -17,8 +16,7 @@ export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
class
Input
extends
StatefulComponent
{
Input
({
GlobalKey
key
,
this
.
initialValue
:
''
,
this
.
initialSelection
,
this
.
value
:
InputValue
.
empty
,
this
.
keyboardType
:
KeyboardType
.
text
,
this
.
icon
,
this
.
labelText
,
...
...
@@ -34,11 +32,8 @@ class Input extends StatefulComponent {
assert
(
key
!=
null
);
}
/// The initial editable text for the input field.
final
String
initialValue
;
/// The initial selection for this input field.
final
TextSelection
initialSelection
;
/// The text of the input field.
final
InputValue
value
;
/// The type of keyboard to use for editing the text.
final
KeyboardType
keyboardType
;
...
...
@@ -68,10 +63,10 @@ class Input extends StatefulComponent {
final
bool
autofocus
;
/// Called when the text being edited changes.
final
ValueChanged
<
String
>
onChanged
;
final
ValueChanged
<
InputValue
>
onChanged
;
/// Called when the user indicates that they are done editing the text in the field.
final
ValueChanged
<
String
>
onSubmitted
;
final
ValueChanged
<
InputValue
>
onSubmitted
;
_InputState
createState
()
=>
new
_InputState
();
}
...
...
@@ -80,89 +75,13 @@ const Duration _kTransitionDuration = const Duration(milliseconds: 200);
const
Curve
_kTransitionCurve
=
Curves
.
ease
;
class
_InputState
extends
State
<
Input
>
{
String
_value
;
EditableString
_editableString
;
KeyboardHandle
_keyboardHandle
;
// Used by tests.
EditableString
get
editableValue
=>
_editableString
;
void
initState
()
{
super
.
initState
();
_value
=
config
.
initialValue
;
_editableString
=
new
EditableString
(
text:
_value
,
selection:
config
.
initialSelection
,
onUpdated:
_handleTextUpdated
,
onSubmitted:
_handleTextSubmitted
);
}
void
dispose
()
{
if
(
_isAttachedToKeyboard
)
_keyboardHandle
.
release
();
super
.
dispose
();
}
bool
get
_isAttachedToKeyboard
=>
_keyboardHandle
!=
null
&&
_keyboardHandle
.
attached
;
void
_attachOrDetachKeyboard
(
bool
focused
)
{
if
(
focused
&&
!
_isAttachedToKeyboard
)
{
_keyboardHandle
=
keyboard
.
attach
(
_editableString
.
createStub
(),
new
mojom
.
KeyboardConfiguration
()
..
type
=
config
.
keyboardType
);
_keyboardHandle
.
setEditingState
(
_editableString
.
editingState
);
_keyboardHandle
.
show
();
}
else
if
(!
focused
&&
_isAttachedToKeyboard
)
{
_keyboardHandle
.
release
();
_keyboardHandle
=
null
;
_editableString
.
didDetachKeyboard
();
}
}
void
_requestKeyboard
()
{
if
(
Focus
.
at
(
context
))
{
assert
(
_isAttachedToKeyboard
);
_keyboardHandle
.
show
();
}
else
{
Focus
.
moveTo
(
config
.
key
);
// we'll get told to rebuild and we'll take care of the keyboard then
}
}
void
_handleTextUpdated
()
{
if
(
_value
!=
_editableString
.
text
)
{
setState
(()
{
_value
=
_editableString
.
text
;
});
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
_value
);
}
}
void
_handleTextSubmitted
()
{
Focus
.
clear
(
context
);
if
(
config
.
onSubmitted
!=
null
)
config
.
onSubmitted
(
_value
);
}
void
_handleSelectionChanged
(
TextSelection
selection
)
{
if
(
_isAttachedToKeyboard
)
{
_editableString
.
setSelection
(
selection
);
_keyboardHandle
.
setEditingState
(
_editableString
.
editingState
);
}
else
{
_editableString
.
setSelection
(
selection
);
_requestKeyboard
();
}
}
GlobalKey
<
RawInputLineState
>
_rawInputLineKey
=
new
GlobalKey
<
RawInputLineState
>();
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
ThemeData
themeData
=
Theme
.
of
(
context
);
bool
focused
=
Focus
.
at
(
context
,
autofocus:
config
.
autofocus
);
_attachOrDetachKeyboard
(
focused
);
TextStyle
textStyle
=
config
.
style
??
themeData
.
text
.
subhead
;
Color
focusHighlightColor
=
themeData
.
accentColor
;
if
(
themeData
.
primarySwatch
!=
null
)
...
...
@@ -171,7 +90,7 @@ class _InputState extends State<Input> {
List
<
Widget
>
stackChildren
=
<
Widget
>[];
bool
hasInlineLabel
=
config
.
labelText
!=
null
&&
!
focused
&&
!
_value
.
isNotEmpty
;
bool
hasInlineLabel
=
config
.
labelText
!=
null
&&
!
focused
&&
!
config
.
value
.
text
.
isNotEmpty
;
if
(
config
.
labelText
!=
null
)
{
TextStyle
labelStyle
=
hasInlineLabel
?
...
...
@@ -194,7 +113,7 @@ class _InputState extends State<Input> {
topPadding
+=
topPaddingIncrement
;
}
if
(
config
.
hintText
!=
null
&&
_value
.
isEmpty
&&
!
hasInlineLabel
)
{
if
(
config
.
hintText
!=
null
&&
config
.
value
.
text
.
isEmpty
&&
!
hasInlineLabel
)
{
TextStyle
hintStyle
=
themeData
.
text
.
subhead
.
copyWith
(
color:
themeData
.
hintColor
);
stackChildren
.
add
(
new
Positioned
(
left:
0.0
,
...
...
@@ -234,14 +153,17 @@ class _InputState extends State<Input> {
)
)
),
child:
new
RawEditableLine
(
value:
_editableString
,
focused:
focused
,
child:
new
RawInputLine
(
key:
_rawInputLineKey
,
value:
config
.
value
,
focusKey:
config
.
key
,
style:
textStyle
,
hideText:
config
.
hideText
,
cursorColor:
cursorColor
,
selectionColor:
cursorColor
,
onSelectionChanged:
_handleSelectionChanged
keyboardType:
config
.
keyboardType
,
onChanged:
config
.
onChanged
,
onSubmitted:
config
.
onSubmitted
)
));
...
...
@@ -278,7 +200,7 @@ class _InputState extends State<Input> {
return
new
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
_requestKeyboard
,
onTap:
()
=>
_rawInputLineKey
.
currentState
?.
requestKeyboard
()
,
child:
new
Padding
(
padding:
const
EdgeDims
.
symmetric
(
horizontal:
16.0
),
child:
child
...
...
packages/flutter/lib/src/painting/text_editing.dart
View file @
52c55344
...
...
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
show
TextAffinity
,
TextPosition
;
import
'dart:ui'
show
hashValues
,
TextAffinity
,
TextPosition
;
export
'dart:ui'
show
TextAffinity
,
TextPosition
;
...
...
@@ -50,6 +50,22 @@ class TextRange {
assert
(
isNormalized
);
return
text
.
substring
(
start
,
end
);
}
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
is
!
TextRange
)
return
false
;
TextRange
typedOther
=
other
;
return
typedOther
.
start
==
start
&&
typedOther
.
end
==
end
;
}
int
get
hashCode
=>
hashValues
(
start
.
hashCode
,
end
.
hashCode
);
}
/// A range of text that represents a selection.
...
...
@@ -122,4 +138,23 @@ class TextSelection extends TextRange {
String
toString
()
{
return
'
$runtimeType
(baseOffset:
$baseOffset
, extentOffset:
$extentOffset
, affinity:
$affinity
, isDirectional:
$isDirectional
)'
;
}
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
is
!
TextSelection
)
return
false
;
TextSelection
typedOther
=
other
;
return
typedOther
.
baseOffset
==
baseOffset
&&
typedOther
.
extentOffset
==
extentOffset
&&
typedOther
.
affinity
==
affinity
&&
typedOther
.
isDirectional
==
isDirectional
;
}
int
get
hashCode
=>
hashValues
(
baseOffset
.
hashCode
,
extentOffset
.
hashCode
,
affinity
.
hashCode
,
isDirectional
.
hashCode
);
}
packages/flutter/lib/src/widgets/editable.dart
View file @
52c55344
...
...
@@ -6,13 +6,16 @@ import 'dart:async';
import
'package:flutter/rendering.dart'
show
RenderEditableLine
;
import
'package:sky_services/editing/editing.mojom.dart'
as
mojom
;
import
'package:flutter/services.dart'
;
import
'basic.dart'
;
import
'framework.dart'
;
import
'focus.dart'
;
import
'scrollable.dart'
;
import
'scroll_behavior.dart'
;
export
'package:flutter/painting.dart'
show
TextSelection
;
export
'package:sky_services/editing/editing.mojom.dart'
show
KeyboardType
;
const
Duration
_kCursorBlinkHalfPeriod
=
const
Duration
(
milliseconds:
500
);
...
...
@@ -27,17 +30,16 @@ TextSelection _getTextSelectionFromEditingState(mojom.EditingState state) {
class
_KeyboardClientImpl
implements
mojom
.
KeyboardClient
{
_KeyboardClientImpl
({
String
text:
''
,
TextSelection
selection
,
this
.
inputValue
,
this
.
onUpdated
,
this
.
onSubmitted
})
:
text
=
text
,
selection
=
selection
??
new
TextSelection
.
collapsed
(
offset:
text
.
length
)
{
})
{
assert
(
inputValue
!=
null
);
assert
(
onUpdated
!=
null
);
assert
(
onSubmitted
!=
null
);
}
/// The current text being edited.
String
text
;
InputValue
inputValue
;
/// Called whenever the text changes.
final
VoidCallback
onUpdated
;
...
...
@@ -45,12 +47,6 @@ class _KeyboardClientImpl implements mojom.KeyboardClient {
/// Called whenever the user indicates they are done editing the string.
final
VoidCallback
onSubmitted
;
// The range of text that is still being composed.
TextRange
composing
=
TextRange
.
empty
;
/// The range of text that is currently selected.
TextSelection
selection
;
/// A keyboard client stub that can be attached to a keyboard service.
mojom
.
KeyboardClientStub
createStub
()
{
return
new
mojom
.
KeyboardClientStub
.
unbound
()..
impl
=
this
;
...
...
@@ -58,69 +54,82 @@ class _KeyboardClientImpl implements mojom.KeyboardClient {
mojom
.
EditingState
get
editingState
{
return
new
mojom
.
EditingState
()
..
text
=
text
..
selectionBase
=
selection
.
baseOffset
..
selectionExtent
=
selection
.
extentOffset
..
selectionAffinity
=
mojom
.
TextAffinity
.
values
[
selection
.
affinity
.
index
]
..
selectionIsDirectional
=
selection
.
isDirectional
..
composingBase
=
composing
.
start
..
composingExtent
=
composing
.
end
;
..
text
=
inputValue
.
text
..
selectionBase
=
inputValue
.
selection
.
baseOffset
..
selectionExtent
=
inputValue
.
selection
.
extentOffset
..
selectionAffinity
=
mojom
.
TextAffinity
.
values
[
inputValue
.
selection
.
affinity
.
index
]
..
selectionIsDirectional
=
inputValue
.
selection
.
isDirectional
..
composingBase
=
inputValue
.
composing
.
start
..
composingExtent
=
inputValue
.
composing
.
end
;
}
void
updateEditingState
(
mojom
.
EditingState
state
)
{
text
=
state
.
text
;
selection
=
_getTextSelectionFromEditingState
(
state
);
composing
=
new
TextRange
(
start:
state
.
composingBase
,
end:
state
.
composingExtent
);
inputValue
=
new
InputValue
(
text:
state
.
text
,
selection:
_getTextSelectionFromEditingState
(
state
),
composing:
new
TextRange
(
start:
state
.
composingBase
,
end:
state
.
composingExtent
)
);
onUpdated
();
}
void
clearComposing
()
{
inputValue
=
inputValue
.
copyWith
(
composing:
TextRange
.
empty
);
}
void
submit
(
mojom
.
SubmitAction
action
)
{
c
omposing
=
TextRange
.
empty
;
c
learComposing
()
;
onSubmitted
();
}
}
/// A string that can be manipulated by a keyboard.
///
/// Can be displayed with [RawEditableLine]. For a more featureful input widget,
/// consider using [Input].
class
EditableString
{
EditableString
({
String
text:
''
,
TextSelection
selection
,
VoidCallback
onUpdated
,
VoidCallback
onSubmitted
})
:
_client
=
new
_KeyboardClientImpl
(
text:
text
,
selection:
selection
,
onUpdated:
onUpdated
,
onSubmitted:
onSubmitted
);
final
_KeyboardClientImpl
_client
;
/// Configurable state of an input field.
class
InputValue
{
const
InputValue
({
this
.
text
:
''
,
this
.
selection
:
const
TextSelection
.
collapsed
(
offset:
-
1
),
this
.
composing
:
TextRange
.
empty
});
/// The current text being edited.
String
get
text
=>
_client
.
text
;
// The range of text that is still being composed.
TextRange
get
composing
=>
_client
.
composing
;
final
String
text
;
/// The range of text that is currently selected.
TextSelection
get
selection
=>
_client
.
selection
;
void
setSelection
(
TextSelection
selection
)
{
_client
.
selection
=
selection
;
}
mojom
.
EditingState
get
editingState
=>
_client
.
editingState
;
final
TextSelection
selection
;
/// A keyboard client stub that can be attached to a keyboard service.
///
/// See [Keyboard].
mojom
.
KeyboardClientStub
createStub
()
=>
_client
.
createStub
();
// The range of text that is still being composed.
final
TextRange
composing
;
static
const
InputValue
empty
=
const
InputValue
();
String
toString
()
=>
'
$runtimeType
(text:
$text
, selection:
$selection
, composing:
$composing
)'
;
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
is
!
InputValue
)
return
false
;
InputValue
typedOther
=
other
;
return
typedOther
.
text
==
text
&&
typedOther
.
selection
==
selection
&&
typedOther
.
composing
==
composing
;
}
int
get
hashCode
=>
hashValues
(
text
.
hashCode
,
selection
.
hashCode
,
composing
.
hashCode
);
void
didDetachKeyboard
()
{
_client
.
composing
=
TextRange
.
empty
;
InputValue
copyWith
({
String
text
,
TextSelection
selection
,
TextRange
composing
})
{
return
new
InputValue
(
text:
text
??
this
.
text
,
selection:
selection
??
this
.
selection
,
composing:
composing
??
this
.
composing
);
}
}
...
...
@@ -128,27 +137,29 @@ class EditableString {
///
/// This control is not intended to be used directly. Instead, consider using
/// [Input], which provides focus management and material design.
class
Raw
Editable
Line
extends
Scrollable
{
Raw
Editable
Line
({
class
Raw
Input
Line
extends
Scrollable
{
Raw
Input
Line
({
Key
key
,
this
.
value
,
this
.
focus
ed
:
false
,
this
.
focus
Key
,
this
.
hideText
:
false
,
this
.
style
,
this
.
cursorColor
,
this
.
selectionColor
,
this
.
onSelectionChanged
this
.
keyboardType
,
this
.
onChanged
,
this
.
onSubmitted
})
:
super
(
key:
key
,
initialScrollOffset:
0.0
,
scrollDirection:
Axis
.
horizontal
);
/// The
editable
string being displayed in this widget.
final
EditableString
value
;
/// The string being displayed in this widget.
final
InputValue
value
;
///
Whether this widget is focused
.
final
bool
focused
;
///
Key of the enclosing widget that holds the focus
.
final
GlobalKey
focusKey
;
/// Whether to hide the text being edited (e.g., for passwords).
final
bool
hideText
;
...
...
@@ -162,22 +173,51 @@ class RawEditableLine extends Scrollable {
/// The color to use when painting the selection.
final
Color
selectionColor
;
///
Called when the user requests a change to the selection
.
final
ValueChanged
<
TextSelection
>
onSelectionChanged
;
///
The type of keyboard to use for editing the text
.
final
KeyboardType
keyboardType
;
RawEditableTextState
createState
()
=>
new
RawEditableTextState
();
/// Called when the text being edited changes.
final
ValueChanged
<
InputValue
>
onChanged
;
/// Called when the user indicates that they are done editing the text in the field.
final
ValueChanged
<
InputValue
>
onSubmitted
;
RawInputLineState
createState
()
=>
new
RawInputLineState
();
}
class
Raw
EditableTextState
extends
ScrollableState
<
RawEditable
Line
>
{
class
Raw
InputLineState
extends
ScrollableState
<
RawInput
Line
>
{
Timer
_cursorTimer
;
bool
_showCursor
=
false
;
double
_contentWidth
=
0.0
;
double
_containerWidth
=
0.0
;
_KeyboardClientImpl
_keyboardClient
;
KeyboardHandle
_keyboardHandle
;
ScrollBehavior
createScrollBehavior
()
=>
new
BoundedBehavior
();
BoundedBehavior
get
scrollBehavior
=>
super
.
scrollBehavior
;
void
initState
()
{
super
.
initState
();
_keyboardClient
=
new
_KeyboardClientImpl
(
inputValue:
config
.
value
,
onUpdated:
_handleTextUpdated
,
onSubmitted:
_handleTextSubmitted
);
}
void
didUpdateConfig
(
RawInputLine
oldConfig
)
{
if
(
_keyboardClient
.
inputValue
!=
config
.
value
)
{
_keyboardClient
.
inputValue
=
config
.
value
;
if
(
_isAttachedToKeyboard
)
{
_keyboardHandle
.
setEditingState
(
_keyboardClient
.
editingState
);
}
}
}
bool
get
_isAttachedToKeyboard
=>
_keyboardHandle
!=
null
&&
_keyboardHandle
.
attached
;
void
_handleContainerSizeChanged
(
Size
newSize
)
{
_containerWidth
=
newSize
.
width
;
_updateScrollBehavior
();
...
...
@@ -198,6 +238,48 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
));
}
void
_attachOrDetachKeyboard
(
bool
focused
)
{
if
(
focused
&&
!
_isAttachedToKeyboard
)
{
_keyboardHandle
=
keyboard
.
attach
(
_keyboardClient
.
createStub
(),
new
mojom
.
KeyboardConfiguration
()
..
type
=
config
.
keyboardType
);
_keyboardHandle
.
setEditingState
(
_keyboardClient
.
editingState
);
_keyboardHandle
.
show
();
}
else
if
(!
focused
&&
_isAttachedToKeyboard
)
{
_keyboardHandle
.
release
();
_keyboardHandle
=
null
;
_keyboardClient
.
clearComposing
();
}
}
void
requestKeyboard
()
{
if
(
_isAttachedToKeyboard
)
{
_keyboardHandle
.
show
();
}
else
{
Focus
.
moveTo
(
config
.
focusKey
);
}
}
void
_handleTextUpdated
()
{
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
_keyboardClient
.
inputValue
);
}
void
_handleTextSubmitted
()
{
Focus
.
clear
(
context
);
if
(
config
.
onSubmitted
!=
null
)
config
.
onSubmitted
(
_keyboardClient
.
inputValue
);
}
void
_handleSelectionChanged
(
TextSelection
selection
)
{
// Note that this will show the keyboard for all selection changes on the
// EditableLineWidget, not just changes triggered by user gestures.
requestKeyboard
();
if
(
config
.
onChanged
!=
null
)
config
.
onChanged
(
_keyboardClient
.
inputValue
.
copyWith
(
selection:
selection
));
}
/// Whether the blinking cursor is actually visible at this precise moment
/// (it's hidden half the time, since it blinks).
bool
get
cursorCurrentlyVisible
=>
_showCursor
;
...
...
@@ -219,6 +301,8 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
}
void
dispose
()
{
if
(
_isAttachedToKeyboard
)
_keyboardHandle
.
release
();
if
(
_cursorTimer
!=
null
)
_stopCursorTimer
();
super
.
dispose
();
...
...
@@ -232,12 +316,15 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
Widget
buildContent
(
BuildContext
context
)
{
assert
(
config
.
style
!=
null
);
assert
(
config
.
focus
ed
!=
null
);
assert
(
config
.
focus
Key
!=
null
);
assert
(
config
.
cursorColor
!=
null
);
if
(
_cursorTimer
==
null
&&
config
.
focused
&&
config
.
value
.
selection
.
isCollapsed
)
bool
focused
=
Focus
.
at
(
config
.
focusKey
.
currentContext
);
_attachOrDetachKeyboard
(
focused
);
if
(
_cursorTimer
==
null
&&
focused
&&
config
.
value
.
selection
.
isCollapsed
)
_startCursorTimer
();
else
if
(
_cursorTimer
!=
null
&&
(!
config
.
focused
||
!
config
.
value
.
selection
.
isCollapsed
))
else
if
(
_cursorTimer
!=
null
&&
(!
focused
||
!
config
.
value
.
selection
.
isCollapsed
))
_stopCursorTimer
();
return
new
SizeObserver
(
...
...
@@ -250,7 +337,7 @@ class RawEditableTextState extends ScrollableState<RawEditableLine> {
selectionColor:
config
.
selectionColor
,
hideText:
config
.
hideText
,
onContentSizeChanged:
_handleContentSizeChanged
,
onSelectionChanged:
config
.
on
SelectionChanged
,
onSelectionChanged:
_handle
SelectionChanged
,
paintOffset:
new
Offset
(-
scrollOffset
,
0.0
)
)
);
...
...
@@ -271,7 +358,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this
.
paintOffset
})
:
super
(
key:
key
);
final
EditableString
value
;
final
InputValue
value
;
final
TextStyle
style
;
final
Color
cursorColor
;
final
bool
showCursor
;
...
...
packages/flutter/test/widget/input_test.dart
View file @
52c55344
...
...
@@ -30,15 +30,16 @@ void main() {
test
(
'Editable text has consistent size'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
GlobalKey
inputKey
=
new
GlobalKey
();
String
inputValue
;
InputValue
inputValue
=
InputValue
.
empty
;
Widget
builder
()
{
return
new
Center
(
child:
new
Material
(
child:
new
Input
(
value:
inputValue
,
key:
inputKey
,
hintText:
'Placeholder'
,
onChanged:
(
String
value
)
{
inputValue
=
value
;
}
onChanged:
(
InputValue
value
)
{
inputValue
=
value
;
}
)
)
);
...
...
@@ -58,7 +59,7 @@ void main() {
..
composingExtent
=
testValue
.
length
);
// Check that the onChanged event handler fired.
expect
(
inputValue
,
equals
(
testValue
));
expect
(
inputValue
.
text
,
equals
(
testValue
));
tester
.
pumpWidget
(
builder
());
}
...
...
@@ -88,7 +89,7 @@ void main() {
tester
.
pumpWidget
(
builder
());
Raw
EditableTextState
editableText
=
tester
.
findStateOfType
(
RawEditableText
State
);
Raw
InputLineState
editableText
=
tester
.
findStateOfType
(
RawInputLine
State
);
// Check that the cursor visibility toggles after each blink interval.
void
checkCursorToggle
()
{
...
...
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