Unverified Commit 2e43b470 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[Material] ToggleButtons (#34599)

* Introduces ToggleButtons widget

* Introduces ToggleButtonsTheme inherited widget

* Introduces ThemeData.toggleButtonsTheme property
parent f1e87c48
...@@ -114,6 +114,8 @@ export 'src/material/theme.dart'; ...@@ -114,6 +114,8 @@ export 'src/material/theme.dart';
export 'src/material/theme_data.dart'; export 'src/material/theme_data.dart';
export 'src/material/time.dart'; export 'src/material/time.dart';
export 'src/material/time_picker.dart'; export 'src/material/time_picker.dart';
export 'src/material/toggle_buttons.dart';
export 'src/material/toggle_buttons_theme.dart';
export 'src/material/toggleable.dart'; export 'src/material/toggleable.dart';
export 'src/material/tooltip.dart'; export 'src/material/tooltip.dart';
export 'src/material/tooltip_theme.dart'; export 'src/material/tooltip_theme.dart';
......
...@@ -27,6 +27,7 @@ import 'slider_theme.dart'; ...@@ -27,6 +27,7 @@ import 'slider_theme.dart';
import 'snack_bar_theme.dart'; import 'snack_bar_theme.dart';
import 'tab_bar_theme.dart'; import 'tab_bar_theme.dart';
import 'text_theme.dart'; import 'text_theme.dart';
import 'toggle_buttons_theme.dart';
import 'tooltip_theme.dart'; import 'tooltip_theme.dart';
import 'typography.dart'; import 'typography.dart';
...@@ -136,6 +137,7 @@ class ThemeData extends Diagnosticable { ...@@ -136,6 +137,7 @@ class ThemeData extends Diagnosticable {
Color disabledColor, Color disabledColor,
Color buttonColor, Color buttonColor,
ButtonThemeData buttonTheme, ButtonThemeData buttonTheme,
ToggleButtonsThemeData toggleButtonsTheme,
Color secondaryHeaderColor, Color secondaryHeaderColor,
Color textSelectionColor, Color textSelectionColor,
Color cursorColor, Color cursorColor,
...@@ -253,6 +255,7 @@ class ThemeData extends Diagnosticable { ...@@ -253,6 +255,7 @@ class ThemeData extends Diagnosticable {
splashColor: splashColor, splashColor: splashColor,
materialTapTargetSize: materialTapTargetSize, materialTapTargetSize: materialTapTargetSize,
); );
toggleButtonsTheme ??= const ToggleButtonsThemeData();
disabledColor ??= isDark ? Colors.white38 : Colors.black38; disabledColor ??= isDark ? Colors.white38 : Colors.black38;
highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor; highlightColor ??= isDark ? _kDarkThemeHighlightColor : _kLightThemeHighlightColor;
splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor; splashColor ??= isDark ? _kDarkThemeSplashColor : _kLightThemeSplashColor;
...@@ -297,6 +300,7 @@ class ThemeData extends Diagnosticable { ...@@ -297,6 +300,7 @@ class ThemeData extends Diagnosticable {
disabledColor: disabledColor, disabledColor: disabledColor,
buttonTheme: buttonTheme, buttonTheme: buttonTheme,
buttonColor: buttonColor, buttonColor: buttonColor,
toggleButtonsTheme: toggleButtonsTheme,
toggleableActiveColor: toggleableActiveColor, toggleableActiveColor: toggleableActiveColor,
secondaryHeaderColor: secondaryHeaderColor, secondaryHeaderColor: secondaryHeaderColor,
textSelectionColor: textSelectionColor, textSelectionColor: textSelectionColor,
...@@ -368,6 +372,7 @@ class ThemeData extends Diagnosticable { ...@@ -368,6 +372,7 @@ class ThemeData extends Diagnosticable {
@required this.disabledColor, @required this.disabledColor,
@required this.buttonTheme, @required this.buttonTheme,
@required this.buttonColor, @required this.buttonColor,
@required this.toggleButtonsTheme,
@required this.secondaryHeaderColor, @required this.secondaryHeaderColor,
@required this.textSelectionColor, @required this.textSelectionColor,
@required this.cursorColor, @required this.cursorColor,
...@@ -425,6 +430,7 @@ class ThemeData extends Diagnosticable { ...@@ -425,6 +430,7 @@ class ThemeData extends Diagnosticable {
assert(disabledColor != null), assert(disabledColor != null),
assert(toggleableActiveColor != null), assert(toggleableActiveColor != null),
assert(buttonTheme != null), assert(buttonTheme != null),
assert(toggleButtonsTheme != null),
assert(secondaryHeaderColor != null), assert(secondaryHeaderColor != null),
assert(textSelectionColor != null), assert(textSelectionColor != null),
assert(cursorColor != null), assert(cursorColor != null),
...@@ -593,6 +599,9 @@ class ThemeData extends Diagnosticable { ...@@ -593,6 +599,9 @@ class ThemeData extends Diagnosticable {
/// and [FlatButton]. /// and [FlatButton].
final ButtonThemeData buttonTheme; final ButtonThemeData buttonTheme;
/// Defines the default configuration of [ToggleButtons] widgets.
final ToggleButtonsThemeData toggleButtonsTheme;
/// The default fill color of the [Material] used in [RaisedButton]s. /// The default fill color of the [Material] used in [RaisedButton]s.
final Color buttonColor; final Color buttonColor;
...@@ -802,6 +811,7 @@ class ThemeData extends Diagnosticable { ...@@ -802,6 +811,7 @@ class ThemeData extends Diagnosticable {
Color unselectedWidgetColor, Color unselectedWidgetColor,
Color disabledColor, Color disabledColor,
ButtonThemeData buttonTheme, ButtonThemeData buttonTheme,
ToggleButtonsTheme toggleButtonsTheme,
Color buttonColor, Color buttonColor,
Color secondaryHeaderColor, Color secondaryHeaderColor,
Color textSelectionColor, Color textSelectionColor,
...@@ -863,6 +873,7 @@ class ThemeData extends Diagnosticable { ...@@ -863,6 +873,7 @@ class ThemeData extends Diagnosticable {
disabledColor: disabledColor ?? this.disabledColor, disabledColor: disabledColor ?? this.disabledColor,
buttonColor: buttonColor ?? this.buttonColor, buttonColor: buttonColor ?? this.buttonColor,
buttonTheme: buttonTheme ?? this.buttonTheme, buttonTheme: buttonTheme ?? this.buttonTheme,
toggleButtonsTheme: toggleButtonsTheme ?? this.toggleButtonsTheme,
secondaryHeaderColor: secondaryHeaderColor ?? this.secondaryHeaderColor, secondaryHeaderColor: secondaryHeaderColor ?? this.secondaryHeaderColor,
textSelectionColor: textSelectionColor ?? this.textSelectionColor, textSelectionColor: textSelectionColor ?? this.textSelectionColor,
cursorColor: cursorColor ?? this.cursorColor, cursorColor: cursorColor ?? this.cursorColor,
...@@ -1000,6 +1011,7 @@ class ThemeData extends Diagnosticable { ...@@ -1000,6 +1011,7 @@ class ThemeData extends Diagnosticable {
unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t), unselectedWidgetColor: Color.lerp(a.unselectedWidgetColor, b.unselectedWidgetColor, t),
disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t), disabledColor: Color.lerp(a.disabledColor, b.disabledColor, t),
buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme, buttonTheme: t < 0.5 ? a.buttonTheme : b.buttonTheme,
toggleButtonsTheme: ToggleButtonsThemeData.lerp(a.toggleButtonsTheme, b.toggleButtonsTheme, t),
buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t), buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t), secondaryHeaderColor: Color.lerp(a.secondaryHeaderColor, b.secondaryHeaderColor, t),
textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t), textSelectionColor: Color.lerp(a.textSelectionColor, b.textSelectionColor, t),
...@@ -1067,6 +1079,7 @@ class ThemeData extends Diagnosticable { ...@@ -1067,6 +1079,7 @@ class ThemeData extends Diagnosticable {
(otherData.disabledColor == disabledColor) && (otherData.disabledColor == disabledColor) &&
(otherData.buttonTheme == buttonTheme) && (otherData.buttonTheme == buttonTheme) &&
(otherData.buttonColor == buttonColor) && (otherData.buttonColor == buttonColor) &&
(otherData.toggleButtonsTheme == toggleButtonsTheme) &&
(otherData.secondaryHeaderColor == secondaryHeaderColor) && (otherData.secondaryHeaderColor == secondaryHeaderColor) &&
(otherData.textSelectionColor == textSelectionColor) && (otherData.textSelectionColor == textSelectionColor) &&
(otherData.cursorColor == cursorColor) && (otherData.cursorColor == cursorColor) &&
...@@ -1132,6 +1145,7 @@ class ThemeData extends Diagnosticable { ...@@ -1132,6 +1145,7 @@ class ThemeData extends Diagnosticable {
disabledColor, disabledColor,
buttonTheme, buttonTheme,
buttonColor, buttonColor,
toggleButtonsTheme,
toggleableActiveColor, toggleableActiveColor,
secondaryHeaderColor, secondaryHeaderColor,
textSelectionColor, textSelectionColor,
...@@ -1205,6 +1219,7 @@ class ThemeData extends Diagnosticable { ...@@ -1205,6 +1219,7 @@ class ThemeData extends Diagnosticable {
properties.add(ColorProperty('errorColor', errorColor, defaultValue: defaultData.errorColor)); properties.add(ColorProperty('errorColor', errorColor, defaultValue: defaultData.errorColor));
properties.add(ColorProperty('toggleableActiveColor', toggleableActiveColor, defaultValue: defaultData.toggleableActiveColor)); properties.add(ColorProperty('toggleableActiveColor', toggleableActiveColor, defaultValue: defaultData.toggleableActiveColor));
properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme)); properties.add(DiagnosticsProperty<ButtonThemeData>('buttonTheme', buttonTheme));
properties.add(DiagnosticsProperty<ToggleButtonsThemeData>('toggleButtonsTheme', toggleButtonsTheme));
properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme)); properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme));
properties.add(DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme)); properties.add(DiagnosticsProperty<TextTheme>('primaryTextTheme', primaryTextTheme));
properties.add(DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme)); properties.add(DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme));
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button.dart';
import 'debug.dart';
import 'theme.dart';
import 'theme_data.dart';
import 'toggle_buttons_theme.dart';
/// A horizontal set of toggle buttons.
///
/// The list of [children] are laid out in a row. The state of each button
/// is controlled by [isSelected], which is a list of bools that determine
/// if a button is in an unselected or selected state. They are both
/// correlated by their index in the list. The length of [isSelected] has to
/// match the length of the [children] list.
///
/// ## Customizing toggle buttons
/// Each toggle's behavior can be configured by the [onPressed] callback, which
/// can update the [isSelected] list however it wants to.
///
/// Here is an implementation that allows for multiple buttons to be
/// simultaneously selected, while requiring none of the buttons to be
/// selected.
/// ```dart
/// ToggleButtons(
/// children: <Widget>[
/// Icon(Icons.ac_unit),
/// Icon(Icons.call),
/// Icon(Icons.cake),
/// ],
/// onPressed: (int index) {
/// setState(() {
/// isSelected[index] = !isSelected[index];
/// });
/// },
/// isSelected: isSelected,
/// ),
/// ```
///
/// Here is an implementation that requires mutually exclusive selection
/// while requiring at least one selection. Note that this assumes that
/// [isSelected] was properly initialized with one selection.
/// ```dart
/// ToggleButtons(
/// children: <Widget>[
/// Icon(Icons.ac_unit),
/// Icon(Icons.call),
/// Icon(Icons.cake),
/// ],
/// onPressed: (int index) {
/// setState(() {
/// for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
/// if (buttonIndex == index) {
/// isSelected[buttonIndex] = true;
/// } else {
/// isSelected[buttonIndex] = false;
/// }
/// }
/// });
/// },
/// isSelected: isSelected,
/// ),
/// ```
///
/// Here is an implementation that requires mutually exclusive selection,
/// but allows for none of the buttons to be selected.
/// ```dart
/// ToggleButtons(
/// children: <Widget>[
/// Icon(Icons.ac_unit),
/// Icon(Icons.call),
/// Icon(Icons.cake),
/// ],
/// onPressed: (int index) {
/// setState(() {
/// for (int buttonIndex = 0; buttonIndex < isSelected.length; buttonIndex++) {
/// if (buttonIndex == index) {
/// isSelected[buttonIndex] = !isSelected[buttonIndex];
/// } else {
/// isSelected[buttonIndex] = false;
/// }
/// }
/// });
/// },
/// isSelected: isSelected,
/// ),
/// ```
///
/// Here is an implementation that allows for multiple buttons to be
/// simultaneously selected, while requiring at least one selection. Note
/// that this assumes that [isSelected] was properly initialized with one
/// selection.
/// ```dart
/// ToggleButtons(
/// children: <Widget>[
/// Icon(Icons.ac_unit),
/// Icon(Icons.call),
/// Icon(Icons.cake),
/// ],
/// onPressed: (int index) {
/// int count = 0;
/// isSelected.forEach((bool val) {
/// if (val) count++;
/// });
///
/// if (isSelected[index] && count < 2)
/// return;
///
/// setState(() {
/// isSelected[index] = !isSelected[index];
/// });
/// },
/// isSelected: isSelected,
/// ),
/// ```
///
/// ## ToggleButton Borders
/// The toggle buttons, by default, have a solid, 1 logical pixel border
/// surrounding itself and separating each button. The toggle button borders'
/// color, width, and corner radii are configurable.
///
/// The [selectedBorderColor] determines the border's color when the button is
/// selected, while [disabledBorderColor] determines the border's color when
/// the button is disabled. [borderColor] is used when the button is enabled.
///
/// To remove the border, set [borderWidth] to null. Setting [borderWidth] to
/// 0.0 results in a hairline border. For more information on hairline borders,
/// see [BorderSide.width].
///
/// See also:
///
/// * <https://material.io/design/components/buttons.html#toggle-button>
class ToggleButtons extends StatelessWidget {
/// Creates a horizontal set of toggle buttons.
///
/// It displays its widgets provided in a [List] of [children] horizontally.
/// The state of each button is controlled by [isSelected], which is a list
/// of bools that determine if a button is in an active, disabled, or
/// selected state. They are both correlated by their index in the list.
/// The length of [isSelected] has to match the length of the [children]
/// list.
///
/// Both [children] and [isSelected] properties arguments are required.
///
/// [isSelected] values must be non-null. [focusNodes] must be null or a
/// list of non-null nodes. [renderBorder] must not be null.
const ToggleButtons({
Key key,
@required this.children,
@required this.isSelected,
this.onPressed,
this.color,
this.selectedColor,
this.disabledColor,
this.fillColor,
this.focusColor,
this.highlightColor,
this.hoverColor,
this.splashColor,
this.focusNodes,
this.renderBorder = true,
this.borderColor,
this.selectedBorderColor,
this.disabledBorderColor,
this.borderRadius,
this.borderWidth,
}) :
assert(children != null),
assert(isSelected != null),
assert(children.length == isSelected.length),
super(key: key);
static const double _defaultBorderWidth = 1.0;
/// The toggle button widgets.
///
/// These are typically [Icon] or [Text] widgets. The boolean selection
/// state of each widget is defined by the corresponding [isSelected]
/// list item.
///
/// The length of children has to match the length of [isSelected]. If
/// [focusNodes] is not null, the length of children has to also match
/// the length of [focusNodes].
final List<Widget> children;
/// The corresponding selection state of each toggle button.
///
/// Each value in this list represents the selection state of the [children]
/// widget at the same index.
///
/// The length of [isSelected] has to match the length of [children].
final List<bool> isSelected;
/// The callback that is called when a button is tapped.
///
/// The index parameter of the callback is the index of the button that is
/// tapped or otherwise activated.
///
/// When the callback is null, all toggle buttons will be disabled.
final void Function(int index) onPressed;
/// The color for descendant [Text] and [Icon] widgets if the button is
/// enabled and not selected.
///
/// If [onPressed] is not null, this color will be used for values in
/// [isSelected] that are false.
///
/// If this property is null, then ToggleButtonTheme.of(context).color
/// is used. If [ToggleButtonsThemeData.color] is also null, then
/// Theme.of(context).colorScheme.onSurface is used.
final Color color;
/// The color for descendant [Text] and [Icon] widgets if the button is
/// selected.
///
/// If [onPressed] is not null, this color will be used for values in
/// [isSelected] that are true.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).selectedColor is used. If
/// [ToggleButtonsThemeData.selectedColor] is also null, then
/// Theme.of(context).colorScheme.primary is used.
final Color selectedColor;
/// The color for descendant [Text] and [Icon] widgets if the button is
/// disabled.
///
/// If [onPressed] is null, this color will be used.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).disabledColor is used. If
/// [ToggleButtonsThemeData.disabledColor] is also null, then
/// Theme.of(context).colorScheme.onSurface.withOpacity(0.38) is used.
final Color disabledColor;
/// The fill color for selected toggle buttons.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).fillColor is used. If
/// [ToggleButtonsThemeData.fillColor] is also null, then
/// the fill color is null.
final Color fillColor;
/// The color to use for filling the button when the button has input focus.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).focusColor is used. If
/// [ToggleButtonsThemeData.focusColor] is also null, then
/// Theme.of(context).focusColor is used.
final Color focusColor;
/// The highlight color for the button's [InkWell].
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).highlightColor is used. If
/// [ToggleButtonsThemeData.highlightColor] is also null, then
/// Theme.of(context).highlightColor is used.
final Color highlightColor;
/// The splash color for the button's [InkWell].
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).splashColor is used. If
/// [ToggleButtonsThemeData.splashColor] is also null, then
/// Theme.of(context).splashColor is used.
final Color splashColor;
/// The color to use for filling the button when the button has a pointer
/// hovering over it.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).hoverColor is used. If
/// [ToggleButtonsThemeData.hoverColor] is also null, then
/// Theme.of(context).hoverColor is used.
final Color hoverColor;
/// The list of [FocusNode]s, corresponding to each toggle button.
///
/// Focus is used to determine which widget should be affected by keyboard
/// events. The focus tree keeps track of which widget is currently focused
/// on by the user.
///
/// If not null, the length of focusNodes has to match the length of
/// [children].
///
/// See [FocusNode] for more information about how focus nodes are used.
final List<FocusNode> focusNodes;
/// Whether or not to render a border around each toggle button.
///
/// When true, a border with [borderWidth], [borderRadius] and the
/// appropriate border color will render. Otherwise, no border will be
/// rendered.
final bool renderBorder;
/// The border color to display when the toggle button is enabled and not
/// selected.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).borderColor is used. If
/// [ToggleButtonsThemeData.borderColor] is also null, then
/// Theme.of(context).colorScheme.onSurface is used.
final Color borderColor;
/// The border color to display when the toggle button is selected.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).selectedBorderColor is used. If
/// [ToggleButtonsThemeData.selectedBorderColor] is also null, then
/// Theme.of(context).colorScheme.primary is used.
final Color selectedBorderColor;
/// The border color to display when the toggle button is disabled.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).disabledBorderColor is used. If
/// [ToggleButtonsThemeData.disabledBorderColor] is also null, then
/// Theme.of(context).disabledBorderColor is used.
final Color disabledBorderColor;
/// The width of the border surrounding each toggle button.
///
/// This applies to both the greater surrounding border, as well as the
/// borders rendered between toggle buttons.
///
/// To render a hairline border (one physical pixel), set borderWidth to 0.0.
/// See [BorderSide.width] for more details on hairline borders.
///
/// To omit the border entirely, set [renderBorder] to false.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).borderWidth is used. If
/// [ToggleButtonsThemeData.borderWidth] is also null, then
/// a width of 1.0 is used.
final double borderWidth;
/// The radii of the border's corners.
///
/// If this property is null, then
/// ToggleButtonTheme.of(context).borderRadius is used. If
/// [ToggleButtonsThemeData.borderRadius] is also null, then
/// the buttons default to non-rounded borders.
final BorderRadius borderRadius;
bool _isFirstIndex(int index, int length, TextDirection textDirection) {
return index == 0 && textDirection == TextDirection.ltr
|| index == length - 1 && textDirection == TextDirection.rtl;
}
bool _isLastIndex(int index, int length, TextDirection textDirection) {
return index == length - 1 && textDirection == TextDirection.ltr
|| index == 0 && textDirection == TextDirection.rtl;
}
BorderRadius _getEdgeBorderRadius(
int index,
int length,
TextDirection textDirection,
ToggleButtonsThemeData toggleButtonsTheme,
) {
final BorderRadius resultingBorderRadius = borderRadius
?? toggleButtonsTheme.borderRadius
?? BorderRadius.zero;
if (_isFirstIndex(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft,
bottomLeft: resultingBorderRadius.bottomLeft,
);
} else if (_isLastIndex(index, length, textDirection)) {
return BorderRadius.only(
topRight: resultingBorderRadius.topRight,
bottomRight: resultingBorderRadius.bottomRight,
);
}
return BorderRadius.zero;
}
BorderRadius _getClipBorderRadius(
int index,
int length,
TextDirection textDirection,
ToggleButtonsThemeData toggleButtonsTheme,
) {
final BorderRadius resultingBorderRadius = borderRadius
?? toggleButtonsTheme.borderRadius
?? BorderRadius.zero;
final double resultingBorderWidth = borderWidth
?? toggleButtonsTheme.borderWidth
?? _defaultBorderWidth;
if (_isFirstIndex(index, length, textDirection)) {
return BorderRadius.only(
topLeft: resultingBorderRadius.topLeft - Radius.circular(resultingBorderWidth / 2.0),
bottomLeft: resultingBorderRadius.bottomLeft - Radius.circular(resultingBorderWidth / 2.0),
);
} else if (_isLastIndex(index, length, textDirection)) {
return BorderRadius.only(
topRight: resultingBorderRadius.topRight - Radius.circular(resultingBorderWidth / 2.0),
bottomRight: resultingBorderRadius.bottomRight - Radius.circular(resultingBorderWidth / 2.0),
);
}
return BorderRadius.zero;
}
BorderSide _getLeadingBorderSide(
int index,
ThemeData theme,
ToggleButtonsThemeData toggleButtonsTheme,
) {
if (!renderBorder)
return BorderSide.none;
final double resultingBorderWidth = borderWidth
?? toggleButtonsTheme.borderWidth
?? _defaultBorderWidth;
if (onPressed != null && (isSelected[index] || (index != 0 && isSelected[index - 1]))) {
return BorderSide(
color: selectedBorderColor
?? toggleButtonsTheme.selectedBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else if (onPressed != null && !isSelected[index]) {
return BorderSide(
color: borderColor
?? toggleButtonsTheme.borderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else {
return BorderSide(
color: disabledBorderColor
?? toggleButtonsTheme.disabledBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
}
}
BorderSide _getHorizontalBorderSide(
int index,
ThemeData theme,
ToggleButtonsThemeData toggleButtonsTheme,
) {
if (!renderBorder)
return BorderSide.none;
final double resultingBorderWidth = borderWidth
?? toggleButtonsTheme.borderWidth
?? _defaultBorderWidth;
if (onPressed != null && isSelected[index]) {
return BorderSide(
color: selectedBorderColor
?? toggleButtonsTheme.selectedBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else if (onPressed != null && !isSelected[index]) {
return BorderSide(
color: borderColor
?? toggleButtonsTheme.borderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else {
return BorderSide(
color: disabledBorderColor
?? toggleButtonsTheme.disabledBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
}
}
BorderSide _getTrailingBorderSide(
int index,
ThemeData theme,
ToggleButtonsThemeData toggleButtonsTheme,
) {
if (!renderBorder)
return BorderSide.none;
if (index != children.length - 1)
return null;
final double resultingBorderWidth = borderWidth
?? toggleButtonsTheme.borderWidth
?? _defaultBorderWidth;
if (onPressed != null && (isSelected[index])) {
return BorderSide(
color: selectedBorderColor
?? toggleButtonsTheme.selectedBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else if (onPressed != null && !isSelected[index]) {
return BorderSide(
color: borderColor
?? toggleButtonsTheme.borderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
} else {
return BorderSide(
color: disabledBorderColor
?? toggleButtonsTheme.disabledBorderColor
?? theme.colorScheme.onSurface.withOpacity(0.12),
width: resultingBorderWidth,
);
}
}
@override
Widget build(BuildContext context) {
assert(
!isSelected.any((bool val) => val == null),
'All elements of isSelected must be non-null.\n'
'The current list of isSelected values is as follows:\n'
'$isSelected'
);
assert(
focusNodes == null || !focusNodes.any((FocusNode val) => val == null),
'All elements of focusNodes must be non-null.\n'
'The current list of focus node values is as follows:\n'
'$focusNodes'
);
assert(
() {
if (focusNodes != null)
return focusNodes.length == children.length;
return true;
}(),
'focusNodes.length must match children.length.\n'
'There are ${focusNodes.length} focus nodes, while'
'there are ${children.length} children.'
);
final ThemeData theme = Theme.of(context);
final ToggleButtonsThemeData toggleButtonsTheme = ToggleButtonsTheme.of(context);
final TextDirection textDirection = Directionality.of(context);
return IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(children.length, (int index) {
final BorderRadius edgeBorderRadius = _getEdgeBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
final BorderRadius clipBorderRadius = _getClipBorderRadius(index, children.length, textDirection, toggleButtonsTheme);
final BorderSide leadingBorderSide = _getLeadingBorderSide(index, theme, toggleButtonsTheme);
final BorderSide horizontalBorderSide = _getHorizontalBorderSide(index, theme, toggleButtonsTheme);
final BorderSide trailingBorderSide = _getTrailingBorderSide(index, theme, toggleButtonsTheme);
return _ToggleButton(
selected: isSelected[index],
color: color,
selectedColor: selectedColor,
disabledColor: disabledColor,
fillColor: fillColor ?? toggleButtonsTheme.fillColor,
focusColor: focusColor ?? toggleButtonsTheme.focusColor,
highlightColor: highlightColor ?? toggleButtonsTheme.highlightColor,
hoverColor: hoverColor ?? toggleButtonsTheme.hoverColor,
splashColor: splashColor ?? toggleButtonsTheme.splashColor,
focusNode: focusNodes != null ? focusNodes[index] : null,
onPressed: onPressed != null
? () { onPressed(index); }
: null,
leadingBorderSide: leadingBorderSide,
horizontalBorderSide: horizontalBorderSide,
trailingBorderSide: trailingBorderSide,
borderRadius: edgeBorderRadius,
clipRadius: clipBorderRadius,
isFirstButton: index == 0,
isLastButton: index == children.length - 1,
child: children[index],
);
}),
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('disabled',
value: onPressed == null,
ifTrue: 'Buttons are disabled',
ifFalse: 'Buttons are enabled',
));
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
properties.add(ColorProperty('fillColor', fillColor, defaultValue: null));
properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
properties.add(ColorProperty('borderColor', borderColor, defaultValue: null));
properties.add(ColorProperty('selectedBorderColor', selectedBorderColor, defaultValue: null));
properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
properties.add(DoubleProperty('borderWidth', borderWidth, defaultValue: null));
}
}
/// An individual toggle button, otherwise known as a segmented button.
///
/// This button is used by [ToggleButtons] to implement a set of segmented controls.
class _ToggleButton extends StatelessWidget {
/// Creates a toggle button based on [RawMaterialButton].
///
/// This class adds some logic to distinguish between enabled, active, and
/// disabled states, to determine the appropriate colors to use.
///
/// It takes in a [shape] property to modify the borders of the button,
/// which is used by [ToggleButtons] to customize borders based on the
/// order in which this button appears in the list.
const _ToggleButton({
Key key,
this.selected = false,
this.color,
this.selectedColor,
this.disabledColor,
this.fillColor,
this.focusColor,
this.highlightColor,
this.hoverColor,
this.splashColor,
this.focusNode,
this.onPressed,
this.leadingBorderSide,
this.horizontalBorderSide,
this.trailingBorderSide,
this.borderRadius,
this.clipRadius,
this.isFirstButton,
this.isLastButton,
this.child,
}) : super(key: key);
/// Determines if the button is displayed as active/selected or enabled.
final bool selected;
/// The color for [Text] and [Icon] widgets if the button is enabled.
///
/// If [selected] is false and [onPressed] is not null, this color will be used.
final Color color;
/// The color for [Text] and [Icon] widgets if the button is selected.
///
/// If [selected] is true and [onPressed] is not null, this color will be used.
final Color selectedColor;
/// The color for [Text] and [Icon] widgets if the button is disabled.
///
/// If [onPressed] is null, this color will be used.
final Color disabledColor;
/// The color of the button's [Material].
final Color fillColor;
/// 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;
/// The highlight color for the button's [InkWell].
final Color highlightColor;
/// The splash color for the button's [InkWell].
final Color splashColor;
/// A leaf node in the focus tree for this button.
///
/// Focus is used to determine which widget should be affected by keyboard
/// events. The focus tree keeps track of which widget is currently focused
/// on by the user.
///
/// See [FocusNode] for more information about how focus nodes are used.
final FocusNode focusNode;
/// Called when the button is tapped or otherwise activated.
///
/// If this is null, the button will be disabled, see [enabled].
final VoidCallback onPressed;
/// The width and color of the button's leading side border.
final BorderSide leadingBorderSide;
/// The width and color of the button's top and bottom side borders.
final BorderSide horizontalBorderSide;
/// The width and color of the button's trailing side border.
final BorderSide trailingBorderSide;
/// The border radii of each corner of the button.
final BorderRadius borderRadius;
/// The corner radii used to clip the button's contents.
///
/// This is used to have the button's contents be properly clipped taking
/// the [borderRadius] and the border's width into account.
final BorderRadius clipRadius;
/// Whether or not this toggle button is the first button in the list.
final bool isFirstButton;
/// Whether or not this toggle button is the last button in the list.
final bool isLastButton;
/// The button's label, which is usually an [Icon] or a [Text] widget.
final Widget child;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
Color currentColor;
Color currentFillColor;
Color currentFocusColor;
Color currentHoverColor;
Color currentSplashColor;
final ThemeData theme = Theme.of(context);
final ToggleButtonsThemeData toggleButtonsTheme = ToggleButtonsTheme.of(context);
if (onPressed != null && selected) {
currentColor = selectedColor
?? toggleButtonsTheme.selectedColor
?? theme.colorScheme.primary;
currentFillColor = fillColor
?? theme.colorScheme.primary.withOpacity(0.12);
currentFocusColor = focusColor
?? toggleButtonsTheme.focusColor
?? theme.colorScheme.primary.withOpacity(0.12);
currentHoverColor = hoverColor
?? toggleButtonsTheme.hoverColor
?? theme.colorScheme.primary.withOpacity(0.04);
currentSplashColor = splashColor
?? toggleButtonsTheme.splashColor
?? theme.colorScheme.primary.withOpacity(0.16);
} else if (onPressed != null && !selected) {
currentColor = color
?? toggleButtonsTheme.color
?? theme.colorScheme.onSurface.withOpacity(0.87);
currentFillColor = theme.colorScheme.surface.withOpacity(0.0);
currentFocusColor = focusColor
?? toggleButtonsTheme.focusColor
?? theme.colorScheme.onSurface.withOpacity(0.12);
currentHoverColor = hoverColor
?? toggleButtonsTheme.hoverColor
?? theme.colorScheme.onSurface.withOpacity(0.04);
currentSplashColor = splashColor
?? toggleButtonsTheme.splashColor
?? theme.colorScheme.onSurface.withOpacity(0.16);
} else {
currentColor = disabledColor
?? toggleButtonsTheme.disabledColor
?? theme.colorScheme.onSurface.withOpacity(0.38);
currentFillColor = theme.colorScheme.surface.withOpacity(0.0);
}
final Widget result = ClipRRect(
borderRadius: clipRadius,
child: RawMaterialButton(
textStyle: TextStyle(
color: currentColor,
),
elevation: 0.0,
highlightElevation: 0.0,
fillColor: currentFillColor,
focusColor: currentFocusColor,
highlightColor: highlightColor
?? theme.colorScheme.surface.withOpacity(0.0),
hoverColor: currentHoverColor,
splashColor: currentSplashColor,
focusNode: focusNode,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: onPressed,
child: child,
),
);
return _SelectToggleButton(
key: key,
leadingBorderSide: leadingBorderSide,
horizontalBorderSide: horizontalBorderSide,
trailingBorderSide: trailingBorderSide,
borderRadius: borderRadius,
isFirstButton: isFirstButton,
isLastButton: isLastButton,
child: result,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(FlagProperty('selected',
value: selected,
ifTrue: 'Button is selected',
ifFalse: 'Button is unselected',
));
}
}
class _SelectToggleButton extends SingleChildRenderObjectWidget {
const _SelectToggleButton({
Key key,
Widget child,
this.leadingBorderSide,
this.horizontalBorderSide,
this.trailingBorderSide,
this.borderRadius,
this.isFirstButton,
this.isLastButton,
}) : super(
key: key,
child: child,
);
// The width and color of the button's leading side border.
final BorderSide leadingBorderSide;
// The width and color of the button's top and bottom side borders.
final BorderSide horizontalBorderSide;
// The width and color of the button's trailing side border.
final BorderSide trailingBorderSide;
// The border radii of each corner of the button.
final BorderRadius borderRadius;
// Whether or not this toggle button is the first button in the list.
final bool isFirstButton;
// Whether or not this toggle button is the last button in the list.
final bool isLastButton;
@override
_SelectToggleButtonRenderObject createRenderObject(BuildContext context) => _SelectToggleButtonRenderObject(
leadingBorderSide,
horizontalBorderSide,
trailingBorderSide,
borderRadius,
isFirstButton,
isLastButton,
Directionality.of(context),
);
@override
void updateRenderObject(BuildContext context, _SelectToggleButtonRenderObject renderObject) {
renderObject
..leadingBorderSide = leadingBorderSide
..horizontalBorderSide = horizontalBorderSide
..trailingBorderSide = trailingBorderSide
..borderRadius = borderRadius
..isFirstButton = isFirstButton
..isLastButton = isLastButton
..textDirection = Directionality.of(context);
}
}
class _SelectToggleButtonRenderObject extends RenderShiftedBox {
_SelectToggleButtonRenderObject(
this._leadingBorderSide,
this._horizontalBorderSide,
this._trailingBorderSide,
this._borderRadius,
this._isFirstButton,
this._isLastButton,
this._textDirection,
[RenderBox child]
) : super(child);
// The width and color of the button's leading side border.
BorderSide get leadingBorderSide => _leadingBorderSide;
BorderSide _leadingBorderSide;
set leadingBorderSide(BorderSide value) {
if (_leadingBorderSide == value)
return;
_leadingBorderSide = value;
markNeedsLayout();
}
// The width and color of the button's top and bottom side borders.
BorderSide get horizontalBorderSide => _horizontalBorderSide;
BorderSide _horizontalBorderSide;
set horizontalBorderSide(BorderSide value) {
if (_horizontalBorderSide == value)
return;
_horizontalBorderSide = value;
markNeedsLayout();
}
// The width and color of the button's trailing side border.
BorderSide get trailingBorderSide => _trailingBorderSide;
BorderSide _trailingBorderSide;
set trailingBorderSide(BorderSide value) {
if (_trailingBorderSide == value)
return;
_trailingBorderSide = value;
markNeedsLayout();
}
// The border radii of each corner of the button.
BorderRadius get borderRadius => _borderRadius;
BorderRadius _borderRadius;
set borderRadius(BorderRadius value) {
if (_borderRadius == value)
return;
_borderRadius = value;
markNeedsLayout();
}
// Whether or not this toggle button is the first button in the list.
bool get isFirstButton => _isFirstButton;
bool _isFirstButton;
set isFirstButton(bool value) {
if (_isFirstButton == value)
return;
_isFirstButton = value;
markNeedsLayout();
}
// Whether or not this toggle button is the last button in the list.
bool get isLastButton => _isLastButton;
bool _isLastButton;
set isLastButton(bool value) {
if (_isLastButton == value)
return;
_isLastButton = value;
markNeedsLayout();
}
// The direction in which text flows for this application.
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
markNeedsLayout();
}
static double _maxHeight(RenderBox box, double width) {
return box == null ? 0.0 : box.getMaxIntrinsicHeight(width);
}
static double _minWidth(RenderBox box, double height) {
return box == null ? 0.0 : box.getMinIntrinsicWidth(height);
}
static double _maxWidth(RenderBox box, double height) {
return box == null ? 0.0 : box.getMaxIntrinsicWidth(height);
}
@override
double computeDistanceToActualBaseline(TextBaseline baseline) {
// The baseline of this widget is the baseline of its child
return child.computeDistanceToActualBaseline(baseline) +
horizontalBorderSide.width;
}
@override
double computeMaxIntrinsicHeight(double width) {
return horizontalBorderSide.width +
_maxHeight(child, width) +
horizontalBorderSide.width;
}
@override
double computeMinIntrinsicHeight(double width) => computeMaxIntrinsicHeight(width);
@override
double computeMaxIntrinsicWidth(double height) {
final double trailingWidth = trailingBorderSide == null ? 0.0 : trailingBorderSide.width;
return leadingBorderSide.width +
_maxWidth(child, height) +
trailingWidth;
}
@override
double computeMinIntrinsicWidth(double height) {
final double trailingWidth = trailingBorderSide == null ? 0.0 : trailingBorderSide.width;
return leadingBorderSide.width +
_minWidth(child, height) +
trailingWidth;
}
@override
void performLayout() {
if (child == null) {
size = constraints.constrain(Size(
leadingBorderSide.width + trailingBorderSide.width,
horizontalBorderSide.width * 2.0,
));
return;
}
final double trailingBorderOffset = isLastButton ? trailingBorderSide.width : 0.0;
double leftConstraint;
double rightConstraint;
switch (textDirection) {
case TextDirection.ltr:
rightConstraint = trailingBorderOffset;
leftConstraint = leadingBorderSide.width;
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
left: leftConstraint,
top: horizontalBorderSide.width,
right: rightConstraint,
bottom: horizontalBorderSide.width,
),
);
child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData;
childParentData.offset = Offset(leadingBorderSide.width, leadingBorderSide.width);
size = constraints.constrain(Size(
leftConstraint + child.size.width + rightConstraint,
horizontalBorderSide.width * 2.0 + child.size.height,
));
break;
case TextDirection.rtl:
rightConstraint = leadingBorderSide.width;
leftConstraint = trailingBorderOffset;
final BoxConstraints innerConstraints = constraints.deflate(
EdgeInsets.only(
left: leftConstraint,
top: horizontalBorderSide.width,
right: rightConstraint,
bottom: horizontalBorderSide.width,
),
);
child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData;
if (isLastButton) {
childParentData.offset = Offset(trailingBorderOffset, trailingBorderOffset);
} else {
childParentData.offset = Offset(0, horizontalBorderSide.width);
}
size = constraints.constrain(Size(
leftConstraint + child.size.width + rightConstraint,
horizontalBorderSide.width * 2.0 + child.size.height,
));
break;
}
}
@override
void paint(PaintingContext context, Offset offset) {
super.paint(context, offset);
final Offset bottomRight = size.bottomRight(offset);
final Rect outer = Rect.fromLTRB(offset.dx, offset.dy, bottomRight.dx, bottomRight.dy);
final Rect center = outer.deflate(horizontalBorderSide.width / 2.0);
const double sweepAngle = math.pi / 2.0;
final RRect rrect = RRect.fromRectAndCorners(
center,
topLeft: borderRadius.topLeft,
topRight: borderRadius.topRight,
bottomLeft: borderRadius.bottomLeft,
bottomRight: borderRadius.bottomRight,
).scaleRadii();
final Rect tlCorner = Rect.fromLTWH(
rrect.left,
rrect.top,
rrect.tlRadiusX * 2.0,
rrect.tlRadiusY * 2.0,
);
final Rect blCorner = Rect.fromLTWH(
rrect.left,
rrect.bottom - (rrect.blRadiusY * 2.0),
rrect.blRadiusX * 2.0,
rrect.blRadiusY * 2.0,
);
final Rect trCorner = Rect.fromLTWH(
rrect.right - (rrect.trRadiusX * 2),
rrect.top,
rrect.trRadiusX * 2,
rrect.trRadiusY * 2,
);
final Rect brCorner = Rect.fromLTWH(
rrect.right - (rrect.brRadiusX * 2),
rrect.bottom - (rrect.brRadiusY * 2),
rrect.brRadiusX * 2,
rrect.brRadiusY * 2,
);
final Paint leadingPaint = leadingBorderSide.toPaint();
switch (textDirection) {
case TextDirection.ltr:
if (isLastButton) {
final Path leftPath = Path()
..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leftPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path()
..moveTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.top)
..lineTo(rrect.right - rrect.trRadiusX, rrect.top)
..addArc(trCorner, math.pi * 3.0 / 2.0, sweepAngle)
..lineTo(rrect.right, rrect.bottom - rrect.brRadiusY)
..addArc(brCorner, 0, sweepAngle)
..lineTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.bottom);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path()
..moveTo(outer.right, rrect.bottom)
..lineTo(rrect.left + rrect.blRadiusX, rrect.bottom)
..addArc(blCorner, math.pi / 2.0, sweepAngle)
..lineTo(rrect.left, rrect.top + rrect.tlRadiusY)
..addArc(tlCorner, math.pi, sweepAngle)
..lineTo(outer.right, rrect.top);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path leadingPath = Path()
..moveTo(rrect.left, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.left, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint horizontalPaint = horizontalBorderSide.toPaint();
final Path horizontalPaths = Path()
..moveTo(rrect.left + horizontalBorderSide.width / 2.0, rrect.top)
..lineTo(outer.right - rrect.trRadiusX, rrect.top)
..moveTo(rrect.left + horizontalBorderSide.width / 2.0 + rrect.tlRadiusX, rrect.bottom)
..lineTo(outer.right - rrect.trRadiusX, rrect.bottom);
context.canvas.drawPath(horizontalPaths, horizontalPaint);
}
break;
case TextDirection.rtl:
if (isLastButton) {
final Path leadingPath = Path()
..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint endingPaint = trailingBorderSide.toPaint();
final Path endingPath = Path()
..moveTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.top)
..lineTo(rrect.left + rrect.tlRadiusX, rrect.top)
..addArc(tlCorner, math.pi * 3.0 / 2.0, -sweepAngle)
..lineTo(rrect.left, rrect.bottom - rrect.blRadiusY)
..addArc(blCorner, math.pi, -sweepAngle)
..lineTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.bottom);
context.canvas.drawPath(endingPath, endingPaint);
} else if (isFirstButton) {
final Path leadingPath = Path()
..moveTo(outer.left, rrect.bottom)
..lineTo(rrect.right - rrect.brRadiusX, rrect.bottom)
..addArc(brCorner, math.pi / 2.0, -sweepAngle)
..lineTo(rrect.right, rrect.top + rrect.trRadiusY)
..addArc(trCorner, 0, -sweepAngle)
..lineTo(outer.left, rrect.top);
context.canvas.drawPath(leadingPath, leadingPaint);
} else {
final Path leadingPath = Path()
..moveTo(rrect.right, rrect.bottom + leadingBorderSide.width / 2)
..lineTo(rrect.right, rrect.top - leadingBorderSide.width / 2);
context.canvas.drawPath(leadingPath, leadingPaint);
final Paint horizontalPaint = horizontalBorderSide.toPaint();
final Path horizontalPaths = Path()
..moveTo(rrect.right - horizontalBorderSide.width / 2.0, rrect.top)
..lineTo(outer.left - rrect.tlRadiusX, rrect.top)
..moveTo(rrect.right - horizontalBorderSide.width / 2.0 + rrect.trRadiusX, rrect.bottom)
..lineTo(outer.left - rrect.tlRadiusX, rrect.bottom);
context.canvas.drawPath(horizontalPaths, horizontalPaint);
}
break;
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
/// Defines the color and border properties of [ToggleButtons] widgets.
///
/// Used by [ToggleButtonsTheme] to control the color and border properties
/// of toggle buttons in a widget subtree.
///
/// To obtain the current [ToggleButtonsTheme], use [ToggleButtonsTheme.of].
///
/// Values specified here are used for [ToggleButtons] properties that are not
/// given an explicit non-null value.
///
/// See also:
///
/// * [ToggleButtonsTheme], which describes the actual configuration of a
/// toggle buttons theme.
class ToggleButtonsThemeData extends Diagnosticable {
/// Creates the set of color and border properties used to configure
/// [ToggleButtons].
const ToggleButtonsThemeData({
this.color,
this.selectedColor,
this.disabledColor,
this.fillColor,
this.focusColor,
this.highlightColor,
this.hoverColor,
this.splashColor,
this.borderColor,
this.selectedBorderColor,
this.disabledBorderColor,
this.borderRadius,
this.borderWidth,
});
/// The color for descendant [Text] and [Icon] widgets if the toggle button
/// is enabled.
final Color color;
/// The color for descendant [Text] and [Icon] widgets if the toggle button
/// is selected.
final Color selectedColor;
/// The color for descendant [Text] and [Icon] widgets if the toggle button
/// is disabled.
final Color disabledColor;
/// The fill color for selected toggle buttons.
final Color fillColor;
/// The color to use for filling the button when the button has input focus.
final Color focusColor;
/// The highlight color for the toggle button's [InkWell].
final Color highlightColor;
/// The splash color for the toggle button's [InkWell].
final Color splashColor;
/// The color to use for filling the toggle button when the button has a
/// pointer hovering over it.
final Color hoverColor;
/// The border color to display when the toggle button is enabled.
final Color borderColor;
/// The border color to display when the toggle button is selected.
final Color selectedBorderColor;
/// The border color to display when the toggle button is disabled.
final Color disabledBorderColor;
/// The width of the border surrounding each toggle button.
///
/// This applies to both the greater surrounding border, as well as the
/// borders dividing each toggle button.
///
/// To render a hairline border (one physical pixel), set borderWidth to 0.0.
/// See [BorderSide.width] for more details on hairline borders.
final double borderWidth;
/// The radii of the border's corners.
final BorderRadius borderRadius;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
ToggleButtonsThemeData copyWith({
Color color,
Color selectedColor,
Color disabledColor,
Color fillColor,
Color focusColor,
Color highlightColor,
Color hoverColor,
Color splashColor,
Color borderColor,
Color selectedBorderColor,
Color disabledBorderColor,
BorderRadius borderRadius,
double borderWidth,
}) {
return ToggleButtonsThemeData(
color: color ?? this.color,
selectedColor: selectedColor ?? this.selectedColor,
disabledColor: disabledColor ?? this.disabledColor,
fillColor: fillColor ?? this.fillColor,
focusColor: focusColor ?? this.focusColor,
highlightColor: highlightColor ?? this.highlightColor,
hoverColor: hoverColor ?? this.hoverColor,
splashColor: splashColor ?? this.splashColor,
borderColor: borderColor ?? this.borderColor,
selectedBorderColor: selectedBorderColor ?? this.selectedBorderColor,
disabledBorderColor: disabledBorderColor ?? this.disabledBorderColor,
borderRadius: borderRadius ?? this.borderRadius,
borderWidth: borderWidth ?? this.borderWidth,
);
}
/// Linearly interpolate between two toggle buttons themes.
static ToggleButtonsThemeData lerp(ToggleButtonsThemeData a, ToggleButtonsThemeData b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return ToggleButtonsThemeData(
color: Color.lerp(a?.color, b?.color, t),
selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t),
fillColor: Color.lerp(a?.fillColor, b?.fillColor, t),
focusColor: Color.lerp(a?.focusColor, b?.focusColor, t),
highlightColor: Color.lerp(a?.highlightColor, b?.highlightColor, t),
hoverColor: Color.lerp(a?.hoverColor, b?.hoverColor, t),
splashColor: Color.lerp(a?.splashColor, b?.splashColor, t),
borderColor: Color.lerp(a?.borderColor, b?.borderColor, t),
selectedBorderColor: Color.lerp(a?.selectedBorderColor, b?.selectedBorderColor, t),
disabledBorderColor: Color.lerp(a?.disabledBorderColor, b?.disabledBorderColor, t),
borderRadius: BorderRadius.lerp(a?.borderRadius, b?.borderRadius, t),
borderWidth: lerpDouble(a?.borderWidth, b?.borderWidth, t),
);
}
@override
int get hashCode {
return hashValues(
color,
selectedColor,
disabledColor,
fillColor,
focusColor,
highlightColor,
hoverColor,
splashColor,
borderColor,
selectedBorderColor,
disabledBorderColor,
borderRadius,
borderWidth,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
final ToggleButtonsThemeData typedOther = other;
return typedOther.color == color
&& typedOther.selectedColor == selectedColor
&& typedOther.disabledColor == disabledColor
&& typedOther.fillColor == fillColor
&& typedOther.focusColor == focusColor
&& typedOther.highlightColor == highlightColor
&& typedOther.hoverColor == hoverColor
&& typedOther.splashColor == splashColor
&& typedOther.borderColor == borderColor
&& typedOther.selectedBorderColor == selectedBorderColor
&& typedOther.disabledBorderColor == disabledBorderColor
&& typedOther.borderRadius == borderRadius
&& typedOther.borderWidth == borderWidth;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ColorProperty('color', color, defaultValue: null));
properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
properties.add(ColorProperty('fillColor', fillColor, defaultValue: null));
properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
properties.add(ColorProperty('highlightColor', highlightColor, defaultValue: null));
properties.add(ColorProperty('hoverColor', hoverColor, defaultValue: null));
properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
properties.add(ColorProperty('borderColor', borderColor, defaultValue: null));
properties.add(ColorProperty('selectedBorderColor', selectedBorderColor, defaultValue: null));
properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(DiagnosticsProperty<BorderRadius>('borderRadius', borderRadius, defaultValue: null));
properties.add(DoubleProperty('borderWidth', borderWidth, defaultValue: null));
}
}
/// An inherited widget that defines color and border parameters for
/// [ToggleButtons] in this widget's subtree.
///
/// Values specified here are used for [ToggleButtons] properties that are not
/// given an explicit non-null value.
class ToggleButtonsTheme extends InheritedWidget {
/// Creates a toggle buttons theme that controls the color and border
/// parameters for [ToggleButtons].
ToggleButtonsTheme({
Key key,
Color color,
Color selectedColor,
Color disabledColor,
Color fillColor,
Color focusColor,
Color highlightColor,
Color hoverColor,
Color splashColor,
Color borderColor,
Color selectedBorderColor,
Color disabledBorderColor,
BorderRadius borderRadius,
double borderWidth,
Widget child,
}) : data = ToggleButtonsThemeData(
color: color,
selectedColor: selectedColor,
disabledColor: disabledColor,
fillColor: fillColor,
focusColor: focusColor,
highlightColor: highlightColor,
hoverColor: hoverColor,
splashColor: splashColor,
borderColor: borderColor,
selectedBorderColor: selectedBorderColor,
disabledBorderColor: disabledBorderColor,
borderRadius: borderRadius,
borderWidth: borderWidth,
),
super(key: key, child: child);
/// Specifies the color and border values for descendant [ToggleButtons] widgets.
final ToggleButtonsThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [ToggleButtonsTheme] widget, then
/// [ThemeData.toggleButtonsTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// ToggleButtonsTheme theme = ToggleButtonsTheme.of(context);
/// ```
static ToggleButtonsThemeData of(BuildContext context) {
final ToggleButtonsTheme toggleButtonsTheme = context.inheritFromWidgetOfExactType(ToggleButtonsTheme);
return toggleButtonsTheme?.data ?? Theme.of(context).toggleButtonsTheme;
}
@override
bool updateShouldNotify(ToggleButtonsTheme oldWidget) => data != oldWidget.data;
}
\ No newline at end of file
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
const double _defaultBorderWidth = 1.0;
Widget boilerplate({Widget child}) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(child: child),
);
}
void main() {
testWidgets('Initial toggle state is reflected', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
onPressed: (int index) {},
isSelected: const <bool>[false, true],
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
);
final DefaultTextStyle textStyleOne = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyleOne.style.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
final DefaultTextStyle textStyleTwo = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'Second child'),
);
expect(textStyleTwo.style.color, theme.colorScheme.primary);
});
testWidgets(
'onPressed is triggered on button tap',
(WidgetTester tester) async {
final List<bool> _isSelected = <bool>[false, true];
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return boilerplate(
child: ToggleButtons(
onPressed: (int index) {
setState(() {
_isSelected[index] = !_isSelected[index];
});
},
isSelected: _isSelected,
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
);
},
),
),
);
DefaultTextStyle textStyleOne;
DefaultTextStyle textStyleTwo;
expect(_isSelected[0], isFalse);
expect(_isSelected[1], isTrue);
textStyleOne = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyleOne.style.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
textStyleTwo = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'Second child'),
);
expect(textStyleTwo.style.color, theme.colorScheme.primary);
await tester.tap(find.text('Second child'));
await tester.pumpAndSettle();
expect(_isSelected[0], isFalse);
expect(_isSelected[1], isFalse);
textStyleOne = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyleOne.style.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
textStyleTwo = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'Second child'),
);
expect(
textStyleTwo.style.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
},
);
testWidgets(
'onPressed that is null disables buttons',
(WidgetTester tester) async {
final List<bool> _isSelected = <bool>[false, true];
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: _isSelected,
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
);
DefaultTextStyle textStyleOne;
DefaultTextStyle textStyleTwo;
expect(_isSelected[0], isFalse);
expect(_isSelected[1], isTrue);
textStyleOne = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyleOne.style.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
textStyleTwo = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'Second child'),
);
expect(
textStyleTwo.style.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
await tester.tap(find.text('Second child'));
await tester.pumpAndSettle();
// nothing should change
expect(_isSelected[0], isFalse);
expect(_isSelected[1], isTrue);
textStyleOne = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyleOne.style.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
textStyleTwo = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'Second child'),
);
expect(
textStyleTwo.style.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
},
);
testWidgets('children property cannot be null', (WidgetTester tester) async {
try {
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false, true],
onPressed: (int index) {},
children: null,
),
),
),
);
fail(
'Should not be possible to create a toggle button with no children.');
} on AssertionError catch (e) {
expect(e.toString(), contains('children != null'));
}
});
testWidgets(
'isSelected property cannot be null',
(WidgetTester tester) async {
try {
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: null,
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
);
fail(
'Should not be possible to create a toggle button with no isSelected.');
} on AssertionError catch (e) {
expect(e.toString(), contains('isSelected != null'));
}
},
);
testWidgets(
'children and isSelected properties have to be the same length',
(WidgetTester tester) async {
try {
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
);
fail('Should not be possible to create a toggle button with mismatching'
'children.length and isSelected.length.');
} on AssertionError catch (e) {
expect(e.toString(), contains('children.length'));
expect(e.toString(), contains('isSelected.length'));
}
},
);
testWidgets(
'Default text/icon colors for enabled, selected and disabled states',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
DefaultTextStyle textStyle;
IconTheme iconTheme;
// default enabled color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyle.style.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(
iconTheme.data.color,
theme.colorScheme.onSurface.withOpacity(0.87),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
await tester.pumpAndSettle();
// default selected color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, theme.colorScheme.primary);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, theme.colorScheme.primary);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
await tester.pumpAndSettle();
// default disabled color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(
textStyle.style.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(
iconTheme.data.color,
theme.colorScheme.onSurface.withOpacity(0.38),
);
},
);
testWidgets(
'Custom text/icon colors for enabled, selected and disabled states',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
const Color enabledColor = Colors.lime;
const Color selectedColor = Colors.green;
const Color disabledColor = Colors.yellow;
// tests are ineffective if the custom colors are the same as the theme's
expect(theme.colorScheme.onSurface, isNot(enabledColor));
expect(theme.colorScheme.primary, isNot(selectedColor));
expect(theme.colorScheme.onSurface.withOpacity(0.38), isNot(disabledColor));
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
color: enabledColor,
isSelected: const <bool>[false],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
DefaultTextStyle textStyle;
IconTheme iconTheme;
// custom enabled color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, enabledColor);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, enabledColor);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
selectedColor: selectedColor,
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
await tester.pumpAndSettle();
// custom selected color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, selectedColor);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, selectedColor);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
disabledColor: disabledColor,
isSelected: const <bool>[true],
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
);
await tester.pumpAndSettle();
// custom disabled color
textStyle = tester.firstWidget(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, disabledColor);
iconTheme = tester.firstWidget(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, disabledColor);
},
);
testWidgets('Default button fillColor - unselected', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
]),
],
),
),
),
);
final Material material = tester.firstWidget<Material>(
find.descendant(
of: find.byType(RawMaterialButton),
matching: find.byType(Material),
),
);
expect(
material.color,
theme.colorScheme.surface.withOpacity(0.0),
);
expect(material.type, MaterialType.button);
});
testWidgets('Default button fillColor - selected', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
]),
],
),
),
),
);
final Material material = tester.firstWidget<Material>(
find.descendant(
of: find.byType(RawMaterialButton),
matching: find.byType(Material),
),
);
expect(
material.color,
theme.colorScheme.primary.withOpacity(0.12),
);
expect(material.type, MaterialType.button);
});
testWidgets('Default button fillColor - disabled', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: null,
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
]),
],
),
),
),
);
final Material material = tester.firstWidget<Material>(
find.descendant(
of: find.byType(RawMaterialButton),
matching: find.byType(Material),
),
);
expect(
material.color,
theme.colorScheme.surface.withOpacity(0.0),
);
expect(material.type, MaterialType.button);
});
testWidgets('Custom button fillColor', (WidgetTester tester) async {
const Color customFillColor = Colors.green;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
fillColor: customFillColor,
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
]),
],
),
),
),
);
final Material material = tester.firstWidget<Material>(
find.descendant(
of: find.byType(RawMaterialButton),
matching: find.byType(Material),
),
);
expect(material.color, customFillColor);
expect(material.type, MaterialType.button);
});
testWidgets('Default InkWell colors - unselected', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
onPressed: (int index) {},
focusNodes: <FocusNode>[focusNode],
children: const <Widget>[
Text('First child'),
],
),
),
),
);
final Offset center = tester.getCenter(find.text('First child'));
// splashColor
final TestGesture touchGesture = await tester.createGesture();
await touchGesture.down(center);
await tester.pumpAndSettle();
RenderObject inkFeatures;
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints
..circle(color: theme.colorScheme.onSurface.withOpacity(0.16)),
);
await touchGesture.up();
await tester.pumpAndSettle();
// hoverColor
final TestGesture hoverGesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await hoverGesture.addPointer();
await hoverGesture.moveTo(center);
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.04)),
);
await hoverGesture.removePointer();
// focusColor
focusNode.requestFocus();
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: theme.colorScheme.onSurface.withOpacity(0.12)));
});
testWidgets('Default InkWell colors - selected', (WidgetTester tester) async {
final ThemeData theme = ThemeData();
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
focusNodes: <FocusNode>[focusNode],
children: const <Widget>[
Text('First child'),
],
),
),
),
);
final Offset center = tester.getCenter(find.text('First child'));
// splashColor
final TestGesture touchGesture = await tester.createGesture();
await touchGesture.down(center);
await tester.pumpAndSettle();
RenderObject inkFeatures;
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints
..circle(color: theme.colorScheme.primary.withOpacity(0.16)),
);
await touchGesture.up();
await tester.pumpAndSettle();
// hoverColor
final TestGesture hoverGesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await hoverGesture.addPointer();
await hoverGesture.moveTo(center);
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints..rect(color: theme.colorScheme.primary.withOpacity(0.04)),
);
await hoverGesture.removePointer();
// focusColor
focusNode.requestFocus();
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: theme.colorScheme.primary.withOpacity(0.12)));
});
testWidgets('Custom InkWell colors', (WidgetTester tester) async {
const Color splashColor = Color(0xff4caf50);
const Color highlightColor = Color(0xffcddc39);
const Color hoverColor = Color(0xffffeb3b);
const Color focusColor = Color(0xffffff00);
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
splashColor: splashColor,
highlightColor: highlightColor,
hoverColor: hoverColor,
focusColor: focusColor,
isSelected: const <bool>[true],
onPressed: (int index) {},
focusNodes: <FocusNode>[focusNode],
children: const <Widget>[
Text('First child'),
],
),
),
),
);
final Offset center = tester.getCenter(find.text('First child'));
// splashColor
final TestGesture touchGesture = await tester.createGesture();
await touchGesture.down(center);
await tester.pumpAndSettle();
RenderObject inkFeatures;
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints
..circle(color: splashColor)
);
await touchGesture.up();
await tester.pumpAndSettle();
// hoverColor
final TestGesture hoverGesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await hoverGesture.addPointer();
await hoverGesture.moveTo(center);
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: hoverColor));
await hoverGesture.removePointer();
// focusColor
focusNode.requestFocus();
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: focusColor));
});
testWidgets(
'Default border width and border colors for enabled, selected and disabled states',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
const double defaultBorderWidth = 1.0;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
);
RenderObject toggleButtonRenderObject;
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false],
children: const <Widget>[
Text('First child'),
],
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: defaultBorderWidth,
),
);
},
);
testWidgets(
'Custom border width and border colors for enabled, selected and disabled states',
(WidgetTester tester) async {
const Color borderColor = Color(0xff4caf50);
const Color selectedBorderColor = Color(0xffcddc39);
const Color disabledBorderColor = Color(0xffffeb3b);
const double customWidth = 2.0;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
borderColor: borderColor,
borderWidth: customWidth,
isSelected: const <bool>[false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
);
RenderObject toggleButtonRenderObject;
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: borderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: borderColor,
strokeWidth: customWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
selectedBorderColor: selectedBorderColor,
borderWidth: customWidth,
isSelected: const <bool>[true],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: selectedBorderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: selectedBorderColor,
strokeWidth: customWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
disabledBorderColor: disabledBorderColor,
borderWidth: customWidth,
isSelected: const <bool>[false],
children: const <Widget>[
Text('First child'),
],
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: disabledBorderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: disabledBorderColor,
strokeWidth: customWidth,
),
);
},
);
testWidgets('Height of segmented control is determined by tallest widget', (WidgetTester tester) async {
final List<Widget> children = <Widget>[
Container(
constraints: const BoxConstraints.tightFor(height: 100.0),
),
Container(
constraints: const BoxConstraints.tightFor(height: 400.0), // tallest widget
),
Container(
constraints: const BoxConstraints.tightFor(height: 200.0),
),
];
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false, true, false],
children: children,
),
),
),
);
final List<Widget> toggleButtons = tester.allWidgets.where((Widget widget) {
return widget.runtimeType.toString() == '_SelectToggleButton';
}).toList();
for (int i = 0; i < toggleButtons.length; i++) {
final Rect rect = tester.getRect(find.byWidget(toggleButtons[i]));
expect(rect.height, 400.0 + 2 * _defaultBorderWidth);
}
});
testWidgets('Sizes of toggle buttons rebuilds with the correct dimensions', (WidgetTester tester) async {
final List<Widget> children = <Widget>[
Container(
constraints: const BoxConstraints.tightFor(
width: 100.0,
height: 100.0,
),
),
Container(
constraints: const BoxConstraints.tightFor(
width: 100.0,
height: 100.0,
),
),
Container(
constraints: const BoxConstraints.tightFor(
width: 100.0,
height: 100.0,
),
),
];
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false, true, false],
children: children,
),
),
),
);
List<Widget> toggleButtons;
toggleButtons = tester.allWidgets.where((Widget widget) {
return widget.runtimeType.toString() == '_SelectToggleButton';
}).toList();
for (int i = 0; i < toggleButtons.length; i++) {
final Rect rect = tester.getRect(find.byWidget(toggleButtons[i]));
expect(rect.height, 100.0 + 2 * _defaultBorderWidth);
// Only the last button paints both leading and trailing borders.
// Other buttons only paint the leading border.
if (i == toggleButtons.length - 1) {
expect(rect.width, 100.0 + 2 * _defaultBorderWidth);
} else {
expect(rect.width, 100.0 + 1 * _defaultBorderWidth);
}
}
final List<Widget> childrenRebuilt = <Widget>[
Container(
constraints: const BoxConstraints.tightFor(
width: 200.0,
height: 200.0,
),
),
Container(
constraints: const BoxConstraints.tightFor(
width: 200.0,
height: 200.0,
),
),
Container(
constraints: const BoxConstraints.tightFor(
width: 200.0,
height: 200.0,
),
),
];
// Update border width and widget sized to verify layout updates correctly
const double customBorderWidth = 5.0;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
borderWidth: customBorderWidth,
isSelected: const <bool>[false, true, false],
children: childrenRebuilt,
),
),
),
);
toggleButtons = tester.allWidgets.where((Widget widget) {
return widget.runtimeType.toString() == '_SelectToggleButton';
}).toList();
// Only the last button paints both leading and trailing borders.
// Other buttons only paint the leading border.
for (int i = 0; i < toggleButtons.length; i++) {
final Rect rect = tester.getRect(find.byWidget(toggleButtons[i]));
expect(rect.height, 200.0 + 2 * customBorderWidth);
if (i == toggleButtons.length - 1) {
expect(rect.width, 200.0 + 2 * customBorderWidth);
} else {
expect(rect.width, 200.0 + 1 * customBorderWidth);
}
}
});
testWidgets('ToggleButtons text baseline alignment', (WidgetTester tester) async {
await tester.pumpWidget(
Material(
child: boilerplate(
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: <Widget>[
ToggleButtons(
borderWidth: 5.0,
isSelected: const <bool>[false, true],
children: const <Widget>[
Text('First child', style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0)),
Text('Second child', style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0)),
],
),
const MaterialButton(
onPressed: null,
child: Text('Material Button', style: TextStyle(fontFamily: 'Ahem', fontSize: 20.0)),
),
const Text('Text', style: TextStyle(fontFamily: 'Ahem', fontSize: 30.0)),
],
),
),
),
);
// The Ahem font extends 0.2 * fontSize below the baseline.
// So the three row elements line up like this:
//
// ToggleButton MaterialButton Text
// ------------------------------------ baseline
// 2 4 6 space below the baseline = 0.2 * fontSize
// ------------------------------------ widget text dy values
final double firstToggleButtonDy = tester.getBottomLeft(find.text('First child')).dy;
final double secondToggleButtonDy = tester.getBottomLeft(find.text('Second child')).dy;
final double materialButtonDy = tester.getBottomLeft(find.text('Material Button')).dy;
final double textDy = tester.getBottomLeft(find.text('Text')).dy;
expect(firstToggleButtonDy, secondToggleButtonDy);
expect(firstToggleButtonDy, closeTo(materialButtonDy - 2.0, 0.001));
expect(firstToggleButtonDy, closeTo(textDy - 4.0, 0.001));
});
testWidgets('Directionality test', (WidgetTester tester) async {
await tester.pumpWidget(
Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: ToggleButtons(
onPressed: (int index) {},
isSelected: const <bool>[false, true],
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
),
);
expect(
tester.getTopRight(find.text('First child')).dx < tester.getTopRight(find.text('Second child')).dx,
isTrue,
);
await tester.pumpWidget(
Material(
child: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: ToggleButtons(
onPressed: (int index) {},
isSelected: const <bool>[false, true],
children: const <Widget>[
Text('First child'),
Text('Second child'),
],
),
),
),
),
);
expect(
tester.getTopRight(find.text('First child')).dx > tester.getTopRight(find.text('Second child')).dx,
isTrue,
);
});
testWidgets(
'Properly draws borders based on state',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtons(
isSelected: const <bool>[false, true, false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
Text('Second child'),
Text('Third child'),
],
),
),
),
);
final List<RenderObject> toggleButtonRenderObject = tester.allRenderObjects.where((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
}).toSet().toList();
// The first button paints the leading, top and bottom sides with a path
expect(
toggleButtonRenderObject[0],
paints
// leading side, top and bottom - enabled
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
// The middle buttons paint a leading side path first, followed by a
// top and bottom side path
expect(
toggleButtonRenderObject[1],
paints
// leading side - selected
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
)
// top and bottom - selected
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
// The last button paints a leading side path first, followed by
// a trailing, top and bottom side path
expect(
toggleButtonRenderObject[2],
paints
// leading side - selected, since previous button is selected
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
)
// trailing side, top and bottom - enabled
..path(
style: PaintingStyle.stroke,
color: theme.colorScheme.onSurface.withOpacity(0.12),
strokeWidth: _defaultBorderWidth,
),
);
},
);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
Widget boilerplate({Widget child}) {
return Directionality(
textDirection: TextDirection.ltr,
child: Center(child: child),
);
}
void main() {
test('ToggleButtonsThemeData copyWith, ==, hashCode basics', () {
expect(const ToggleButtonsThemeData(), const ToggleButtonsThemeData().copyWith());
expect(const ToggleButtonsThemeData().hashCode, const ToggleButtonsThemeData().copyWith().hashCode);
});
test('ToggleButtonsThemeData defaults', () {
const ToggleButtonsThemeData themeData = ToggleButtonsThemeData();
expect(themeData.color, null);
expect(themeData.selectedColor, null);
expect(themeData.disabledColor, null);
expect(themeData.fillColor, null);
expect(themeData.focusColor, null);
expect(themeData.highlightColor, null);
expect(themeData.hoverColor, null);
expect(themeData.splashColor, null);
expect(themeData.borderColor, null);
expect(themeData.selectedBorderColor, null);
expect(themeData.disabledBorderColor, null);
expect(themeData.borderRadius, null);
expect(themeData.borderWidth, null);
final ToggleButtonsTheme theme = ToggleButtonsTheme();
expect(theme.data.color, null);
expect(theme.data.selectedColor, null);
expect(theme.data.disabledColor, null);
expect(theme.data.fillColor, null);
expect(theme.data.focusColor, null);
expect(theme.data.highlightColor, null);
expect(theme.data.hoverColor, null);
expect(theme.data.splashColor, null);
expect(theme.data.borderColor, null);
expect(theme.data.selectedBorderColor, null);
expect(theme.data.disabledBorderColor, null);
expect(theme.data.borderRadius, null);
expect(theme.data.borderWidth, null);
});
testWidgets('Default ToggleButtonsThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ToggleButtonsThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('ToggleButtonsThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ToggleButtonsThemeData(
color: Color(0xfffffff0),
selectedColor: Color(0xfffffff1),
disabledColor: Color(0xfffffff2),
fillColor: Color(0xfffffff3),
focusColor: Color(0xfffffff4),
highlightColor: Color(0xfffffff5),
hoverColor: Color(0xfffffff6),
splashColor: Color(0xfffffff7),
borderColor: Color(0xfffffff8),
selectedBorderColor: Color(0xfffffff9),
disabledBorderColor: Color(0xfffffffa),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderWidth: 2.0,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[
'color: Color(0xfffffff0)',
'selectedColor: Color(0xfffffff1)',
'disabledColor: Color(0xfffffff2)',
'fillColor: Color(0xfffffff3)',
'focusColor: Color(0xfffffff4)',
'highlightColor: Color(0xfffffff5)',
'hoverColor: Color(0xfffffff6)',
'splashColor: Color(0xfffffff7)',
'borderColor: Color(0xfffffff8)',
'selectedBorderColor: Color(0xfffffff9)',
'disabledBorderColor: Color(0xfffffffa)',
'borderRadius: BorderRadius.circular(4.0)',
'borderWidth: 2.0',
]);
});
testWidgets(
'Theme text/icon colors for enabled, selected and disabled states',
(WidgetTester tester) async {
final ThemeData theme = ThemeData();
const Color enabledColor = Colors.lime;
const Color selectedColor = Colors.green;
const Color disabledColor = Colors.yellow;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
child: ToggleButtons(
color: enabledColor,
isSelected: const <bool>[false],
onPressed: (int index) {},
children: <Widget>[
// This Row is used like this to test for both TextStyle
// and IconTheme for Text and Icon widgets respectively.
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
),
);
DefaultTextStyle textStyle;
IconTheme iconTheme;
// custom theme enabled color
expect(theme.colorScheme.onSurface, isNot(enabledColor));
textStyle = tester.firstWidget<DefaultTextStyle>(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, enabledColor);
iconTheme = tester.firstWidget<IconTheme>(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, enabledColor);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
selectedColor: selectedColor,
child: ToggleButtons(
color: enabledColor,
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
),
);
await tester.pumpAndSettle();
// custom theme selected color
expect(theme.colorScheme.primary, isNot(selectedColor));
textStyle = tester.firstWidget<DefaultTextStyle>(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, selectedColor);
iconTheme = tester.firstWidget<IconTheme>(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, selectedColor);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
disabledColor: disabledColor,
child: ToggleButtons(
color: enabledColor,
isSelected: const <bool>[false],
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
Icon(Icons.check),
]),
],
),
),
),
),
);
await tester.pumpAndSettle();
// custom theme disabled color
expect(theme.disabledColor, isNot(disabledColor));
textStyle = tester.firstWidget<DefaultTextStyle>(
find.widgetWithText(DefaultTextStyle, 'First child'),
);
expect(textStyle.style.color, disabledColor);
iconTheme = tester.firstWidget<IconTheme>(
find.widgetWithIcon(IconTheme, Icons.check),
);
expect(iconTheme.data.color, disabledColor);
},
);
testWidgets('Theme button fillColor', (WidgetTester tester) async {
const Color customFillColor = Colors.green;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
fillColor: customFillColor,
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
children: <Widget>[
Row(children: const <Widget>[
Text('First child'),
]),
],
),
),
),
),
);
final Material material = tester.firstWidget<Material>(
find.descendant(
of: find.byType(RawMaterialButton),
matching: find.byType(Material),
),
);
expect(material.color, customFillColor);
expect(material.type, MaterialType.button);
});
testWidgets('Theme InkWell colors', (WidgetTester tester) async {
const Color splashColor = Color(0xff4caf50);
const Color highlightColor = Color(0xffcddc39);
const Color hoverColor = Color(0xffffeb3b);
const Color focusColor = Color(0xffffff00);
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
splashColor: splashColor,
highlightColor: highlightColor,
hoverColor: hoverColor,
focusColor: focusColor,
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
focusNodes: <FocusNode>[focusNode],
children: const <Widget>[
Text('First child'),
],
),
),
),
),
);
final Offset center = tester.getCenter(find.text('First child'));
// splashColor
// highlightColor
final TestGesture touchGesture = await tester.createGesture();
await touchGesture.down(center);
await tester.pumpAndSettle();
RenderObject inkFeatures;
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(
inkFeatures,
paints
..circle(color: splashColor)
..rect(color: highlightColor),
);
await touchGesture.up();
await tester.pumpAndSettle();
// hoverColor
final TestGesture hoverGesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await hoverGesture.addPointer();
await hoverGesture.moveTo(center);
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: hoverColor));
await hoverGesture.removePointer();
// focusColor
focusNode.requestFocus();
await tester.pumpAndSettle();
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_RenderInkFeatures';
});
expect(inkFeatures, paints..rect(color: focusColor));
});
testWidgets(
'Theme border width and border colors for enabled, selected and disabled states',
(WidgetTester tester) async {
const Color borderColor = Color(0xff4caf50);
const Color selectedBorderColor = Color(0xffcddc39);
const Color disabledBorderColor = Color(0xffffeb3b);
const double customWidth = 2.0;
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
borderColor: borderColor,
borderWidth: customWidth,
child: ToggleButtons(
isSelected: const <bool>[false],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
),
);
RenderObject toggleButtonRenderObject;
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: borderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: borderColor,
strokeWidth: customWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
selectedBorderColor: selectedBorderColor,
borderWidth: customWidth,
child: ToggleButtons(
isSelected: const <bool>[true],
onPressed: (int index) {},
children: const <Widget>[
Text('First child'),
],
),
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: selectedBorderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: selectedBorderColor,
strokeWidth: customWidth,
),
);
await tester.pumpWidget(
Material(
child: boilerplate(
child: ToggleButtonsTheme(
disabledBorderColor: disabledBorderColor,
borderWidth: customWidth,
child: ToggleButtons(
isSelected: const <bool>[false],
children: const <Widget>[
Text('First child'),
],
),
),
),
),
);
toggleButtonRenderObject = tester.allRenderObjects.firstWhere((RenderObject object) {
return object.runtimeType.toString() == '_SelectToggleButtonRenderObject';
});
expect(
toggleButtonRenderObject,
paints
// trailing side
..path(
style: PaintingStyle.stroke,
color: disabledBorderColor,
strokeWidth: customWidth,
)
// leading side, top and bottom
..path(
style: PaintingStyle.stroke,
color: disabledBorderColor,
strokeWidth: customWidth,
),
);
},
);
}
\ No newline at end of file
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