Unverified Commit d7321d92 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Make _RenderSlider not be a semantics container (#58535)

This PR makes _RenderSlider not be a semantics container. This is so that the FocusableActionDetector in the Slider widget will get to aggregate the semantics information, since otherwise Talkback won't focus the slider because it thinks that the focus node doesn't have anything to say (which it doesn't but the _RenderSlider child does). If the _RenderSlider is a semantics container, then it keeps its speakable information to itself, but it isn't the Focus widget, so when the keyboard focus goes to the focus node, the accessibility focus doesn't move.

Since the _RenderSlider is always wrapped by the Slider widget, there's nothing lost in making it not be a container.
parent 43e28084
...@@ -712,32 +712,35 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -712,32 +712,35 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
// in range_slider.dart. // in range_slider.dart.
Size _screenSize() => MediaQuery.of(context).size; Size _screenSize() => MediaQuery.of(context).size;
return FocusableActionDetector( return Semantics(
actions: _actionMap, container: true,
shortcuts: _shortcutMap, child: FocusableActionDetector(
focusNode: widget.focusNode, actions: _actionMap,
autofocus: widget.autofocus, shortcuts: _shortcutMap,
enabled: _enabled, focusNode: widget.focusNode,
onShowFocusHighlight: _handleFocusHighlightChanged, autofocus: widget.autofocus,
onShowHoverHighlight: _handleHoverChanged, enabled: _enabled,
mouseCursor: effectiveMouseCursor, onShowFocusHighlight: _handleFocusHighlightChanged,
child: CompositedTransformTarget( onShowHoverHighlight: _handleHoverChanged,
link: _layerLink, mouseCursor: effectiveMouseCursor,
child: _SliderRenderObjectWidget( child: CompositedTransformTarget(
key: _renderObjectKey, link: _layerLink,
value: _unlerp(widget.value), child: _SliderRenderObjectWidget(
divisions: widget.divisions, key: _renderObjectKey,
label: widget.label, value: _unlerp(widget.value),
sliderTheme: sliderTheme, divisions: widget.divisions,
textScaleFactor: MediaQuery.of(context).textScaleFactor, label: widget.label,
screenSize: _screenSize(), sliderTheme: sliderTheme,
onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null, textScaleFactor: MediaQuery.of(context).textScaleFactor,
onChangeStart: widget.onChangeStart != null ? _handleDragStart : null, screenSize: _screenSize(),
onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null, onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
state: this, onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
semanticFormatterCallback: widget.semanticFormatterCallback, onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
hasFocus: _focused, state: this,
hovering: _hovering, semanticFormatterCallback: widget.semanticFormatterCallback,
hasFocus: _focused,
hovering: _hovering,
),
), ),
), ),
); );
...@@ -1110,6 +1113,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1110,6 +1113,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return; return;
_hasFocus = value; _hasFocus = value;
_updateForFocusOrHover(_hasFocus); _updateForFocusOrHover(_hasFocus);
markNeedsSemanticsUpdate();
} }
/// True if this slider is being hovered over by a pointer. /// True if this slider is being hovered over by a pointer.
...@@ -1136,6 +1140,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1136,6 +1140,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
} }
} }
} }
bool get showValueIndicator { bool get showValueIndicator {
bool showValueIndicator; bool showValueIndicator;
switch (_sliderTheme.showValueIndicator) { switch (_sliderTheme.showValueIndicator) {
...@@ -1472,20 +1477,32 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { ...@@ -1472,20 +1477,32 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config); super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = isInteractive; // The Slider widget has its own Focus widget with semantics information,
// and we want that semantics node to collect the semantics information here
// so that it's all in the same node: otherwise Talkback sees that the node
// has focusable children, and it won't focus the Slider's Focus widget
// because it thinks the Focus widget's node doesn't have anything to say
// (which it doesn't, but this child does). Aggregating the semantic
// information into one node means that Talkback will recognize that it has
// something to say and focus it when it receives keyboard focus.
// (See https://github.com/flutter/flutter/issues/57038 for context).
config.isSemanticBoundary = false;
config.isEnabled = isInteractive;
config.textDirection = textDirection;
if (isInteractive) { if (isInteractive) {
config.textDirection = textDirection;
config.onIncrease = increaseAction; config.onIncrease = increaseAction;
config.onDecrease = decreaseAction; config.onDecrease = decreaseAction;
if (semanticFormatterCallback != null) { }
config.value = semanticFormatterCallback(_state._lerp(value)); config.label = _label ?? '';
config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double)); if (semanticFormatterCallback != null) {
config.decreasedValue = semanticFormatterCallback(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0) as double)); config.value = semanticFormatterCallback(_state._lerp(value));
} else { config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0) as double));
config.value = '${(value * 100).round()}%'; config.decreasedValue = semanticFormatterCallback(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0) as double));
config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; } else {
config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; config.value = '${(value * 100).round()}%';
} config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
} }
} }
......
...@@ -1347,17 +1347,12 @@ void main() { ...@@ -1347,17 +1347,12 @@ void main() {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 3,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable], flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
children: <TestSemantics>[ actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
TestSemantics( value: '50%',
id: 4, increasedValue: '55%',
value: '50%', decreasedValue: '45%',
increasedValue: '55%', textDirection: TextDirection.ltr,
decreasedValue: '45%',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
), ),
], ],
), ),
...@@ -1400,7 +1395,12 @@ void main() { ...@@ -1400,7 +1395,12 @@ void main() {
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 5, id: 3,
flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState],
value: '50%',
increasedValue: '55%',
decreasedValue: '45%',
textDirection: TextDirection.ltr,
), ),
], ],
), ),
...@@ -1455,17 +1455,60 @@ void main() { ...@@ -1455,17 +1455,60 @@ void main() {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 3,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable], flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
children: <TestSemantics>[ actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
TestSemantics( value: '50%',
id: 4, increasedValue: '60%',
value: '50%', decreasedValue: '40%',
increasedValue: '60%', textDirection: TextDirection.ltr,
decreasedValue: '40%', ),
textDirection: TextDirection.ltr, ],
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index, ),
), ],
], ),
],
),
ignoreRect: true,
ignoreTransform: true,
),
);
// Disable slider
await tester.pumpWidget(MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData.fromWindow(window),
child: const Material(
child: Slider(
value: 0.5,
onChanged: null,
),
),
),
),
));
expect(
semantics,
hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState],
value: '50%',
increasedValue: '60%',
decreasedValue: '40%',
textDirection: TextDirection.ltr,
), ),
], ],
), ),
...@@ -1517,17 +1560,12 @@ void main() { ...@@ -1517,17 +1560,12 @@ void main() {
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics( TestSemantics(
id: 3, id: 3,
flags: <SemanticsFlag>[SemanticsFlag.isFocusable], flags: <SemanticsFlag>[SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable],
children: <TestSemantics>[ actions: <SemanticsAction>[SemanticsAction.increase, SemanticsAction.decrease],
TestSemantics( value: '40',
id: 4, increasedValue: '60',
value: '40', decreasedValue: '20',
increasedValue: '60', textDirection: TextDirection.ltr,
decreasedValue: '20',
textDirection: TextDirection.ltr,
actions: SemanticsAction.decrease.index | SemanticsAction.increase.index,
),
],
), ),
], ],
), ),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment