Unverified Commit ed36fe5d authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Revert "Add focus nodes, hover, and shortcuts to switches, checkboxes, and...

Revert "Add focus nodes, hover, and shortcuts to switches, checkboxes, and radio buttons. (#43213)" (#43367)

This reverts commit 90006720.
parent 90006720
...@@ -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);
}
...@@ -1046,7 +1046,6 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { ...@@ -1046,7 +1046,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>{
...@@ -286,227 +282,5 @@ void main() { ...@@ -286,227 +282,5 @@ void main() {
), ),
); );
}, 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