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
52d06b82
Commit
52d06b82
authored
Dec 08, 2017
by
Michael Goderbauer
Committed by
Chris Bracken
Dec 08, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
a11y cursor movement (#13405)
parent
ca5ab1b4
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
267 additions
and
6 deletions
+267
-6
engine.version
bin/internal/engine.version
+1
-1
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+12
-4
text_painter.dart
packages/flutter/lib/src/painting/text_painter.dart
+20
-0
custom_paint.dart
packages/flutter/lib/src/rendering/custom_paint.dart
+6
-0
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+25
-0
proxy_box.dart
packages/flutter/lib/src/rendering/proxy_box.dart
+54
-0
semantics.dart
packages/flutter/lib/src/semantics/semantics.dart
+20
-0
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+9
-1
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+118
-0
semantics_test.dart
packages/flutter/test/widgets/semantics_test.dart
+2
-0
No files found.
bin/internal/engine.version
View file @
52d06b82
f888186e50f20fdb49ceea0dae74b6443a22ddaa
9d5cd4b12ec7fc29ad2117bf7851844be861b74d
packages/flutter/lib/src/material/text_field.dart
View file @
52d06b82
...
...
@@ -387,10 +387,18 @@ class _TextFieldState extends State<TextField> {
);
}
return
new
GestureDetector
(
return
new
Semantics
(
onTap:
()
{
if
(!
_controller
.
selection
.
isValid
)
_controller
.
selection
=
new
TextSelection
.
collapsed
(
offset:
_controller
.
text
.
length
);
_requestKeyboard
();
},
child:
new
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
_requestKeyboard
,
child:
child
,
excludeFromSemantics:
true
,
),
);
}
}
packages/flutter/lib/src/painting/text_painter.dart
View file @
52d06b82
...
...
@@ -401,6 +401,26 @@ class TextPainter {
return
value
&
0xF800
==
0xD800
;
}
/// Returns the closest offset after `offset` at which the inout cursor can be
/// positioned.
int
getOffsetAfter
(
int
offset
)
{
final
int
nextCodeUnit
=
_text
.
codeUnitAt
(
offset
);
if
(
nextCodeUnit
==
null
)
return
null
;
// TODO(goderbauer): doesn't handle flag emojis (https://github.com/flutter/flutter/issues/13404).
return
_isUtf16Surrogate
(
nextCodeUnit
)
?
offset
+
2
:
offset
+
1
;
}
/// Returns the closest offset before `offset` at which the inout cursor can
/// be positioned.
int
getOffsetBefore
(
int
offset
)
{
final
int
prevCodeUnit
=
_text
.
codeUnitAt
(
offset
-
1
);
if
(
prevCodeUnit
==
null
)
return
null
;
// TODO(goderbauer): doesn't handle flag emojis (https://github.com/flutter/flutter/issues/13404).
return
_isUtf16Surrogate
(
prevCodeUnit
)
?
offset
-
2
:
offset
-
1
;
}
Offset
_getOffsetFromUpstream
(
int
offset
,
Rect
caretPrototype
)
{
final
int
prevCodeUnit
=
_text
.
codeUnitAt
(
offset
-
1
);
if
(
prevCodeUnit
==
null
)
...
...
packages/flutter/lib/src/rendering/custom_paint.dart
View file @
52d06b82
...
...
@@ -845,6 +845,12 @@ class RenderCustomPaint extends RenderProxyBox {
if
(
properties
.
onDecrease
!=
null
)
{
config
.
addAction
(
SemanticsAction
.
decrease
,
properties
.
onDecrease
);
}
if
(
properties
.
onMoveCursorForwardByCharacter
!=
null
)
{
config
.
addAction
(
SemanticsAction
.
moveCursorForwardByCharacter
,
properties
.
onMoveCursorForwardByCharacter
);
}
if
(
properties
.
onMoveCursorBackwardByCharacter
!=
null
)
{
config
.
addAction
(
SemanticsAction
.
moveCursorBackwardByCharacter
,
properties
.
onMoveCursorBackwardByCharacter
);
}
newChild
.
updateWith
(
config:
config
,
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
52d06b82
...
...
@@ -316,6 +316,7 @@ class RenderEditable extends RenderBox {
_selection
=
value
;
_selectionRects
=
null
;
markNeedsPaint
();
markNeedsSemanticsUpdate
();
}
/// The offset at which the text should be painted.
...
...
@@ -346,6 +347,30 @@ class RenderEditable extends RenderBox {
..
textDirection
=
textDirection
..
isFocused
=
hasFocus
..
isTextField
=
true
;
if
(
_selection
?.
isValid
==
true
)
{
if
(
_textPainter
.
getOffsetBefore
(
_selection
.
extentOffset
)
!=
null
)
{
config
.
addAction
(
SemanticsAction
.
moveCursorBackwardByCharacter
,
()
{
final
int
offset
=
_textPainter
.
getOffsetBefore
(
_selection
.
extentOffset
);
if
(
offset
==
null
)
return
;
onSelectionChanged
(
new
TextSelection
.
collapsed
(
offset:
offset
),
this
,
SelectionChangedCause
.
keyboard
,
);
});
}
if
(
_textPainter
.
getOffsetAfter
(
_selection
.
extentOffset
)
!=
null
)
{
config
.
addAction
(
SemanticsAction
.
moveCursorForwardByCharacter
,
()
{
final
int
offset
=
_textPainter
.
getOffsetAfter
(
_selection
.
extentOffset
);
if
(
offset
==
null
)
return
;
onSelectionChanged
(
new
TextSelection
.
collapsed
(
offset:
offset
),
this
,
SelectionChangedCause
.
keyboard
,
);
});
}
}
}
@override
...
...
packages/flutter/lib/src/rendering/proxy_box.dart
View file @
52d06b82
...
...
@@ -2830,6 +2830,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
VoidCallback
onScrollDown
,
VoidCallback
onIncrease
,
VoidCallback
onDecrease
,
VoidCallback
onMoveCursorForwardByCharacter
,
VoidCallback
onMoveCursorBackwardByCharacter
,
})
:
assert
(
container
!=
null
),
_container
=
container
,
_explicitChildNodes
=
explicitChildNodes
,
...
...
@@ -2850,6 +2852,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_onScrollDown
=
onScrollDown
,
_onIncrease
=
onIncrease
,
_onDecrease
=
onDecrease
,
_onMoveCursorForwardByCharacter
=
onMoveCursorForwardByCharacter
,
_onMoveCursorBackwardByCharacter
=
onMoveCursorBackwardByCharacter
,
super
(
child
);
/// If 'container' is true, this [RenderObject] will introduce a new
...
...
@@ -3162,6 +3166,42 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate
();
}
/// The handler for [SemanticsAction.onMoveCursorForwardByCharacter].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field forward by one character.
///
/// TalkBack users can trigger this by pressing the volume up key while the
/// input focus is in a text field.
VoidCallback
get
onMoveCursorForwardByCharacter
=>
_onMoveCursorForwardByCharacter
;
VoidCallback
_onMoveCursorForwardByCharacter
;
set
onMoveCursorForwardByCharacter
(
VoidCallback
handler
)
{
if
(
_onMoveCursorForwardByCharacter
==
handler
)
return
;
final
bool
hadValue
=
_onMoveCursorForwardByCharacter
!=
null
;
_onMoveCursorForwardByCharacter
=
handler
;
if
((
handler
!=
null
)
!=
hadValue
)
markNeedsSemanticsUpdate
();
}
/// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one character.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
VoidCallback
get
onMoveCursorBackwardByCharacter
=>
_onMoveCursorBackwardByCharacter
;
VoidCallback
_onMoveCursorBackwardByCharacter
;
set
onMoveCursorBackwardByCharacter
(
VoidCallback
handler
)
{
if
(
_onMoveCursorBackwardByCharacter
==
handler
)
return
;
final
bool
hadValue
=
_onMoveCursorBackwardByCharacter
!=
null
;
_onMoveCursorBackwardByCharacter
=
handler
;
if
((
handler
!=
null
)
!=
hadValue
)
markNeedsSemanticsUpdate
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
config
.
isSemanticBoundary
=
container
;
...
...
@@ -3204,6 +3244,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config
.
addAction
(
SemanticsAction
.
increase
,
_performIncrease
);
if
(
onDecrease
!=
null
)
config
.
addAction
(
SemanticsAction
.
decrease
,
_performDecrease
);
if
(
onMoveCursorForwardByCharacter
!=
null
)
config
.
addAction
(
SemanticsAction
.
moveCursorForwardByCharacter
,
_performMoveCursorForwardByCharacter
);
if
(
onMoveCursorBackwardByCharacter
!=
null
)
config
.
addAction
(
SemanticsAction
.
moveCursorBackwardByCharacter
,
_performMoveCursorBackwardByCharacter
);
}
void
_performTap
()
{
...
...
@@ -3245,6 +3289,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
if
(
onDecrease
!=
null
)
onDecrease
();
}
void
_performMoveCursorForwardByCharacter
()
{
if
(
onMoveCursorForwardByCharacter
!=
null
)
onMoveCursorForwardByCharacter
();
}
void
_performMoveCursorBackwardByCharacter
()
{
if
(
onMoveCursorBackwardByCharacter
!=
null
)
onMoveCursorBackwardByCharacter
();
}
}
/// Causes the semantics of all earlier render objects below the same semantic
...
...
packages/flutter/lib/src/semantics/semantics.dart
View file @
52d06b82
...
...
@@ -252,6 +252,8 @@ class SemanticsProperties extends DiagnosticableTree {
this
.
onScrollDown
,
this
.
onIncrease
,
this
.
onDecrease
,
this
.
onMoveCursorForwardByCharacter
,
this
.
onMoveCursorBackwardByCharacter
,
});
/// If non-null, indicates that this subtree represents a checkbox
...
...
@@ -436,6 +438,24 @@ class SemanticsProperties extends DiagnosticableTree {
/// volume down button.
final
VoidCallback
onDecrease
;
/// The handler for [SemanticsAction.onMoveCursorForwardByCharacter].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field forward by one character.
///
/// TalkBack users can trigger this by pressing the volume up key while the
/// input focus is in a text field.
final
VoidCallback
onMoveCursorForwardByCharacter
;
/// The handler for [SemanticsAction.onMoveCursorBackwardByCharacter].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one character.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
final
VoidCallback
onMoveCursorBackwardByCharacter
;
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
description
)
{
super
.
debugFillProperties
(
description
);
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
52d06b82
...
...
@@ -4726,6 +4726,8 @@ class Semantics extends SingleChildRenderObjectWidget {
VoidCallback
onScrollDown
,
VoidCallback
onIncrease
,
VoidCallback
onDecrease
,
VoidCallback
onMoveCursorForwardByCharacter
,
VoidCallback
onMoveCursorBackwardByCharacter
,
})
:
this
.
fromProperties
(
key:
key
,
child:
child
,
...
...
@@ -4749,6 +4751,8 @@ class Semantics extends SingleChildRenderObjectWidget {
onScrollDown:
onScrollDown
,
onIncrease:
onIncrease
,
onDecrease:
onDecrease
,
onMoveCursorForwardByCharacter:
onMoveCursorForwardByCharacter
,
onMoveCursorBackwardByCharacter:
onMoveCursorBackwardByCharacter
,
),
);
...
...
@@ -4814,6 +4818,8 @@ class Semantics extends SingleChildRenderObjectWidget {
onScrollDown:
properties
.
onScrollDown
,
onIncrease:
properties
.
onIncrease
,
onDecrease:
properties
.
onDecrease
,
onMoveCursorForwardByCharacter:
properties
.
onMoveCursorForwardByCharacter
,
onMoveCursorBackwardByCharacter:
properties
.
onMoveCursorBackwardByCharacter
,
);
}
...
...
@@ -4849,7 +4855,9 @@ class Semantics extends SingleChildRenderObjectWidget {
..
onScrollUp
=
properties
.
onScrollUp
..
onScrollDown
=
properties
.
onScrollDown
..
onIncrease
=
properties
.
onIncrease
..
onDecrease
=
properties
.
onDecrease
;
..
onDecrease
=
properties
.
onDecrease
..
onMoveCursorForwardByCharacter
=
properties
.
onMoveCursorForwardByCharacter
..
onMoveCursorBackwardByCharacter
=
properties
.
onMoveCursorForwardByCharacter
;
}
@override
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
52d06b82
...
...
@@ -356,4 +356,122 @@ void main() {
expect
(
textState
.
selectionOverlay
.
handlesAreVisible
,
isFalse
);
expect
(
textState
.
selectionOverlay
.
textEditingValue
.
selection
,
const
TextSelection
.
collapsed
(
offset:
10
));
});
testWidgets
(
'exposes correct cursor movement semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
new
SemanticsTester
(
tester
);
controller
.
text
=
'test'
;
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
new
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
cursorColor
,
),
));
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
));
controller
.
selection
=
new
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
);
await
tester
.
pumpAndSettle
();
// At end, can only go backwards.
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorBackwardByCharacter
,
],
));
controller
.
selection
=
new
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
-
2
);
await
tester
.
pumpAndSettle
();
// Somewhere in the middle, can go in both directions.
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorBackwardByCharacter
,
SemanticsAction
.
moveCursorForwardByCharacter
,
],
));
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
0
);
await
tester
.
pumpAndSettle
();
// At beginning, can only go forward.
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorForwardByCharacter
,
],
));
semantics
.
dispose
();
});
testWidgets
(
'can move cursor with a11y means'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
new
SemanticsTester
(
tester
);
controller
.
text
=
'test'
;
controller
.
selection
=
new
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
);
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
new
EditableText
(
controller:
controller
,
focusNode:
focusNode
,
style:
textStyle
,
cursorColor:
cursorColor
,
),
));
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorBackwardByCharacter
,
],
));
final
RenderEditable
render
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
o
)
=>
o
.
runtimeType
==
RenderEditable
);
final
int
semanticsId
=
render
.
debugSemantics
.
id
;
expect
(
controller
.
selection
.
baseOffset
,
4
);
expect
(
controller
.
selection
.
extentOffset
,
4
);
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
semanticsId
,
SemanticsAction
.
moveCursorBackwardByCharacter
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
.
baseOffset
,
3
);
expect
(
controller
.
selection
.
extentOffset
,
3
);
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorBackwardByCharacter
,
SemanticsAction
.
moveCursorForwardByCharacter
,
],
));
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
semanticsId
,
SemanticsAction
.
moveCursorBackwardByCharacter
);
await
tester
.
pumpAndSettle
();
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
semanticsId
,
SemanticsAction
.
moveCursorBackwardByCharacter
);
await
tester
.
pumpAndSettle
();
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
semanticsId
,
SemanticsAction
.
moveCursorBackwardByCharacter
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
selection
.
extentOffset
,
0
);
await
tester
.
pumpAndSettle
();
expect
(
semantics
,
includesNodeWith
(
value:
'test'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
moveCursorForwardByCharacter
,
],
));
semantics
.
dispose
();
});
}
packages/flutter/test/widgets/semantics_test.dart
View file @
52d06b82
...
...
@@ -388,6 +388,8 @@ void main() {
onScrollDown:
()
=>
performedActions
.
add
(
SemanticsAction
.
scrollDown
),
onIncrease:
()
=>
performedActions
.
add
(
SemanticsAction
.
increase
),
onDecrease:
()
=>
performedActions
.
add
(
SemanticsAction
.
decrease
),
onMoveCursorForwardByCharacter:
()
=>
performedActions
.
add
(
SemanticsAction
.
moveCursorForwardByCharacter
),
onMoveCursorBackwardByCharacter:
()
=>
performedActions
.
add
(
SemanticsAction
.
moveCursorBackwardByCharacter
),
)
);
...
...
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