Unverified Commit 38f2d276 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Revert "Re-Land: Add focus nodes, hover, and shortcuts to switches,...

Revert "Re-Land: Add focus nodes, hover, and shortcuts to switches, checkboxes, and radio buttons. (#43384)" (#43647)

This reverts commit 8017f63b, since it breaks web tests.
parent b31ca1aa
...@@ -198,16 +198,8 @@ void main() { ...@@ -198,16 +198,8 @@ void main() {
}); });
test('Checkbox has correct Android semantics', () async { test('Checkbox has correct Android semantics', () async {
Future<AndroidSemanticsNode> getCheckboxSemantics(String key) async {
return getSemantics(
find.descendant(
of: find.byValueKey(key),
matching: find.byType('_CheckboxRenderObjectWidget'),
),
);
}
expect( expect(
await getCheckboxSemantics(checkboxKeyValue), await getSemantics(find.byValueKey(checkboxKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.checkBox, className: AndroidClassName.checkBox,
isChecked: false, isChecked: false,
...@@ -224,7 +216,7 @@ void main() { ...@@ -224,7 +216,7 @@ void main() {
await driver.tap(find.byValueKey(checkboxKeyValue)); await driver.tap(find.byValueKey(checkboxKeyValue));
expect( expect(
await getCheckboxSemantics(checkboxKeyValue), await getSemantics(find.byValueKey(checkboxKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.checkBox, className: AndroidClassName.checkBox,
isChecked: true, isChecked: true,
...@@ -238,7 +230,7 @@ void main() { ...@@ -238,7 +230,7 @@ void main() {
), ),
); );
expect( expect(
await getCheckboxSemantics(disabledCheckboxKeyValue), await getSemantics(find.byValueKey(disabledCheckboxKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.checkBox, className: AndroidClassName.checkBox,
isCheckable: true, isCheckable: true,
...@@ -250,16 +242,8 @@ void main() { ...@@ -250,16 +242,8 @@ void main() {
); );
}); });
test('Radio has correct Android semantics', () async { test('Radio has correct Android semantics', () async {
Future<AndroidSemanticsNode> getRadioSemantics(String key) async {
return getSemantics(
find.descendant(
of: find.byValueKey(key),
matching: find.byType('_RadioRenderObjectWidget'),
),
);
}
expect( expect(
await getRadioSemantics(radio2KeyValue), await getSemantics(find.byValueKey(radio2KeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.radio, className: AndroidClassName.radio,
isChecked: false, isChecked: false,
...@@ -276,7 +260,7 @@ void main() { ...@@ -276,7 +260,7 @@ void main() {
await driver.tap(find.byValueKey(radio2KeyValue)); await driver.tap(find.byValueKey(radio2KeyValue));
expect( expect(
await getRadioSemantics(radio2KeyValue), await getSemantics(find.byValueKey(radio2KeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.radio, className: AndroidClassName.radio,
isChecked: true, isChecked: true,
...@@ -291,16 +275,8 @@ void main() { ...@@ -291,16 +275,8 @@ void main() {
); );
}); });
test('Switch has correct Android semantics', () async { test('Switch has correct Android semantics', () async {
Future<AndroidSemanticsNode> getSwitchSemantics(String key) async {
return getSemantics(
find.descendant(
of: find.byValueKey(key),
matching: find.byType('_SwitchRenderObjectWidget'),
),
);
}
expect( expect(
await getSwitchSemantics(switchKeyValue), await getSemantics(find.byValueKey(switchKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.toggleSwitch, className: AndroidClassName.toggleSwitch,
isChecked: false, isChecked: false,
...@@ -317,7 +293,7 @@ void main() { ...@@ -317,7 +293,7 @@ void main() {
await driver.tap(find.byValueKey(switchKeyValue)); await driver.tap(find.byValueKey(switchKeyValue));
expect( expect(
await getSwitchSemantics(switchKeyValue), await getSemantics(find.byValueKey(switchKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.toggleSwitch, className: AndroidClassName.toggleSwitch,
isChecked: true, isChecked: true,
...@@ -334,16 +310,8 @@ void main() { ...@@ -334,16 +310,8 @@ void main() {
// Regression test for https://github.com/flutter/flutter/issues/20820. // Regression test for https://github.com/flutter/flutter/issues/20820.
test('Switch can be labeled', () async { test('Switch can be labeled', () async {
Future<AndroidSemanticsNode> getSwitchSemantics(String key) async {
return getSemantics(
find.descendant(
of: find.byValueKey(key),
matching: find.byType('_SwitchRenderObjectWidget'),
),
);
}
expect( expect(
await getSwitchSemantics(labeledSwitchKeyValue), await getSemantics(find.byValueKey(labeledSwitchKeyValue)),
hasAndroidSemantics( hasAndroidSemantics(
className: AndroidClassName.toggleSwitch, className: AndroidClassName.toggleSwitch,
isChecked: false, isChecked: false,
......
...@@ -4,8 +4,6 @@ ...@@ -4,8 +4,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -54,7 +52,7 @@ class Checkbox extends StatefulWidget { ...@@ -54,7 +52,7 @@ class Checkbox extends StatefulWidget {
/// * [onChanged], which is called when the value of the checkbox should /// * [onChanged], which is called when the value of the checkbox should
/// change. It can be set to null to disable the checkbox. /// change. It can be set to null to disable the checkbox.
/// ///
/// The values of [tristate] and [autofocus] must not be null. /// The value of [tristate] must not be null.
const Checkbox({ const Checkbox({
Key key, Key key,
@required this.value, @required this.value,
...@@ -62,14 +60,9 @@ class Checkbox extends StatefulWidget { ...@@ -62,14 +60,9 @@ class Checkbox extends StatefulWidget {
@required this.onChanged, @required this.onChanged,
this.activeColor, this.activeColor,
this.checkColor, this.checkColor,
this.focusColor,
this.hoverColor,
this.materialTapTargetSize, this.materialTapTargetSize,
this.focusNode,
this.autofocus = false,
}) : assert(tristate != null), }) : assert(tristate != null),
assert(tristate || value != null), assert(tristate || value != null),
assert(autofocus != null),
super(key: key); super(key: key);
/// Whether this checkbox is checked. /// Whether this checkbox is checked.
...@@ -120,10 +113,10 @@ class Checkbox extends StatefulWidget { ...@@ -120,10 +113,10 @@ class Checkbox extends StatefulWidget {
/// ///
/// Checkbox displays a dash when its value is null. /// Checkbox displays a dash when its value is null.
/// ///
/// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged] /// When a tri-state checkbox is tapped its [onChanged] callback will be
/// callback will be applied to true if the current value is false, to null if /// applied to true if the current value is null or false, false otherwise.
/// value is true, and to false if value is null (i.e. it cycles through false /// Typically tri-state checkboxes are disabled (the onChanged callback is
/// => true => null => false when tapped). /// null) so they don't respond to taps.
/// ///
/// If tristate is false (the default), [value] must not be null. /// If tristate is false (the default), [value] must not be null.
final bool tristate; final bool tristate;
...@@ -137,18 +130,6 @@ class Checkbox extends StatefulWidget { ...@@ -137,18 +130,6 @@ class Checkbox extends StatefulWidget {
/// * [MaterialTapTargetSize], for a description of how this affects tap targets. /// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize; final MaterialTapTargetSize materialTapTargetSize;
/// The color for the checkbox's [Material] when it has the input focus.
final Color focusColor;
/// The color for the checkbox's [Material] when a pointer is hovering over it.
final Color hoverColor;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// The width of a checkbox widget. /// The width of a checkbox widget.
static const double width = 18.0; static const double width = 18.0;
...@@ -157,68 +138,6 @@ class Checkbox extends StatefulWidget { ...@@ -157,68 +138,6 @@ class Checkbox extends StatefulWidget {
} }
class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
bool get enabled => widget.onChanged != null;
Map<LocalKey, ActionFactory> _actionMap;
bool _showHighlight = false;
@override
void initState() {
super.initState();
_actionMap = <LocalKey, ActionFactory>{
SelectAction.key: _createAction,
if (!kIsWeb) ActivateAction.key: _createAction,
};
_updateHighlightMode(WidgetsBinding.instance.focusManager.highlightMode);
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
}
void _actionHandler(FocusNode node, Intent intent){
if (widget.onChanged != null) {
switch (widget.value) {
case false:
widget.onChanged(true);
break;
case true:
widget.onChanged(widget.tristate ? null : false);
break;
default: // case null:
widget.onChanged(false);
break;
}
}
final RenderObject renderObject = node.context.findRenderObject();
renderObject.sendSemanticsEvent(const TapSemanticEvent());
}
Action _createAction() {
return CallbackAction(
SelectAction.key,
onInvoke: _actionHandler,
);
}
void _updateHighlightMode(FocusHighlightMode mode) {
switch (WidgetsBinding.instance.focusManager.highlightMode) {
case FocusHighlightMode.touch:
_showHighlight = false;
break;
case FocusHighlightMode.traditional:
_showHighlight = true;
break;
}
}
void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) {
return;
}
setState(() { _updateHighlightMode(mode); });
}
bool hovering = false;
void _handleMouseEnter(PointerEnterEvent event) => setState(() { hovering = true; });
void _handleMouseExit(PointerExitEvent event) => setState(() { hovering = false; });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
...@@ -233,36 +152,15 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin { ...@@ -233,36 +152,15 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
break; break;
} }
final BoxConstraints additionalConstraints = BoxConstraints.tight(size); final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
return MouseRegion( return _CheckboxRenderObjectWidget(
onEnter: enabled ? _handleMouseEnter : null, value: widget.value,
onExit: enabled ? _handleMouseExit : null, tristate: widget.tristate,
child: Actions( activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
actions: _actionMap, checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
child: Focus( inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
focusNode: widget.focusNode, onChanged: widget.onChanged,
autofocus: widget.autofocus, additionalConstraints: additionalConstraints,
canRequestFocus: enabled, vsync: this,
debugLabel: '${describeIdentity(widget)}(${widget.value})',
child: Builder(
builder: (BuildContext context) {
return _CheckboxRenderObjectWidget(
value: widget.value,
tristate: widget.tristate,
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
inactiveColor: enabled ? themeData.unselectedWidgetColor : themeData.disabledColor,
focusColor: widget.focusColor ?? themeData.focusColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor,
onChanged: widget.onChanged,
additionalConstraints: additionalConstraints,
vsync: this,
hasFocus: enabled && _showHighlight && Focus.of(context).hasFocus,
hovering: enabled && _showHighlight && hovering,
);
},
),
),
),
); );
} }
} }
...@@ -275,13 +173,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -275,13 +173,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
@required this.activeColor, @required this.activeColor,
@required this.checkColor, @required this.checkColor,
@required this.inactiveColor, @required this.inactiveColor,
@required this.focusColor,
@required this.hoverColor,
@required this.onChanged, @required this.onChanged,
@required this.vsync, @required this.vsync,
@required this.additionalConstraints, @required this.additionalConstraints,
@required this.hasFocus,
@required this.hovering,
}) : assert(tristate != null), }) : assert(tristate != null),
assert(tristate || value != null), assert(tristate || value != null),
assert(activeColor != null), assert(activeColor != null),
...@@ -291,13 +185,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -291,13 +185,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
final bool value; final bool value;
final bool tristate; final bool tristate;
final bool hasFocus;
final bool hovering;
final Color activeColor; final Color activeColor;
final Color checkColor; final Color checkColor;
final Color inactiveColor; final Color inactiveColor;
final Color focusColor;
final Color hoverColor;
final ValueChanged<bool> onChanged; final ValueChanged<bool> onChanged;
final TickerProvider vsync; final TickerProvider vsync;
final BoxConstraints additionalConstraints; final BoxConstraints additionalConstraints;
...@@ -309,13 +199,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -309,13 +199,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
activeColor: activeColor, activeColor: activeColor,
checkColor: checkColor, checkColor: checkColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
onChanged: onChanged, onChanged: onChanged,
vsync: vsync, vsync: vsync,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
hasFocus: hasFocus,
hovering: hovering,
); );
@override @override
...@@ -326,13 +212,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -326,13 +212,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
..activeColor = activeColor ..activeColor = activeColor
..checkColor = checkColor ..checkColor = checkColor
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..focusColor = focusColor
..hoverColor = hoverColor
..onChanged = onChanged ..onChanged = onChanged
..additionalConstraints = additionalConstraints ..additionalConstraints = additionalConstraints
..vsync = vsync ..vsync = vsync;
..hasFocus = hasFocus
..hovering = hovering;
} }
} }
...@@ -347,12 +229,8 @@ class _RenderCheckbox extends RenderToggleable { ...@@ -347,12 +229,8 @@ class _RenderCheckbox extends RenderToggleable {
Color activeColor, Color activeColor,
this.checkColor, this.checkColor,
Color inactiveColor, Color inactiveColor,
Color focusColor,
Color hoverColor,
BoxConstraints additionalConstraints, BoxConstraints additionalConstraints,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
bool hasFocus,
bool hovering,
@required TickerProvider vsync, @required TickerProvider vsync,
}) : _oldValue = value, }) : _oldValue = value,
super( super(
...@@ -360,13 +238,9 @@ class _RenderCheckbox extends RenderToggleable { ...@@ -360,13 +238,9 @@ class _RenderCheckbox extends RenderToggleable {
tristate: tristate, tristate: tristate,
activeColor: activeColor, activeColor: activeColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
onChanged: onChanged, onChanged: onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
vsync: vsync, vsync: vsync,
hasFocus: hasFocus,
hovering: hovering,
); );
bool _oldValue; bool _oldValue;
......
...@@ -489,25 +489,20 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe ...@@ -489,25 +489,20 @@ class _InkResponseState<T extends InkResponse> extends State<T> with AutomaticKe
bool get highlightsExist => _highlights.values.where((InkHighlight highlight) => highlight != null).isNotEmpty; bool get highlightsExist => _highlights.values.where((InkHighlight highlight) => highlight != null).isNotEmpty;
void _handleAction(FocusNode node, Intent intent) {
_startSplash(context: node.context);
_handleTap(node.context);
}
Action _createAction() { Action _createAction() {
return CallbackAction( return CallbackAction(
ActivateAction.key, ActivateAction.key,
onInvoke: _handleAction, onInvoke: (FocusNode node, Intent intent) {
_startSplash(context: node.context);
_handleTap(node.context);
},
); );
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_actionMap = <LocalKey, ActionFactory>{ _actionMap = <LocalKey, ActionFactory>{ ActivateAction.key: _createAction };
SelectAction.key: _createAction,
if (!kIsWeb) ActivateAction.key: _createAction,
};
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange); WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
} }
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
...@@ -109,13 +107,8 @@ class Radio<T> extends StatefulWidget { ...@@ -109,13 +107,8 @@ class Radio<T> extends StatefulWidget {
@required this.groupValue, @required this.groupValue,
@required this.onChanged, @required this.onChanged,
this.activeColor, this.activeColor,
this.focusColor,
this.hoverColor,
this.materialTapTargetSize, this.materialTapTargetSize,
this.focusNode, }) : super(key: key);
this.autofocus = false,
}) : assert(autofocus != null),
super(key: key);
/// The value represented by this radio button. /// The value represented by this radio button.
final T value; final T value;
...@@ -168,77 +161,15 @@ class Radio<T> extends StatefulWidget { ...@@ -168,77 +161,15 @@ class Radio<T> extends StatefulWidget {
/// * [MaterialTapTargetSize], for a description of how this affects tap targets. /// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize; final MaterialTapTargetSize materialTapTargetSize;
/// The color for the radio's [Material] when it has the input focus.
final Color focusColor;
/// The color for the radio's [Material] when a pointer is hovering over it.
final Color hoverColor;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
@override @override
_RadioState<T> createState() => _RadioState<T>(); _RadioState<T> createState() => _RadioState<T>();
} }
class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
bool get enabled => widget.onChanged != null; bool get _enabled => widget.onChanged != null;
Map<LocalKey, ActionFactory> _actionMap;
bool _showHighlight = false;
@override
void initState() {
super.initState();
_actionMap = <LocalKey, ActionFactory>{
SelectAction.key: _createAction,
if (!kIsWeb) ActivateAction.key: _createAction,
};
_updateHighlightMode(WidgetsBinding.instance.focusManager.highlightMode);
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
}
void _actionHandler(FocusNode node, Intent intent){
if (widget.onChanged != null) {
widget.onChanged(widget.value);
}
final RenderObject renderObject = node.context.findRenderObject();
renderObject.sendSemanticsEvent(const TapSemanticEvent());
}
Action _createAction() {
return CallbackAction(
SelectAction.key,
onInvoke: _actionHandler,
);
}
void _updateHighlightMode(FocusHighlightMode mode) {
switch (WidgetsBinding.instance.focusManager.highlightMode) {
case FocusHighlightMode.touch:
_showHighlight = false;
break;
case FocusHighlightMode.traditional:
_showHighlight = true;
break;
}
}
void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) {
return;
}
setState(() { _updateHighlightMode(mode); });
}
bool hovering = false;
void _handleMouseEnter(PointerEnterEvent event) => setState(() { hovering = true; });
void _handleMouseExit(PointerExitEvent event) => setState(() { hovering = false; });
Color _getInactiveColor(ThemeData themeData) { Color _getInactiveColor(ThemeData themeData) {
return enabled ? themeData.unselectedWidgetColor : themeData.disabledColor; return _enabled ? themeData.unselectedWidgetColor : themeData.disabledColor;
} }
void _handleChanged(bool selected) { void _handleChanged(bool selected) {
...@@ -260,34 +191,13 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin { ...@@ -260,34 +191,13 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
break; break;
} }
final BoxConstraints additionalConstraints = BoxConstraints.tight(size); final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
return MouseRegion( return _RadioRenderObjectWidget(
onEnter: enabled ? _handleMouseEnter : null, selected: widget.value == widget.groupValue,
onExit: enabled ? _handleMouseExit : null, activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
child: Actions( inactiveColor: _getInactiveColor(themeData),
actions: _actionMap, onChanged: _enabled ? _handleChanged : null,
child: Focus( additionalConstraints: additionalConstraints,
focusNode: widget.focusNode, vsync: this,
autofocus: widget.autofocus,
canRequestFocus: enabled,
debugLabel: '${describeIdentity(widget)}(${widget.value})',
child: Builder(
builder: (BuildContext context) {
return _RadioRenderObjectWidget(
selected: widget.value == widget.groupValue,
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
inactiveColor: _getInactiveColor(themeData),
focusColor: widget.focusColor ?? themeData.focusColor,
hoverColor: widget.hoverColor ?? themeData.hoverColor,
onChanged: enabled ? _handleChanged : null,
additionalConstraints: additionalConstraints,
vsync: this,
hasFocus: enabled && _showHighlight && Focus.of(context).hasFocus,
hovering: enabled && _showHighlight && hovering,
);
},
),
),
),
); );
} }
} }
...@@ -298,13 +208,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -298,13 +208,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
@required this.selected, @required this.selected,
@required this.activeColor, @required this.activeColor,
@required this.inactiveColor, @required this.inactiveColor,
@required this.focusColor,
@required this.hoverColor,
@required this.additionalConstraints, @required this.additionalConstraints,
this.onChanged, this.onChanged,
@required this.vsync, @required this.vsync,
@required this.hasFocus,
@required this.hovering,
}) : assert(selected != null), }) : assert(selected != null),
assert(activeColor != null), assert(activeColor != null),
assert(inactiveColor != null), assert(inactiveColor != null),
...@@ -312,12 +218,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -312,12 +218,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
super(key: key); super(key: key);
final bool selected; final bool selected;
final bool hasFocus;
final bool hovering;
final Color inactiveColor; final Color inactiveColor;
final Color activeColor; final Color activeColor;
final Color focusColor;
final Color hoverColor;
final ValueChanged<bool> onChanged; final ValueChanged<bool> onChanged;
final TickerProvider vsync; final TickerProvider vsync;
final BoxConstraints additionalConstraints; final BoxConstraints additionalConstraints;
...@@ -327,13 +229,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -327,13 +229,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
value: selected, value: selected,
activeColor: activeColor, activeColor: activeColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
onChanged: onChanged, onChanged: onChanged,
vsync: vsync, vsync: vsync,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
hasFocus: hasFocus,
hovering: hovering,
); );
@override @override
...@@ -342,13 +240,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -342,13 +240,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
..value = selected ..value = selected
..activeColor = activeColor ..activeColor = activeColor
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..focusColor = focusColor
..hoverColor = hoverColor
..onChanged = onChanged ..onChanged = onChanged
..additionalConstraints = additionalConstraints ..additionalConstraints = additionalConstraints
..vsync = vsync ..vsync = vsync;
..hasFocus = hasFocus
..hovering = hovering;
} }
} }
...@@ -357,25 +251,17 @@ class _RenderRadio extends RenderToggleable { ...@@ -357,25 +251,17 @@ class _RenderRadio extends RenderToggleable {
bool value, bool value,
Color activeColor, Color activeColor,
Color inactiveColor, Color inactiveColor,
Color focusColor,
Color hoverColor,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
BoxConstraints additionalConstraints, BoxConstraints additionalConstraints,
@required TickerProvider vsync, @required TickerProvider vsync,
bool hasFocus,
bool hovering,
}) : super( }) : super(
value: value, value: value,
tristate: false, tristate: false,
activeColor: activeColor, activeColor: activeColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
focusColor: focusColor,
hoverColor: hoverColor,
onChanged: onChanged, onChanged: onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
vsync: vsync, vsync: vsync,
hasFocus: hasFocus,
hovering: hovering,
); );
@override @override
......
...@@ -74,13 +74,9 @@ class Switch extends StatefulWidget { ...@@ -74,13 +74,9 @@ class Switch extends StatefulWidget {
this.inactiveThumbImage, this.inactiveThumbImage,
this.materialTapTargetSize, this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.focusColor, }) : _switchType = _SwitchType.material,
this.hoverColor, assert(dragStartBehavior != null),
this.focusNode, super(key: key);
this.autofocus = false,
}) : _switchType = _SwitchType.material,
assert(dragStartBehavior != null),
super(key: key);
/// Creates a [CupertinoSwitch] if the target platform is iOS, creates a /// Creates a [CupertinoSwitch] if the target platform is iOS, creates a
/// material design switch otherwise. /// material design switch otherwise.
...@@ -102,13 +98,8 @@ class Switch extends StatefulWidget { ...@@ -102,13 +98,8 @@ class Switch extends StatefulWidget {
this.inactiveThumbImage, this.inactiveThumbImage,
this.materialTapTargetSize, this.materialTapTargetSize,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.focusColor, }) : _switchType = _SwitchType.adaptive,
this.hoverColor, super(key: key);
this.focusNode,
this.autofocus = false,
}) : assert(autofocus != null),
_switchType = _SwitchType.adaptive,
super(key: key);
/// Whether this switch is on or off. /// Whether this switch is on or off.
/// ///
...@@ -189,18 +180,6 @@ class Switch extends StatefulWidget { ...@@ -189,18 +180,6 @@ class Switch extends StatefulWidget {
/// {@macro flutter.cupertino.switch.dragStartBehavior} /// {@macro flutter.cupertino.switch.dragStartBehavior}
final DragStartBehavior dragStartBehavior; final DragStartBehavior dragStartBehavior;
/// The color for the button's [Material] when it has the input focus.
final Color focusColor;
/// The color for the button's [Material] when a pointer is hovering over it.
final Color hoverColor;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
@override @override
_SwitchState createState() => _SwitchState(); _SwitchState createState() => _SwitchState();
...@@ -213,53 +192,6 @@ class Switch extends StatefulWidget { ...@@ -213,53 +192,6 @@ class Switch extends StatefulWidget {
} }
class _SwitchState extends State<Switch> with TickerProviderStateMixin { class _SwitchState extends State<Switch> with TickerProviderStateMixin {
Map<LocalKey, ActionFactory> _actionMap;
bool _showHighlight = false;
@override
void initState() {
super.initState();
_actionMap = <LocalKey, ActionFactory>{
SelectAction.key: _createAction,
if (!kIsWeb) ActivateAction.key: _createAction,
};
_updateHighlightMode(WidgetsBinding.instance.focusManager.highlightMode);
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
}
void _actionHandler(FocusNode node, Intent intent){
if (widget.onChanged != null) {
widget.onChanged(!widget.value);
}
final RenderObject renderObject = node.context.findRenderObject();
renderObject.sendSemanticsEvent(const TapSemanticEvent());
}
Action _createAction() {
return CallbackAction(
SelectAction.key,
onInvoke: _actionHandler,
);
}
void _updateHighlightMode(FocusHighlightMode mode) {
switch (WidgetsBinding.instance.focusManager.highlightMode) {
case FocusHighlightMode.touch:
_showHighlight = false;
break;
case FocusHighlightMode.traditional:
_showHighlight = true;
break;
}
}
void _handleFocusHighlightModeChange(FocusHighlightMode mode) {
if (!mounted) {
return;
}
setState(() { _updateHighlightMode(mode); });
}
Size getSwitchSize(ThemeData theme) { Size getSwitchSize(ThemeData theme) {
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) { switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
case MaterialTapTargetSize.padded: case MaterialTapTargetSize.padded:
...@@ -273,12 +205,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -273,12 +205,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
return null; return null;
} }
bool get enabled => widget.onChanged != null;
bool hovering = false;
void _handleMouseEnter(PointerEnterEvent event) => setState(() { hovering = true; });
void _handleMouseExit(PointerExitEvent event) => setState(() { hovering = false; });
Widget buildMaterialSwitch(BuildContext context) { Widget buildMaterialSwitch(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
...@@ -286,12 +212,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -286,12 +212,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
final Color activeThumbColor = widget.activeColor ?? theme.toggleableActiveColor; final Color activeThumbColor = widget.activeColor ?? theme.toggleableActiveColor;
final Color activeTrackColor = widget.activeTrackColor ?? activeThumbColor.withAlpha(0x80); final Color activeTrackColor = widget.activeTrackColor ?? activeThumbColor.withAlpha(0x80);
final Color hoverColor = widget.hoverColor ?? theme.hoverColor;
final Color focusColor = widget.focusColor ?? theme.focusColor;
Color inactiveThumbColor; Color inactiveThumbColor;
Color inactiveTrackColor; Color inactiveTrackColor;
if (enabled) { if (widget.onChanged != null) {
const Color black32 = Color(0x52000000); // Black with 32% opacity const Color black32 = Color(0x52000000); // Black with 32% opacity
inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade400 : Colors.grey.shade50); inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade400 : Colors.grey.shade50);
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white30 : black32); inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white30 : black32);
...@@ -300,59 +224,33 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin { ...@@ -300,59 +224,33 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12); inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12);
} }
return MouseRegion( return _SwitchRenderObjectWidget(
onEnter: enabled ? _handleMouseEnter : null, dragStartBehavior: widget.dragStartBehavior,
onExit: enabled ? _handleMouseExit : null, value: widget.value,
child: Actions( activeColor: activeThumbColor,
actions: _actionMap, inactiveColor: inactiveThumbColor,
child: Focus( activeThumbImage: widget.activeThumbImage,
focusNode: widget.focusNode, inactiveThumbImage: widget.inactiveThumbImage,
autofocus: widget.autofocus, activeTrackColor: activeTrackColor,
canRequestFocus: enabled, inactiveTrackColor: inactiveTrackColor,
debugLabel: '${describeIdentity(widget)}({$widget.value})', configuration: createLocalImageConfiguration(context),
child: Builder( onChanged: widget.onChanged,
builder: (BuildContext context) { additionalConstraints: BoxConstraints.tight(getSwitchSize(theme)),
final bool hasFocus = Focus.of(context).hasFocus; vsync: this,
return _SwitchRenderObjectWidget(
dragStartBehavior: widget.dragStartBehavior,
value: widget.value,
activeColor: activeThumbColor,
inactiveColor: inactiveThumbColor,
hoverColor: hoverColor,
focusColor: focusColor,
activeThumbImage: widget.activeThumbImage,
inactiveThumbImage: widget.inactiveThumbImage,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
configuration: createLocalImageConfiguration(context),
onChanged: widget.onChanged,
additionalConstraints: BoxConstraints.tight(getSwitchSize(theme)),
hasFocus: enabled && _showHighlight && hasFocus,
hovering: enabled && _showHighlight && hovering,
vsync: this,
);
},
),
),
),
); );
} }
Widget buildCupertinoSwitch(BuildContext context) { Widget buildCupertinoSwitch(BuildContext context) {
final Size size = getSwitchSize(Theme.of(context)); final Size size = getSwitchSize(Theme.of(context));
return Focus( return Container(
focusNode: widget.focusNode, width: size.width, // Same size as the Material switch.
autofocus: widget.autofocus, height: size.height,
child: Container( alignment: Alignment.center,
width: size.width, // Same size as the Material switch. child: CupertinoSwitch(
height: size.height, dragStartBehavior: widget.dragStartBehavior,
alignment: Alignment.center, value: widget.value,
child: CupertinoSwitch( onChanged: widget.onChanged,
dragStartBehavior: widget.dragStartBehavior, activeColor: widget.activeColor,
value: widget.value,
onChanged: widget.onChanged,
activeColor: widget.activeColor,
),
), ),
); );
} }
...@@ -386,8 +284,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -386,8 +284,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
this.value, this.value,
this.activeColor, this.activeColor,
this.inactiveColor, this.inactiveColor,
this.hoverColor,
this.focusColor,
this.activeThumbImage, this.activeThumbImage,
this.inactiveThumbImage, this.inactiveThumbImage,
this.activeTrackColor, this.activeTrackColor,
...@@ -397,15 +293,11 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -397,15 +293,11 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
this.vsync, this.vsync,
this.additionalConstraints, this.additionalConstraints,
this.dragStartBehavior, this.dragStartBehavior,
this.hasFocus,
this.hovering,
}) : super(key: key); }) : super(key: key);
final bool value; final bool value;
final Color activeColor; final Color activeColor;
final Color inactiveColor; final Color inactiveColor;
final Color hoverColor;
final Color focusColor;
final ImageProvider activeThumbImage; final ImageProvider activeThumbImage;
final ImageProvider inactiveThumbImage; final ImageProvider inactiveThumbImage;
final Color activeTrackColor; final Color activeTrackColor;
...@@ -415,8 +307,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -415,8 +307,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
final TickerProvider vsync; final TickerProvider vsync;
final BoxConstraints additionalConstraints; final BoxConstraints additionalConstraints;
final DragStartBehavior dragStartBehavior; final DragStartBehavior dragStartBehavior;
final bool hasFocus;
final bool hovering;
@override @override
_RenderSwitch createRenderObject(BuildContext context) { _RenderSwitch createRenderObject(BuildContext context) {
...@@ -425,8 +315,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -425,8 +315,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
value: value, value: value,
activeColor: activeColor, activeColor: activeColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
hoverColor: hoverColor,
focusColor: focusColor,
activeThumbImage: activeThumbImage, activeThumbImage: activeThumbImage,
inactiveThumbImage: inactiveThumbImage, inactiveThumbImage: inactiveThumbImage,
activeTrackColor: activeTrackColor, activeTrackColor: activeTrackColor,
...@@ -435,8 +323,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -435,8 +323,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
onChanged: onChanged, onChanged: onChanged,
textDirection: Directionality.of(context), textDirection: Directionality.of(context),
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
hasFocus: hasFocus,
hovering: hovering,
vsync: vsync, vsync: vsync,
); );
} }
...@@ -447,8 +333,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -447,8 +333,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..value = value ..value = value
..activeColor = activeColor ..activeColor = activeColor
..inactiveColor = inactiveColor ..inactiveColor = inactiveColor
..hoverColor = hoverColor
..focusColor = focusColor
..activeThumbImage = activeThumbImage ..activeThumbImage = activeThumbImage
..inactiveThumbImage = inactiveThumbImage ..inactiveThumbImage = inactiveThumbImage
..activeTrackColor = activeTrackColor ..activeTrackColor = activeTrackColor
...@@ -458,8 +342,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget { ...@@ -458,8 +342,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
..textDirection = Directionality.of(context) ..textDirection = Directionality.of(context)
..additionalConstraints = additionalConstraints ..additionalConstraints = additionalConstraints
..dragStartBehavior = dragStartBehavior ..dragStartBehavior = dragStartBehavior
..hasFocus = hasFocus
..hovering = hovering
..vsync = vsync; ..vsync = vsync;
} }
} }
...@@ -469,8 +351,6 @@ class _RenderSwitch extends RenderToggleable { ...@@ -469,8 +351,6 @@ class _RenderSwitch extends RenderToggleable {
bool value, bool value,
Color activeColor, Color activeColor,
Color inactiveColor, Color inactiveColor,
Color hoverColor,
Color focusColor,
ImageProvider activeThumbImage, ImageProvider activeThumbImage,
ImageProvider inactiveThumbImage, ImageProvider inactiveThumbImage,
Color activeTrackColor, Color activeTrackColor,
...@@ -479,10 +359,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -479,10 +359,8 @@ class _RenderSwitch extends RenderToggleable {
BoxConstraints additionalConstraints, BoxConstraints additionalConstraints,
@required TextDirection textDirection, @required TextDirection textDirection,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
DragStartBehavior dragStartBehavior,
bool hasFocus,
bool hovering,
@required TickerProvider vsync, @required TickerProvider vsync,
DragStartBehavior dragStartBehavior,
}) : assert(textDirection != null), }) : assert(textDirection != null),
_activeThumbImage = activeThumbImage, _activeThumbImage = activeThumbImage,
_inactiveThumbImage = inactiveThumbImage, _inactiveThumbImage = inactiveThumbImage,
...@@ -495,12 +373,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -495,12 +373,8 @@ class _RenderSwitch extends RenderToggleable {
tristate: false, tristate: false,
activeColor: activeColor, activeColor: activeColor,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
hoverColor: hoverColor,
focusColor: focusColor,
onChanged: onChanged, onChanged: onChanged,
additionalConstraints: additionalConstraints, additionalConstraints: additionalConstraints,
hasFocus: hasFocus,
hovering: hovering,
vsync: vsync, vsync: vsync,
) { ) {
_drag = HorizontalDragGestureRecognizer() _drag = HorizontalDragGestureRecognizer()
......
...@@ -10,15 +10,9 @@ import 'package:flutter/scheduler.dart'; ...@@ -10,15 +10,9 @@ import 'package:flutter/scheduler.dart';
import 'constants.dart'; import 'constants.dart';
// Duration of the animation that moves the toggle from one state to another.
const Duration _kToggleDuration = Duration(milliseconds: 200); const Duration _kToggleDuration = Duration(milliseconds: 200);
// Radius of the radial reaction over time.
final Animatable<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius); final Animatable<double> _kRadialReactionRadiusTween = Tween<double>(begin: 0.0, end: kRadialReactionRadius);
// Duration of the fade animation for the reaction when focus and hover occur.
const Duration _kReactionFadeDuration = Duration(milliseconds: 50);
/// A base class for material style toggleable controls with toggle animations. /// A base class for material style toggleable controls with toggle animations.
/// ///
/// This class handles storing the current value, dispatching ValueChanged on a /// This class handles storing the current value, dispatching ValueChanged on a
...@@ -34,13 +28,9 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -34,13 +28,9 @@ abstract class RenderToggleable extends RenderConstrainedBox {
bool tristate = false, bool tristate = false,
@required Color activeColor, @required Color activeColor,
@required Color inactiveColor, @required Color inactiveColor,
Color hoverColor,
Color focusColor,
ValueChanged<bool> onChanged, ValueChanged<bool> onChanged,
BoxConstraints additionalConstraints, BoxConstraints additionalConstraints,
@required TickerProvider vsync, @required TickerProvider vsync,
bool hasFocus = false,
bool hovering = false,
}) : assert(tristate != null), }) : assert(tristate != null),
assert(tristate || value != null), assert(tristate || value != null),
assert(activeColor != null), assert(activeColor != null),
...@@ -50,11 +40,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -50,11 +40,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
_tristate = tristate, _tristate = tristate,
_activeColor = activeColor, _activeColor = activeColor,
_inactiveColor = inactiveColor, _inactiveColor = inactiveColor,
_hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha),
_onChanged = onChanged, _onChanged = onChanged,
_hasFocus = hasFocus,
_hovering = hovering,
_vsync = vsync, _vsync = vsync,
super(additionalConstraints: additionalConstraints) { super(additionalConstraints: additionalConstraints) {
_tap = TapGestureRecognizer() _tap = TapGestureRecognizer()
...@@ -80,24 +66,6 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -80,24 +66,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
parent: _reactionController, parent: _reactionController,
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
)..addListener(markNeedsPaint); )..addListener(markNeedsPaint);
_reactionHoverFadeController = AnimationController(
duration: _kReactionFadeDuration,
value: hovering || hasFocus ? 1.0 : 0.0,
vsync: vsync,
);
_reactionHoverFade = CurvedAnimation(
parent: _reactionHoverFadeController,
curve: Curves.fastOutSlowIn,
)..addListener(markNeedsPaint);
_reactionFocusFadeController = AnimationController(
duration: _kReactionFadeDuration,
value: hovering || hasFocus ? 1.0 : 0.0,
vsync: vsync,
);
_reactionFocusFade = CurvedAnimation(
parent: _reactionFocusFadeController,
curve: Curves.fastOutSlowIn,
)..addListener(markNeedsPaint);
} }
/// Used by subclasses to manipulate the visual value of the control. /// Used by subclasses to manipulate the visual value of the control.
...@@ -134,66 +102,6 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -134,66 +102,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
AnimationController _reactionController; AnimationController _reactionController;
Animation<double> _reaction; Animation<double> _reaction;
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hasFocus] changes.
///
/// Some controls have a radial ink reaction to focus. This animation
/// controller can be used to start or stop these ink reaction fade-ins and
/// fade-outs.
///
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController get reactionFocusFadeController => _reactionFocusFadeController;
AnimationController _reactionFocusFadeController;
Animation<double> _reactionFocusFade;
/// Used by subclasses to control the radial reaction's opacity animation for
/// [hovering] changes.
///
/// Some controls have a radial ink reaction to pointer hover. This animation
/// controller can be used to start or stop these ink reaction fade-ins and
/// fade-outs.
///
/// Subclasses should call [paintRadialReaction] to actually paint the radial
/// reaction.
@protected
AnimationController get reactionHoverFadeController => _reactionHoverFadeController;
AnimationController _reactionHoverFadeController;
Animation<double> _reactionHoverFade;
/// True if this toggleable has the input focus.
bool get hasFocus => _hasFocus;
bool _hasFocus;
set hasFocus(bool value) {
assert(value != null);
if (value == _hasFocus)
return;
_hasFocus = value;
if (_hasFocus) {
_reactionFocusFadeController.forward();
} else {
_reactionFocusFadeController.reverse();
}
markNeedsPaint();
}
/// True if this toggleable is being hovered over by a pointer.
bool get hovering => _hovering;
bool _hovering;
set hovering(bool value) {
assert(value != null);
if (value == _hovering)
return;
_hovering = value;
if (_hovering) {
_reactionHoverFadeController.forward();
} else {
_reactionHoverFadeController.reverse();
}
markNeedsPaint();
}
/// The [TickerProvider] for the [AnimationController]s that run the animations. /// The [TickerProvider] for the [AnimationController]s that run the animations.
TickerProvider get vsync => _vsync; TickerProvider get vsync => _vsync;
TickerProvider _vsync; TickerProvider _vsync;
...@@ -284,54 +192,6 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -284,54 +192,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
markNeedsPaint(); markNeedsPaint();
} }
/// The color that should be used for the reaction when [hovering] is true.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it is being hovered over.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color get hoverColor => _hoverColor;
Color _hoverColor;
set hoverColor(Color value) {
assert(value != null);
if (value == _hoverColor)
return;
_hoverColor = value;
markNeedsPaint();
}
/// The color that should be used for the reaction when [hasFocus] is true.
///
/// Used when the toggleable needs to change the reaction color/transparency,
/// when it has focus.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color get focusColor => _focusColor;
Color _focusColor;
set focusColor(Color value) {
assert(value != null);
if (value == _focusColor)
return;
_focusColor = value;
markNeedsPaint();
}
/// The color that should be used for the reaction when drawn.
///
/// Used when the toggleable needs to change the reaction color/transparency
/// that is displayed when the toggleable is toggled by a tap.
///
/// Defaults to the [activeColor] at alpha [kRadialReactionAlpha].
Color get reactionColor => _reactionColor;
Color _reactionColor;
set reactionColor(Color value) {
assert(value != null);
if (value == _reactionColor)
return;
_reactionColor = value;
markNeedsPaint();
}
/// Called when the control changes value. /// Called when the control changes value.
/// ///
/// If the control is tapped, [onChanged] is called immediately with the new /// If the control is tapped, [onChanged] is called immediately with the new
...@@ -463,18 +323,12 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -463,18 +323,12 @@ abstract class RenderToggleable extends RenderConstrainedBox {
/// point at which the user interacted with the control, which is handled /// point at which the user interacted with the control, which is handled
/// automatically). /// automatically).
void paintRadialReaction(Canvas canvas, Offset offset, Offset origin) { void paintRadialReaction(Canvas canvas, Offset offset, Offset origin) {
if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) { if (!_reaction.isDismissed) {
final Paint reactionPaint = Paint() // TODO(abarth): We should have a different reaction color when position is zero.
..color = Color.lerp( final Paint reactionPaint = Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
Color.lerp(activeColor.withAlpha(kRadialReactionAlpha), hoverColor, _reactionHoverFade.value),
focusColor,
_reactionFocusFade.value,
);
final Offset center = Offset.lerp(_downPosition ?? origin, origin, _reaction.value); final Offset center = Offset.lerp(_downPosition ?? origin, origin, _reaction.value);
final double reactionRadius = hasFocus || hovering final double radius = _kRadialReactionRadiusTween.evaluate(_reaction);
? kRadialReactionRadius canvas.drawCircle(center + offset, radius, reactionPaint);
: _kRadialReactionRadiusTween.evaluate(_reaction);
canvas.drawCircle(center + offset, reactionRadius, reactionPaint);
} }
} }
......
...@@ -376,8 +376,8 @@ class DoNothingAction extends Action { ...@@ -376,8 +376,8 @@ class DoNothingAction extends Action {
/// An action that invokes the currently focused control. /// An action that invokes the currently focused control.
/// ///
/// This is an abstract class that serves as a base class for actions that /// This is an abstract class that serves as a base class for actions that
/// activate a control. By default, is bound to [LogicalKeyboardKey.enter] in /// activate a control. It is bound to [LogicalKeyboardKey.enter] in the default
/// the default keyboard map in [WidgetsApp]. /// keyboard map in [WidgetsApp].
abstract class ActivateAction extends Action { abstract class ActivateAction extends Action {
/// Creates a [ActivateAction] with a fixed [key]; /// Creates a [ActivateAction] with a fixed [key];
const ActivateAction() : super(key); const ActivateAction() : super(key);
...@@ -385,16 +385,3 @@ abstract class ActivateAction extends Action { ...@@ -385,16 +385,3 @@ abstract class ActivateAction extends Action {
/// The [LocalKey] that uniquely identifies this action. /// The [LocalKey] that uniquely identifies this action.
static const LocalKey key = ValueKey<Type>(ActivateAction); static const LocalKey key = ValueKey<Type>(ActivateAction);
} }
/// An action that selects the currently focused control.
///
/// This is an abstract class that serves as a base class for actions that
/// select something, like a checkbox or a radio button. By default, it is bound
/// to [LogicalKeyboardKey.space] in the default keyboard map in [WidgetsApp].
abstract class SelectAction extends Action {
/// Creates a [SelectAction] with a fixed [key];
const SelectAction() : super(key);
/// The [LocalKey] that uniquely identifies this action.
static const LocalKey key = ValueKey<Type>(SelectAction);
}
...@@ -1048,7 +1048,6 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { ...@@ -1048,7 +1048,6 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down), LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up), LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key), LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key),
LogicalKeySet(LogicalKeyboardKey.space): const Intent(SelectAction.key),
}; };
final Map<LocalKey, ActionFactory> _actionMap = <LocalKey, ActionFactory>{ final Map<LocalKey, ActionFactory> _actionMap = <LocalKey, ActionFactory>{
......
...@@ -897,8 +897,8 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -897,8 +897,8 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null)); properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null));
properties.add(FlagProperty('canRequestFocus', value: canRequestFocus, ifFalse: 'NOT FOCUSABLE', defaultValue: true)); properties.add(FlagProperty('canRequestFocus', value: canRequestFocus, ifFalse: 'NOT FOCUSABLE', defaultValue: true));
properties.add(FlagProperty('hasFocus', value: hasFocus && !hasPrimaryFocus, ifTrue: 'IN FOCUS PATH', defaultValue: false)); properties.add(FlagProperty('hasFocus', value: hasFocus, ifTrue: 'FOCUSED', defaultValue: false));
properties.add(FlagProperty('hasPrimaryFocus', value: hasPrimaryFocus, ifTrue: 'PRIMARY FOCUS', defaultValue: false)); properties.add(StringProperty('debugLabel', debugLabel, defaultValue: null));
} }
@override @override
......
...@@ -67,12 +67,11 @@ void main() { ...@@ -67,12 +67,11 @@ void main() {
), ),
)); ));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics( expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
hasCheckedState: true, hasCheckedState: true,
hasEnabledState: true, hasEnabledState: true,
isEnabled: true, isEnabled: true,
hasTapAction: true, hasTapAction: true,
isFocusable: true,
)); ));
await tester.pumpWidget(Material( await tester.pumpWidget(Material(
...@@ -82,13 +81,12 @@ void main() { ...@@ -82,13 +81,12 @@ void main() {
), ),
)); ));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics( expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
hasCheckedState: true, hasCheckedState: true,
hasEnabledState: true, hasEnabledState: true,
isChecked: true, isChecked: true,
isEnabled: true, isEnabled: true,
hasTapAction: true, hasTapAction: true,
isFocusable: true,
)); ));
await tester.pumpWidget(const Material( await tester.pumpWidget(const Material(
...@@ -98,10 +96,9 @@ void main() { ...@@ -98,10 +96,9 @@ void main() {
), ),
)); ));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics( expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
hasCheckedState: true, hasCheckedState: true,
hasEnabledState: true, hasEnabledState: true,
isFocusable: true,
)); ));
await tester.pumpWidget(const Material( await tester.pumpWidget(const Material(
...@@ -111,7 +108,7 @@ void main() { ...@@ -111,7 +108,7 @@ void main() {
), ),
)); ));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics( expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
hasCheckedState: true, hasCheckedState: true,
hasEnabledState: true, hasEnabledState: true,
isChecked: true, isChecked: true,
...@@ -133,14 +130,13 @@ void main() { ...@@ -133,14 +130,13 @@ void main() {
), ),
)); ));
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics( expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
label: 'foo', label: 'foo',
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
hasCheckedState: true, hasCheckedState: true,
hasEnabledState: true, hasEnabledState: true,
isEnabled: true, isEnabled: true,
hasTapAction: true, hasTapAction: true,
isFocusable: true,
)); ));
handle.dispose(); handle.dispose();
}); });
...@@ -206,7 +202,6 @@ void main() { ...@@ -206,7 +202,6 @@ void main() {
SemanticsFlag.hasCheckedState, SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled, SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
], ],
actions: <SemanticsAction>[SemanticsAction.tap], actions: <SemanticsAction>[SemanticsAction.tap],
), hasLength(1)); ), hasLength(1));
...@@ -227,7 +222,6 @@ void main() { ...@@ -227,7 +222,6 @@ void main() {
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled, SemanticsFlag.isEnabled,
SemanticsFlag.isChecked, SemanticsFlag.isChecked,
SemanticsFlag.isFocusable,
], ],
actions: <SemanticsAction>[SemanticsAction.tap], actions: <SemanticsAction>[SemanticsAction.tap],
), hasLength(1)); ), hasLength(1));
...@@ -247,7 +241,6 @@ void main() { ...@@ -247,7 +241,6 @@ void main() {
SemanticsFlag.hasCheckedState, SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled, SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
], ],
actions: <SemanticsAction>[SemanticsAction.tap], actions: <SemanticsAction>[SemanticsAction.tap],
), hasLength(1)); ), hasLength(1));
...@@ -281,7 +274,7 @@ void main() { ...@@ -281,7 +274,7 @@ void main() {
); );
await tester.tap(find.byType(Checkbox)); await tester.tap(find.byType(Checkbox));
final RenderObject object = tester.firstRenderObject(find.byType(Focus)); final RenderObject object = tester.firstRenderObject(find.byType(Checkbox));
expect(checkboxValue, true); expect(checkboxValue, true);
expect(semanticEvent, <String, dynamic>{ expect(semanticEvent, <String, dynamic>{
...@@ -311,9 +304,7 @@ void main() { ...@@ -311,9 +304,7 @@ void main() {
} }
RenderToggleable getCheckboxRenderer() { RenderToggleable getCheckboxRenderer() {
return tester.renderObject<RenderToggleable>(find.byWidgetPredicate((Widget widget) { return tester.renderObject<RenderToggleable>(find.byType(Checkbox));
return widget.runtimeType.toString() == '_CheckboxRenderObjectWidget';
}));
} }
await tester.pumpWidget(buildFrame(false)); await tester.pumpWidget(buildFrame(false));
...@@ -365,9 +356,7 @@ void main() { ...@@ -365,9 +356,7 @@ void main() {
} }
RenderToggleable getCheckboxRenderer() { RenderToggleable getCheckboxRenderer() {
return tester.renderObject<RenderToggleable>(find.byWidgetPredicate((Widget widget) { return tester.renderObject<RenderToggleable>(find.byType(Checkbox));
return widget.runtimeType.toString() == '_CheckboxRenderObjectWidget';
}));
} }
await tester.pumpWidget(buildFrame(checkColor: const Color(0xFFFFFFFF))); await tester.pumpWidget(buildFrame(checkColor: const Color(0xFFFFFFFF)));
...@@ -387,181 +376,4 @@ void main() { ...@@ -387,181 +376,4 @@ void main() {
expect(getCheckboxRenderer(), paints..rrect(color: const Color(0xFF000000))); // paints's color is 0xFF000000 (params) expect(getCheckboxRenderer(), paints..rrect(color: const Color(0xFF000000))); // paints's color is 0xFF000000 (params)
}); });
testWidgets('Checkbox is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Checkbox');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Checkbox(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(color: Colors.orange[500])
..rrect(
color: const Color(0xff1e88e5),
rrect: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)))
..path(color: Colors.white),
);
// Check the false value.
value = false;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..circle(color: Colors.orange[500])
..drrect(
color: const Color(0x8a000000),
outer: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)),
inner: RRect.fromLTRBR(393.0,
293.0, 407.0, 307.0, const Radius.circular(-1.0))),
);
// Check what happens when disabled.
value = false;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..drrect(
color: const Color(0x61000000),
outer: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)),
inner: RRect.fromLTRBR(393.0,
293.0, 407.0, 307.0, const Radius.circular(-1.0))),
);
});
testWidgets('Checkbox can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Checkbox(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
hoverColor: Colors.orange[500],
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..rrect(
color: const Color(0xff1e88e5),
rrect: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)))
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Checkbox)));
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..rrect(
color: const Color(0xff1e88e5),
rrect: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)))
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
);
// Check what happens when disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Checkbox))),
paints
..rrect(
color: const Color(0x61000000),
rrect: RRect.fromLTRBR(
391.0, 291.0, 409.0, 309.0, const Radius.circular(1.0)))
..path(color: const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0),
);
});
testWidgets('Checkbox can be toggled by keyboard shortcuts', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Checkbox(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(value, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isTrue);
});
} }
...@@ -9,7 +9,6 @@ import 'package:flutter/services.dart'; ...@@ -9,7 +9,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
void main() { void main() {
...@@ -132,7 +131,6 @@ void main() { ...@@ -132,7 +131,6 @@ void main() {
SemanticsFlag.hasCheckedState, SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled, SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
], ],
actions: <SemanticsAction>[ actions: <SemanticsAction>[
SemanticsAction.tap, SemanticsAction.tap,
...@@ -159,7 +157,6 @@ void main() { ...@@ -159,7 +157,6 @@ void main() {
SemanticsFlag.isChecked, SemanticsFlag.isChecked,
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled, SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
], ],
actions: <SemanticsAction>[ actions: <SemanticsAction>[
SemanticsAction.tap, SemanticsAction.tap,
...@@ -184,7 +181,6 @@ void main() { ...@@ -184,7 +181,6 @@ void main() {
SemanticsFlag.isInMutuallyExclusiveGroup, SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.hasCheckedState, SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState, SemanticsFlag.hasEnabledState,
SemanticsFlag.isFocusable,
], ],
), ),
], ],
...@@ -236,7 +232,7 @@ void main() { ...@@ -236,7 +232,7 @@ void main() {
)); ));
await tester.tap(find.byKey(key)); await tester.tap(find.byKey(key));
final RenderObject object = tester.firstRenderObject(find.byType(Focus)); final RenderObject object = tester.firstRenderObject(find.byKey(key));
expect(radioValue, 1); expect(radioValue, 1);
expect(semanticEvent, <String, dynamic>{ expect(semanticEvent, <String, dynamic>{
...@@ -283,227 +279,5 @@ void main() { ...@@ -283,227 +279,5 @@ void main() {
matchesGoldenFile('radio.ink_ripple.png'), matchesGoldenFile('radio.ink_ripple.png'),
); );
}, skip: isBrowser); }, skip: isBrowser);
testWidgets('Radio is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Radio');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
int groupValue = 0;
const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 100,
height: 100,
color: Colors.white,
child: Radio<int>(
key: radioKey,
value: 0,
onChanged: enabled ? (int newValue) {
setState(() {
groupValue = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
groupValue: groupValue,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: Colors.orange[500])
..circle(color: const Color(0xff1e88e5))
..circle(color: const Color(0xff1e88e5)),
);
// Check when the radio isn't selected.
groupValue = 1;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0)
);
// Check when the radio is selected, but disabled.
groupValue = 0;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: const Color(0x61000000))
..circle(color: const Color(0x61000000)),
);
});
testWidgets('Radio can be hovered and has correct focus color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
int groupValue = 0;
const Key radioKey = Key('radio');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 100,
height: 100,
color: Colors.white,
child: Radio<int>(
key: radioKey,
value: 0,
onChanged: enabled ? (int newValue) {
setState(() {
groupValue = newValue;
});
} : null,
hoverColor: Colors.orange[500],
groupValue: groupValue,
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: const Color(0xff1e88e5))
..circle(color: const Color(0xff1e88e5)),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byKey(radioKey)));
// Check when the radio isn't selected.
groupValue = 1;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: Colors.orange[500])
..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0)
);
// Check when the radio is selected, but disabled.
groupValue = 0;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byKey(radioKey))),
paints
..rect(
color: const Color(0xffffffff),
rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0))
..circle(color: const Color(0x61000000))
..circle(color: const Color(0x61000000)),
);
});
testWidgets('Radio can be toggled by keyboard shortcuts', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
int groupValue = 1;
const Key radioKey0 = Key('radio0');
const Key radioKey1 = Key('radio1');
final FocusNode focusNode1 = FocusNode(debugLabel: 'radio1');
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Container(
width: 100,
height: 100,
color: Colors.white,
child: Row(
children: <Widget>[
Radio<int>(
key: radioKey0,
value: 0,
onChanged: enabled ? (int newValue) {
setState(() {
groupValue = newValue;
});
} : null,
hoverColor: Colors.orange[500],
groupValue: groupValue,
autofocus: true,
),
Radio<int>(
key: radioKey1,
value: 1,
onChanged: enabled ? (int newValue) {
setState(() {
groupValue = newValue;
});
} : null,
hoverColor: Colors.orange[500],
groupValue: groupValue,
focusNode: focusNode1,
),
],
),
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(groupValue, equals(0));
focusNode1.requestFocus();
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(groupValue, equals(1));
});
} }
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -48,7 +47,6 @@ void main() { ...@@ -48,7 +47,6 @@ void main() {
Shortcuts( Shortcuts(
shortcuts: <LogicalKeySet, Intent>{ shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key), LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key),
LogicalKeySet(LogicalKeyboardKey.space): const Intent(SelectAction.key),
}, },
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -76,12 +74,6 @@ void main() { ...@@ -76,12 +74,6 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(pressed, isTrue); expect(pressed, isTrue);
pressed = false;
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(pressed, kIsWeb ? isFalse : isTrue);
}); });
testWidgets('materialTapTargetSize.padded expands hit test area', (WidgetTester tester) async { testWidgets('materialTapTargetSize.padded expands hit test area', (WidgetTester tester) async {
......
...@@ -518,7 +518,7 @@ void main() { ...@@ -518,7 +518,7 @@ void main() {
), ),
); );
await tester.tap(find.byType(Switch)); await tester.tap(find.byType(Switch));
final RenderObject object = tester.firstRenderObject(find.byType(Focus)); final RenderObject object = tester.firstRenderObject(find.byType(Switch));
expect(value, true); expect(value, true);
expect(semanticEvent, <String, dynamic>{ expect(semanticEvent, <String, dynamic>{
...@@ -623,198 +623,4 @@ void main() { ...@@ -623,198 +623,4 @@ void main() {
}); });
testWidgets('Switch is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x801e88e5),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: Colors.orange[500])
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xff1e88e5)),
);
// Check the false value.
value = false;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x52000000),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: Colors.orange[500])
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xfffafafa)),
);
// Check what happens when disabled.
value = false;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x1f000000),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xffbdbdbd)),
);
});
testWidgets('Switch can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
hoverColor: Colors.orange[500],
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x801e88e5),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xff1e88e5)),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
addTearDown(gesture.removePointer);
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x801e88e5),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: Colors.orange[500])
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xff1e88e5)),
);
// Check what happens when disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x1f000000),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: const Color(0xffbdbdbd)),
);
});
testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(value, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isTrue);
});
} }
...@@ -66,12 +66,9 @@ void main() { ...@@ -66,12 +66,9 @@ void main() {
FocusNode( FocusNode(
debugLabel: 'Label', debugLabel: 'Label',
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); final List<String> description = builder.properties.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)).map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[ expect(description, <String>[
'context: null', 'debugLabel: "Label"',
'canRequestFocus: true',
'hasFocus: false',
'hasPrimaryFocus: false'
]); ]);
}); });
}); });
...@@ -624,12 +621,9 @@ void main() { ...@@ -624,12 +621,9 @@ void main() {
FocusScopeNode( FocusScopeNode(
debugLabel: 'Scope Label', debugLabel: 'Scope Label',
).debugFillProperties(builder); ).debugFillProperties(builder);
final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); final List<String> description = builder.properties.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)).map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[ expect(description, <String>[
'context: null', 'debugLabel: "Scope Label"',
'canRequestFocus: true',
'hasFocus: false',
'hasPrimaryFocus: false'
]); ]);
}); });
testWidgets('debugDescribeFocusTree produces correct output', (WidgetTester tester) async { testWidgets('debugDescribeFocusTree produces correct output', (WidgetTester tester) async {
...@@ -669,36 +663,43 @@ void main() { ...@@ -669,36 +663,43 @@ void main() {
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n' ' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n'
' │\n' ' │\n'
' └─rootScope: FocusScopeNode#00000(Root Focus Scope)\n' ' └─rootScope: FocusScopeNode#00000(Root Focus Scope)\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ debugLabel: "Root Focus Scope"\n'
' │ focusedChildren: FocusScopeNode#00000\n' ' │ focusedChildren: FocusScopeNode#00000\n'
' │\n' ' │\n'
' ├─Child 1: FocusScopeNode#00000(Scope 1)\n' ' ├─Child 1: FocusScopeNode#00000(Scope 1)\n'
' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Scope 1"\n'
' │ │\n' ' │ │\n'
' │ └─Child 1: FocusNode#00000(Parent 1)\n' ' │ └─Child 1: FocusNode#00000(Parent 1)\n'
' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Parent 1"\n'
' │ │\n' ' │ │\n'
' │ ├─Child 1: FocusNode#00000(Child 1)\n' ' │ ├─Child 1: FocusNode#00000(Child 1)\n'
' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Child 1"\n'
' │ │\n' ' │ │\n'
' │ └─Child 2: FocusNode#00000\n' ' │ └─Child 2: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n' ' │ context: Container-[GlobalKey#00000]\n'
' │\n' ' │\n'
' └─Child 2: FocusScopeNode#00000\n' ' └─Child 2: FocusScopeNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n' ' │ context: Container-[GlobalKey#00000]\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ focusedChildren: FocusNode#00000(Child 4)\n' ' │ focusedChildren: FocusNode#00000(Child 4)\n'
' │\n' ' │\n'
' └─Child 1: FocusNode#00000(Parent 2)\n' ' └─Child 1: FocusNode#00000(Parent 2)\n'
' │ context: Container-[GlobalKey#00000]\n' ' │ context: Container-[GlobalKey#00000]\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ debugLabel: "Parent 2"\n'
' │\n' ' │\n'
' ├─Child 1: FocusNode#00000(Child 3)\n' ' ├─Child 1: FocusNode#00000(Child 3)\n'
' │ context: Container-[GlobalKey#00000]\n' ' │ context: Container-[GlobalKey#00000]\n'
' │ debugLabel: "Child 3"\n'
' │\n' ' │\n'
' └─Child 2: FocusNode#00000(Child 4)\n' ' └─Child 2: FocusNode#00000(Child 4)\n'
' context: Container-[GlobalKey#00000]\n' ' context: Container-[GlobalKey#00000]\n'
' PRIMARY FOCUS\n' ' FOCUSED\n'
' debugLabel: "Child 4"\n'
)); ));
}); });
}); });
......
...@@ -229,29 +229,34 @@ void main() { ...@@ -229,29 +229,34 @@ void main() {
parentFocusScope.toStringDeep(), parentFocusScope.toStringDeep(),
equalsIgnoringHashCodes('FocusScopeNode#00000(Parent Scope Node)\n' equalsIgnoringHashCodes('FocusScopeNode#00000(Parent Scope Node)\n'
' │ context: FocusScope\n' ' │ context: FocusScope\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ debugLabel: "Parent Scope Node"\n'
' │ focusedChildren: FocusNode#00000(Child)\n' ' │ focusedChildren: FocusNode#00000(Child)\n'
' │\n' ' │\n'
' └─Child 1: FocusNode#00000(Child)\n' ' └─Child 1: FocusNode#00000(Child)\n'
' context: Focus\n' ' context: Focus\n'
' PRIMARY FOCUS\n'), ' FOCUSED\n'
' debugLabel: "Child"\n'),
); );
expect(WidgetsBinding.instance.focusManager.rootScope, hasAGoodToStringDeep); expect(WidgetsBinding.instance.focusManager.rootScope, hasAGoodToStringDeep);
expect( expect(
WidgetsBinding.instance.focusManager.rootScope.toStringDeep(minLevel: DiagnosticLevel.info), WidgetsBinding.instance.focusManager.rootScope.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes('FocusScopeNode#00000(Root Focus Scope)\n' equalsIgnoringHashCodes('FocusScopeNode#00000(Root Focus Scope)\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ debugLabel: "Root Focus Scope"\n'
' │ focusedChildren: FocusScopeNode#00000(Parent Scope Node)\n' ' │ focusedChildren: FocusScopeNode#00000(Parent Scope Node)\n'
' │\n' ' │\n'
' └─Child 1: FocusScopeNode#00000(Parent Scope Node)\n' ' └─Child 1: FocusScopeNode#00000(Parent Scope Node)\n'
' │ context: FocusScope\n' ' │ context: FocusScope\n'
' │ IN FOCUS PATH\n' ' │ FOCUSED\n'
' │ debugLabel: "Parent Scope Node"\n'
' │ focusedChildren: FocusNode#00000(Child)\n' ' │ focusedChildren: FocusNode#00000(Child)\n'
' │\n' ' │\n'
' └─Child 1: FocusNode#00000(Child)\n' ' └─Child 1: FocusNode#00000(Child)\n'
' context: Focus\n' ' context: Focus\n'
' PRIMARY FOCUS\n'), ' FOCUSED\n'
' debugLabel: "Child"\n'),
); );
// Add the child focus scope to the focus tree. // Add the child focus scope to the focus tree.
......
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