Revert "Add focus nodes, hover, and shortcuts to switches, checkboxes, and radio buttons. (#43213)" (#43367)
This reverts commit 900067207e.
This commit is contained in:
@@ -4,8 +4,6 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -54,7 +52,7 @@ class Checkbox extends StatefulWidget {
|
||||
/// * [onChanged], which is called when the value of the checkbox should
|
||||
/// 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({
|
||||
Key key,
|
||||
@required this.value,
|
||||
@@ -62,14 +60,9 @@ class Checkbox extends StatefulWidget {
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.checkColor,
|
||||
this.focusColor,
|
||||
this.hoverColor,
|
||||
this.materialTapTargetSize,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
assert(autofocus != null),
|
||||
super(key: key);
|
||||
|
||||
/// Whether this checkbox is checked.
|
||||
@@ -120,10 +113,10 @@ class Checkbox extends StatefulWidget {
|
||||
///
|
||||
/// Checkbox displays a dash when its value is null.
|
||||
///
|
||||
/// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged]
|
||||
/// callback will be applied to true if the current value is false, to null if
|
||||
/// value is true, and to false if value is null (i.e. it cycles through false
|
||||
/// => true => null => false when tapped).
|
||||
/// When a tri-state checkbox is tapped its [onChanged] callback will be
|
||||
/// applied to true if the current value is null or false, false otherwise.
|
||||
/// Typically tri-state checkboxes are disabled (the onChanged callback is
|
||||
/// null) so they don't respond to taps.
|
||||
///
|
||||
/// If tristate is false (the default), [value] must not be null.
|
||||
final bool tristate;
|
||||
@@ -137,18 +130,6 @@ class Checkbox extends StatefulWidget {
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
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.
|
||||
static const double width = 18.0;
|
||||
|
||||
@@ -157,68 +138,6 @@ class Checkbox extends StatefulWidget {
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
@@ -233,36 +152,15 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
|
||||
break;
|
||||
}
|
||||
final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
|
||||
return MouseRegion(
|
||||
onEnter: enabled ? _handleMouseEnter : null,
|
||||
onExit: enabled ? _handleMouseExit : null,
|
||||
child: Actions(
|
||||
actions: _actionMap,
|
||||
child: Focus(
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
canRequestFocus: enabled,
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
return _CheckboxRenderObjectWidget(
|
||||
value: widget.value,
|
||||
tristate: widget.tristate,
|
||||
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
|
||||
checkColor: widget.checkColor ?? const Color(0xFFFFFFFF),
|
||||
inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
|
||||
onChanged: widget.onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -275,13 +173,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
@required this.activeColor,
|
||||
@required this.checkColor,
|
||||
@required this.inactiveColor,
|
||||
@required this.focusColor,
|
||||
@required this.hoverColor,
|
||||
@required this.onChanged,
|
||||
@required this.vsync,
|
||||
@required this.additionalConstraints,
|
||||
@required this.hasFocus,
|
||||
@required this.hovering,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
assert(activeColor != null),
|
||||
@@ -291,13 +185,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
|
||||
final bool value;
|
||||
final bool tristate;
|
||||
final bool hasFocus;
|
||||
final bool hovering;
|
||||
final Color activeColor;
|
||||
final Color checkColor;
|
||||
final Color inactiveColor;
|
||||
final Color focusColor;
|
||||
final Color hoverColor;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
@@ -309,13 +199,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
activeColor: activeColor,
|
||||
checkColor: checkColor,
|
||||
inactiveColor: inactiveColor,
|
||||
focusColor: focusColor,
|
||||
hoverColor: hoverColor,
|
||||
onChanged: onChanged,
|
||||
vsync: vsync,
|
||||
additionalConstraints: additionalConstraints,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -326,13 +212,9 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..activeColor = activeColor
|
||||
..checkColor = checkColor
|
||||
..inactiveColor = inactiveColor
|
||||
..focusColor = focusColor
|
||||
..hoverColor = hoverColor
|
||||
..onChanged = onChanged
|
||||
..additionalConstraints = additionalConstraints
|
||||
..vsync = vsync
|
||||
..hasFocus = hasFocus
|
||||
..hovering = hovering;
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,12 +229,8 @@ class _RenderCheckbox extends RenderToggleable {
|
||||
Color activeColor,
|
||||
this.checkColor,
|
||||
Color inactiveColor,
|
||||
Color focusColor,
|
||||
Color hoverColor,
|
||||
BoxConstraints additionalConstraints,
|
||||
ValueChanged<bool> onChanged,
|
||||
bool hasFocus,
|
||||
bool hovering,
|
||||
@required TickerProvider vsync,
|
||||
}) : _oldValue = value,
|
||||
super(
|
||||
@@ -360,13 +238,9 @@ class _RenderCheckbox extends RenderToggleable {
|
||||
tristate: tristate,
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
focusColor: focusColor,
|
||||
hoverColor: hoverColor,
|
||||
onChanged: onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
);
|
||||
|
||||
bool _oldValue;
|
||||
|
||||
@@ -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;
|
||||
|
||||
void _handleAction(FocusNode node, Intent intent) {
|
||||
_startSplash(context: node.context);
|
||||
_handleTap(node.context);
|
||||
}
|
||||
|
||||
Action _createAction() {
|
||||
return CallbackAction(
|
||||
ActivateAction.key,
|
||||
onInvoke: _handleAction,
|
||||
onInvoke: (FocusNode node, Intent intent) {
|
||||
_startSplash(context: node.context);
|
||||
_handleTap(node.context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_actionMap = <LocalKey, ActionFactory>{
|
||||
SelectAction.key: _createAction,
|
||||
if (!kIsWeb) ActivateAction.key: _createAction,
|
||||
};
|
||||
_actionMap = <LocalKey, ActionFactory>{ ActivateAction.key: _createAction };
|
||||
WidgetsBinding.instance.focusManager.addHighlightModeListener(_handleFocusHighlightModeChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -109,13 +107,8 @@ class Radio<T> extends StatefulWidget {
|
||||
@required this.groupValue,
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.focusColor,
|
||||
this.hoverColor,
|
||||
this.materialTapTargetSize,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
}) : assert(autofocus != null),
|
||||
super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
/// The value represented by this radio button.
|
||||
final T value;
|
||||
@@ -168,77 +161,15 @@ class Radio<T> extends StatefulWidget {
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
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
|
||||
_RadioState<T> createState() => _RadioState<T>();
|
||||
}
|
||||
|
||||
class _RadioState<T> extends State<Radio<T>> 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) {
|
||||
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; });
|
||||
bool get _enabled => widget.onChanged != null;
|
||||
|
||||
Color _getInactiveColor(ThemeData themeData) {
|
||||
return enabled ? themeData.unselectedWidgetColor : themeData.disabledColor;
|
||||
return _enabled ? themeData.unselectedWidgetColor : themeData.disabledColor;
|
||||
}
|
||||
|
||||
void _handleChanged(bool selected) {
|
||||
@@ -260,34 +191,13 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
|
||||
break;
|
||||
}
|
||||
final BoxConstraints additionalConstraints = BoxConstraints.tight(size);
|
||||
return MouseRegion(
|
||||
onEnter: enabled ? _handleMouseEnter : null,
|
||||
onExit: enabled ? _handleMouseExit : null,
|
||||
child: Actions(
|
||||
actions: _actionMap,
|
||||
child: Focus(
|
||||
focusNode: widget.focusNode,
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
return _RadioRenderObjectWidget(
|
||||
selected: widget.value == widget.groupValue,
|
||||
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
|
||||
inactiveColor: _getInactiveColor(themeData),
|
||||
onChanged: _enabled ? _handleChanged : null,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -298,13 +208,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
@required this.selected,
|
||||
@required this.activeColor,
|
||||
@required this.inactiveColor,
|
||||
@required this.focusColor,
|
||||
@required this.hoverColor,
|
||||
@required this.additionalConstraints,
|
||||
this.onChanged,
|
||||
@required this.vsync,
|
||||
@required this.hasFocus,
|
||||
@required this.hovering,
|
||||
}) : assert(selected != null),
|
||||
assert(activeColor != null),
|
||||
assert(inactiveColor != null),
|
||||
@@ -312,12 +218,8 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
super(key: key);
|
||||
|
||||
final bool selected;
|
||||
final bool hasFocus;
|
||||
final bool hovering;
|
||||
final Color inactiveColor;
|
||||
final Color activeColor;
|
||||
final Color focusColor;
|
||||
final Color hoverColor;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
@@ -327,13 +229,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
value: selected,
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
focusColor: focusColor,
|
||||
hoverColor: hoverColor,
|
||||
onChanged: onChanged,
|
||||
vsync: vsync,
|
||||
additionalConstraints: additionalConstraints,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -342,13 +240,9 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..value = selected
|
||||
..activeColor = activeColor
|
||||
..inactiveColor = inactiveColor
|
||||
..focusColor = focusColor
|
||||
..hoverColor = hoverColor
|
||||
..onChanged = onChanged
|
||||
..additionalConstraints = additionalConstraints
|
||||
..vsync = vsync
|
||||
..hasFocus = hasFocus
|
||||
..hovering = hovering;
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,25 +251,17 @@ class _RenderRadio extends RenderToggleable {
|
||||
bool value,
|
||||
Color activeColor,
|
||||
Color inactiveColor,
|
||||
Color focusColor,
|
||||
Color hoverColor,
|
||||
ValueChanged<bool> onChanged,
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TickerProvider vsync,
|
||||
bool hasFocus,
|
||||
bool hovering,
|
||||
}) : super(
|
||||
value: value,
|
||||
tristate: false,
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
focusColor: focusColor,
|
||||
hoverColor: hoverColor,
|
||||
onChanged: onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -74,13 +74,9 @@ class Switch extends StatefulWidget {
|
||||
this.inactiveThumbImage,
|
||||
this.materialTapTargetSize,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.focusColor,
|
||||
this.hoverColor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
}) : _switchType = _SwitchType.material,
|
||||
assert(dragStartBehavior != null),
|
||||
super(key: key);
|
||||
}) : _switchType = _SwitchType.material,
|
||||
assert(dragStartBehavior != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a [CupertinoSwitch] if the target platform is iOS, creates a
|
||||
/// material design switch otherwise.
|
||||
@@ -102,13 +98,8 @@ class Switch extends StatefulWidget {
|
||||
this.inactiveThumbImage,
|
||||
this.materialTapTargetSize,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.focusColor,
|
||||
this.hoverColor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
}) : assert(autofocus != null),
|
||||
_switchType = _SwitchType.adaptive,
|
||||
super(key: key);
|
||||
}) : _switchType = _SwitchType.adaptive,
|
||||
super(key: key);
|
||||
|
||||
/// Whether this switch is on or off.
|
||||
///
|
||||
@@ -189,18 +180,6 @@ class Switch extends StatefulWidget {
|
||||
/// {@macro flutter.cupertino.switch.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
|
||||
_SwitchState createState() => _SwitchState();
|
||||
|
||||
@@ -213,53 +192,6 @@ class Switch extends StatefulWidget {
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
@@ -273,12 +205,6 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
|
||||
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) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData theme = Theme.of(context);
|
||||
@@ -286,12 +212,10 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
|
||||
|
||||
final Color activeThumbColor = widget.activeColor ?? theme.toggleableActiveColor;
|
||||
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 inactiveTrackColor;
|
||||
if (enabled) {
|
||||
if (widget.onChanged != null) {
|
||||
const Color black32 = Color(0x52000000); // Black with 32% opacity
|
||||
inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade400 : Colors.grey.shade50);
|
||||
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white30 : black32);
|
||||
@@ -300,59 +224,33 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
|
||||
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
onEnter: enabled ? _handleMouseEnter : null,
|
||||
onExit: enabled ? _handleMouseExit : null,
|
||||
child: Actions(
|
||||
actions: _actionMap,
|
||||
child: Focus(
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
canRequestFocus: enabled,
|
||||
debugLabel: '${describeIdentity(widget)}({$widget.value})',
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
final bool hasFocus = Focus.of(context).hasFocus;
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
return _SwitchRenderObjectWidget(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
value: widget.value,
|
||||
activeColor: activeThumbColor,
|
||||
inactiveColor: inactiveThumbColor,
|
||||
activeThumbImage: widget.activeThumbImage,
|
||||
inactiveThumbImage: widget.inactiveThumbImage,
|
||||
activeTrackColor: activeTrackColor,
|
||||
inactiveTrackColor: inactiveTrackColor,
|
||||
configuration: createLocalImageConfiguration(context),
|
||||
onChanged: widget.onChanged,
|
||||
additionalConstraints: BoxConstraints.tight(getSwitchSize(theme)),
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildCupertinoSwitch(BuildContext context) {
|
||||
final Size size = getSwitchSize(Theme.of(context));
|
||||
return Focus(
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autofocus,
|
||||
child: Container(
|
||||
width: size.width, // Same size as the Material switch.
|
||||
height: size.height,
|
||||
alignment: Alignment.center,
|
||||
child: CupertinoSwitch(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
activeColor: widget.activeColor,
|
||||
),
|
||||
return Container(
|
||||
width: size.width, // Same size as the Material switch.
|
||||
height: size.height,
|
||||
alignment: Alignment.center,
|
||||
child: CupertinoSwitch(
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
value: widget.value,
|
||||
onChanged: widget.onChanged,
|
||||
activeColor: widget.activeColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -386,8 +284,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
this.value,
|
||||
this.activeColor,
|
||||
this.inactiveColor,
|
||||
this.hoverColor,
|
||||
this.focusColor,
|
||||
this.activeThumbImage,
|
||||
this.inactiveThumbImage,
|
||||
this.activeTrackColor,
|
||||
@@ -397,15 +293,11 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
this.vsync,
|
||||
this.additionalConstraints,
|
||||
this.dragStartBehavior,
|
||||
this.hasFocus,
|
||||
this.hovering,
|
||||
}) : super(key: key);
|
||||
|
||||
final bool value;
|
||||
final Color activeColor;
|
||||
final Color inactiveColor;
|
||||
final Color hoverColor;
|
||||
final Color focusColor;
|
||||
final ImageProvider activeThumbImage;
|
||||
final ImageProvider inactiveThumbImage;
|
||||
final Color activeTrackColor;
|
||||
@@ -415,8 +307,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
final bool hasFocus;
|
||||
final bool hovering;
|
||||
|
||||
@override
|
||||
_RenderSwitch createRenderObject(BuildContext context) {
|
||||
@@ -425,8 +315,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
value: value,
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
hoverColor: hoverColor,
|
||||
focusColor: focusColor,
|
||||
activeThumbImage: activeThumbImage,
|
||||
inactiveThumbImage: inactiveThumbImage,
|
||||
activeTrackColor: activeTrackColor,
|
||||
@@ -435,8 +323,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
onChanged: onChanged,
|
||||
textDirection: Directionality.of(context),
|
||||
additionalConstraints: additionalConstraints,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
vsync: vsync,
|
||||
);
|
||||
}
|
||||
@@ -447,8 +333,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..value = value
|
||||
..activeColor = activeColor
|
||||
..inactiveColor = inactiveColor
|
||||
..hoverColor = hoverColor
|
||||
..focusColor = focusColor
|
||||
..activeThumbImage = activeThumbImage
|
||||
..inactiveThumbImage = inactiveThumbImage
|
||||
..activeTrackColor = activeTrackColor
|
||||
@@ -458,8 +342,6 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..textDirection = Directionality.of(context)
|
||||
..additionalConstraints = additionalConstraints
|
||||
..dragStartBehavior = dragStartBehavior
|
||||
..hasFocus = hasFocus
|
||||
..hovering = hovering
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
@@ -469,8 +351,6 @@ class _RenderSwitch extends RenderToggleable {
|
||||
bool value,
|
||||
Color activeColor,
|
||||
Color inactiveColor,
|
||||
Color hoverColor,
|
||||
Color focusColor,
|
||||
ImageProvider activeThumbImage,
|
||||
ImageProvider inactiveThumbImage,
|
||||
Color activeTrackColor,
|
||||
@@ -479,10 +359,8 @@ class _RenderSwitch extends RenderToggleable {
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TextDirection textDirection,
|
||||
ValueChanged<bool> onChanged,
|
||||
DragStartBehavior dragStartBehavior,
|
||||
bool hasFocus,
|
||||
bool hovering,
|
||||
@required TickerProvider vsync,
|
||||
DragStartBehavior dragStartBehavior,
|
||||
}) : assert(textDirection != null),
|
||||
_activeThumbImage = activeThumbImage,
|
||||
_inactiveThumbImage = inactiveThumbImage,
|
||||
@@ -495,12 +373,8 @@ class _RenderSwitch extends RenderToggleable {
|
||||
tristate: false,
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
hoverColor: hoverColor,
|
||||
focusColor: focusColor,
|
||||
onChanged: onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
hasFocus: hasFocus,
|
||||
hovering: hovering,
|
||||
vsync: vsync,
|
||||
) {
|
||||
_drag = HorizontalDragGestureRecognizer()
|
||||
|
||||
@@ -10,15 +10,9 @@ import 'package:flutter/scheduler.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
|
||||
// Duration of the animation that moves the toggle from one state to another.
|
||||
const Duration _kToggleDuration = Duration(milliseconds: 200);
|
||||
|
||||
// Radius of the radial reaction over time.
|
||||
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.
|
||||
///
|
||||
/// This class handles storing the current value, dispatching ValueChanged on a
|
||||
@@ -34,13 +28,9 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
bool tristate = false,
|
||||
@required Color activeColor,
|
||||
@required Color inactiveColor,
|
||||
Color hoverColor,
|
||||
Color focusColor,
|
||||
ValueChanged<bool> onChanged,
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TickerProvider vsync,
|
||||
bool hasFocus = false,
|
||||
bool hovering = false,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
assert(activeColor != null),
|
||||
@@ -50,11 +40,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
_tristate = tristate,
|
||||
_activeColor = activeColor,
|
||||
_inactiveColor = inactiveColor,
|
||||
_hoverColor = hoverColor ?? activeColor.withAlpha(kRadialReactionAlpha),
|
||||
_focusColor = focusColor ?? activeColor.withAlpha(kRadialReactionAlpha),
|
||||
_onChanged = onChanged,
|
||||
_hasFocus = hasFocus,
|
||||
_hovering = hovering,
|
||||
_vsync = vsync,
|
||||
super(additionalConstraints: additionalConstraints) {
|
||||
_tap = TapGestureRecognizer()
|
||||
@@ -80,24 +66,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
parent: _reactionController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
)..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.
|
||||
@@ -134,66 +102,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
AnimationController _reactionController;
|
||||
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.
|
||||
TickerProvider get vsync => _vsync;
|
||||
TickerProvider _vsync;
|
||||
@@ -284,54 +192,6 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
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.
|
||||
///
|
||||
/// If the control is tapped, [onChanged] is called immediately with the new
|
||||
@@ -463,18 +323,12 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
/// point at which the user interacted with the control, which is handled
|
||||
/// automatically).
|
||||
void paintRadialReaction(Canvas canvas, Offset offset, Offset origin) {
|
||||
if (!_reaction.isDismissed || !_reactionFocusFade.isDismissed || !_reactionHoverFade.isDismissed) {
|
||||
final Paint reactionPaint = Paint()
|
||||
..color = Color.lerp(
|
||||
Color.lerp(activeColor.withAlpha(kRadialReactionAlpha), hoverColor, _reactionHoverFade.value),
|
||||
focusColor,
|
||||
_reactionFocusFade.value,
|
||||
);
|
||||
if (!_reaction.isDismissed) {
|
||||
// TODO(abarth): We should have a different reaction color when position is zero.
|
||||
final Paint reactionPaint = Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
|
||||
final Offset center = Offset.lerp(_downPosition ?? origin, origin, _reaction.value);
|
||||
final double reactionRadius = hasFocus || hovering
|
||||
? kRadialReactionRadius
|
||||
: _kRadialReactionRadiusTween.evaluate(_reaction);
|
||||
canvas.drawCircle(center + offset, reactionRadius, reactionPaint);
|
||||
final double radius = _kRadialReactionRadiusTween.evaluate(_reaction);
|
||||
canvas.drawCircle(center + offset, radius, reactionPaint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,8 +376,8 @@ class DoNothingAction extends Action {
|
||||
/// An action that invokes the currently focused control.
|
||||
///
|
||||
/// 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
|
||||
/// the default keyboard map in [WidgetsApp].
|
||||
/// activate a control. It is bound to [LogicalKeyboardKey.enter] in the default
|
||||
/// keyboard map in [WidgetsApp].
|
||||
abstract class ActivateAction extends Action {
|
||||
/// Creates a [ActivateAction] with a fixed [key];
|
||||
const ActivateAction() : super(key);
|
||||
@@ -385,16 +385,3 @@ abstract class ActivateAction extends Action {
|
||||
/// The [LocalKey] that uniquely identifies this action.
|
||||
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 {
|
||||
LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down),
|
||||
LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up),
|
||||
LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key),
|
||||
LogicalKeySet(LogicalKeyboardKey.space): const Intent(SelectAction.key),
|
||||
};
|
||||
|
||||
final Map<LocalKey, ActionFactory> _actionMap = <LocalKey, ActionFactory>{
|
||||
|
||||
@@ -897,8 +897,8 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<BuildContext>('context', context, defaultValue: null));
|
||||
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('hasPrimaryFocus', value: hasPrimaryFocus, ifTrue: 'PRIMARY FOCUS', defaultValue: false));
|
||||
properties.add(FlagProperty('hasFocus', value: hasFocus, ifTrue: 'FOCUSED', defaultValue: false));
|
||||
properties.add(StringProperty('debugLabel', debugLabel, defaultValue: null));
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -67,12 +67,11 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
||||
expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
|
||||
hasCheckedState: true,
|
||||
hasEnabledState: true,
|
||||
isEnabled: true,
|
||||
hasTapAction: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(Material(
|
||||
@@ -82,13 +81,12 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
||||
expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
|
||||
hasCheckedState: true,
|
||||
hasEnabledState: true,
|
||||
isChecked: true,
|
||||
isEnabled: true,
|
||||
hasTapAction: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(const Material(
|
||||
@@ -98,10 +96,9 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
||||
expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
|
||||
hasCheckedState: true,
|
||||
hasEnabledState: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
|
||||
await tester.pumpWidget(const Material(
|
||||
@@ -111,7 +108,7 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
||||
expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
|
||||
hasCheckedState: true,
|
||||
hasEnabledState: true,
|
||||
isChecked: true,
|
||||
@@ -133,14 +130,13 @@ void main() {
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSemantics(find.byType(Focus)), matchesSemantics(
|
||||
expect(tester.getSemantics(find.byType(Checkbox)), matchesSemantics(
|
||||
label: 'foo',
|
||||
textDirection: TextDirection.ltr,
|
||||
hasCheckedState: true,
|
||||
hasEnabledState: true,
|
||||
isEnabled: true,
|
||||
hasTapAction: true,
|
||||
isFocusable: true,
|
||||
));
|
||||
handle.dispose();
|
||||
});
|
||||
@@ -206,7 +202,6 @@ void main() {
|
||||
SemanticsFlag.hasCheckedState,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
), hasLength(1));
|
||||
@@ -227,7 +222,6 @@ void main() {
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isChecked,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
), hasLength(1));
|
||||
@@ -247,7 +241,6 @@ void main() {
|
||||
SemanticsFlag.hasCheckedState,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
), hasLength(1));
|
||||
@@ -281,7 +274,7 @@ void main() {
|
||||
);
|
||||
|
||||
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(semanticEvent, <String, dynamic>{
|
||||
@@ -311,9 +304,7 @@ void main() {
|
||||
}
|
||||
|
||||
RenderToggleable getCheckboxRenderer() {
|
||||
return tester.renderObject<RenderToggleable>(find.byWidgetPredicate((Widget widget) {
|
||||
return widget.runtimeType.toString() == '_CheckboxRenderObjectWidget';
|
||||
}));
|
||||
return tester.renderObject<RenderToggleable>(find.byType(Checkbox));
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(false));
|
||||
@@ -365,9 +356,7 @@ void main() {
|
||||
}
|
||||
|
||||
RenderToggleable getCheckboxRenderer() {
|
||||
return tester.renderObject<RenderToggleable>(find.byWidgetPredicate((Widget widget) {
|
||||
return widget.runtimeType.toString() == '_CheckboxRenderObjectWidget';
|
||||
}));
|
||||
return tester.renderObject<RenderToggleable>(find.byType(Checkbox));
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(checkColor: const Color(0xFFFFFFFF)));
|
||||
@@ -387,181 +376,4 @@ void main() {
|
||||
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';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
@@ -132,7 +131,6 @@ void main() {
|
||||
SemanticsFlag.hasCheckedState,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
@@ -159,7 +157,6 @@ void main() {
|
||||
SemanticsFlag.isChecked,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap,
|
||||
@@ -184,7 +181,6 @@ void main() {
|
||||
SemanticsFlag.isInMutuallyExclusiveGroup,
|
||||
SemanticsFlag.hasCheckedState,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isFocusable,
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -236,7 +232,7 @@ void main() {
|
||||
));
|
||||
|
||||
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(semanticEvent, <String, dynamic>{
|
||||
@@ -286,227 +282,5 @@ void main() {
|
||||
),
|
||||
);
|
||||
}, 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 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -48,7 +47,6 @@ void main() {
|
||||
Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.enter): const Intent(ActivateAction.key),
|
||||
LogicalKeySet(LogicalKeyboardKey.space): const Intent(SelectAction.key),
|
||||
},
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@@ -76,12 +74,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
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 {
|
||||
|
||||
@@ -518,7 +518,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
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(semanticEvent, <String, dynamic>{
|
||||
@@ -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() {
|
||||
FocusNode(
|
||||
debugLabel: 'Label',
|
||||
).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>[
|
||||
'context: null',
|
||||
'canRequestFocus: true',
|
||||
'hasFocus: false',
|
||||
'hasPrimaryFocus: false'
|
||||
'debugLabel: "Label"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -624,12 +621,9 @@ void main() {
|
||||
FocusScopeNode(
|
||||
debugLabel: 'Scope Label',
|
||||
).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>[
|
||||
'context: null',
|
||||
'canRequestFocus: true',
|
||||
'hasFocus: false',
|
||||
'hasPrimaryFocus: false'
|
||||
'debugLabel: "Scope Label"',
|
||||
]);
|
||||
});
|
||||
testWidgets('debugDescribeFocusTree produces correct output', (WidgetTester tester) async {
|
||||
@@ -669,36 +663,43 @@ void main() {
|
||||
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n'
|
||||
' │\n'
|
||||
' └─rootScope: FocusScopeNode#00000(Root Focus Scope)\n'
|
||||
' │ IN FOCUS PATH\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Root Focus Scope"\n'
|
||||
' │ focusedChildren: FocusScopeNode#00000\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusScopeNode#00000(Scope 1)\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Scope 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 1: FocusNode#00000(Parent 1)\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Parent 1"\n'
|
||||
' │ │\n'
|
||||
' │ ├─Child 1: FocusNode#00000(Child 1)\n'
|
||||
' │ │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ │ debugLabel: "Child 1"\n'
|
||||
' │ │\n'
|
||||
' │ └─Child 2: FocusNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusScopeNode#00000\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ IN FOCUS PATH\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ focusedChildren: FocusNode#00000(Child 4)\n'
|
||||
' │\n'
|
||||
' └─Child 1: FocusNode#00000(Parent 2)\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ IN FOCUS PATH\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Parent 2"\n'
|
||||
' │\n'
|
||||
' ├─Child 1: FocusNode#00000(Child 3)\n'
|
||||
' │ context: Container-[GlobalKey#00000]\n'
|
||||
' │ debugLabel: "Child 3"\n'
|
||||
' │\n'
|
||||
' └─Child 2: FocusNode#00000(Child 4)\n'
|
||||
' context: Container-[GlobalKey#00000]\n'
|
||||
' PRIMARY FOCUS\n'
|
||||
' FOCUSED\n'
|
||||
' debugLabel: "Child 4"\n'
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -229,29 +229,34 @@ void main() {
|
||||
parentFocusScope.toStringDeep(),
|
||||
equalsIgnoringHashCodes('FocusScopeNode#00000(Parent Scope Node)\n'
|
||||
' │ context: FocusScope\n'
|
||||
' │ IN FOCUS PATH\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Parent Scope Node"\n'
|
||||
' │ focusedChildren: FocusNode#00000(Child)\n'
|
||||
' │\n'
|
||||
' └─Child 1: FocusNode#00000(Child)\n'
|
||||
' context: Focus\n'
|
||||
' PRIMARY FOCUS\n'),
|
||||
' FOCUSED\n'
|
||||
' debugLabel: "Child"\n'),
|
||||
);
|
||||
|
||||
expect(WidgetsBinding.instance.focusManager.rootScope, hasAGoodToStringDeep);
|
||||
expect(
|
||||
WidgetsBinding.instance.focusManager.rootScope.toStringDeep(minLevel: DiagnosticLevel.info),
|
||||
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'
|
||||
' │\n'
|
||||
' └─Child 1: FocusScopeNode#00000(Parent Scope Node)\n'
|
||||
' │ context: FocusScope\n'
|
||||
' │ IN FOCUS PATH\n'
|
||||
' │ FOCUSED\n'
|
||||
' │ debugLabel: "Parent Scope Node"\n'
|
||||
' │ focusedChildren: FocusNode#00000(Child)\n'
|
||||
' │\n'
|
||||
' └─Child 1: FocusNode#00000(Child)\n'
|
||||
' context: Focus\n'
|
||||
' PRIMARY FOCUS\n'),
|
||||
' FOCUSED\n'
|
||||
' debugLabel: "Child"\n'),
|
||||
);
|
||||
|
||||
// Add the child focus scope to the focus tree.
|
||||
|
||||
Reference in New Issue
Block a user