Unverified Commit 9240c9a0 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Remove deprecated OutlineButton (#98546)

parent cd9e30bb
......@@ -104,7 +104,6 @@ export 'src/material/navigation_bar_theme.dart';
export 'src/material/navigation_rail.dart';
export 'src/material/navigation_rail_theme.dart';
export 'src/material/no_splash.dart';
export 'src/material/outline_button.dart';
export 'src/material/outlined_button.dart';
export 'src/material/outlined_button_theme.dart';
export 'src/material/page.dart';
......
......@@ -26,10 +26,10 @@ import 'theme_data.dart';
/// [ButtonStyle] of a [TextButton], [ElevatedButton] or an
/// [OutlinedButton].
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// TextButton, ElevatedButton, and OutlinedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
/// FlatButton and RaisedButton have been replaced by
/// TextButton and ElevatedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme and
/// ElevatedButtonTheme. The original classes
/// have been deprecated, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
......
......@@ -11,7 +11,6 @@ import 'constants.dart';
import 'flat_button.dart';
import 'material_button.dart';
import 'material_state.dart';
import 'outline_button.dart';
import 'raised_button.dart';
import 'theme.dart';
import 'theme_data.dart' show MaterialTapTargetSize;
......@@ -22,7 +21,7 @@ import 'theme_data.dart' show MaterialTapTargetSize;
///
/// See also:
///
/// * [RaisedButton], [FlatButton], [OutlineButton], which are configured
/// * [RaisedButton] and [FlatButton] which are configured
/// based on the ambient [ButtonTheme].
enum ButtonTextTheme {
/// Button text is black or white depending on [ThemeData.brightness].
......@@ -69,7 +68,7 @@ enum ButtonBarLayoutBehavior {
///
/// See also:
///
/// * [FlatButton] [RaisedButton], and [OutlineButton], which are styled
/// * [FlatButton] and [RaisedButton] which are styled
/// based on the ambient button theme.
/// * [RawMaterialButton], which can be used to configure a button that doesn't
/// depend on any inherited themes.
......@@ -175,10 +174,10 @@ class ButtonTheme extends InheritedTheme {
/// * [ElevatedButton], [ElevatedButtonTheme], [ElevatedButtonThemeData],
/// * [OutlinedButton], [OutlinedButtonTheme], [OutlinedButtonThemeData]
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// TextButton, ElevatedButton, and OutlinedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
/// FlatButton and RaisedButton have been replaced by
/// TextButton and ElevatedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme and
/// ElevatedButtonTheme. The original classes
/// have been deprecated, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
......@@ -197,8 +196,8 @@ class ButtonThemeData with Diagnosticable {
/// [height] parameters must greater than or equal to zero.
///
/// The ButtonTheme's methods that have a [MaterialButton] parameter and
/// have a name with a `get` prefix are used by [RaisedButton],
/// [OutlineButton], and [FlatButton] to configure a [RawMaterialButton].
/// have a name with a `get` prefix are used by [RaisedButton]
/// and [FlatButton] to configure a [RawMaterialButton].
const ButtonThemeData({
this.textTheme = ButtonTextTheme.normal,
this.minWidth = 88.0,
......@@ -279,8 +278,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getPadding], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getPadding], which is used by [RaisedButton] and [FlatButton].
EdgeInsetsGeometry get padding {
if (_padding != null)
return _padding!;
......@@ -306,8 +304,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getShape], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getShape], which is used by [RaisedButton] and [FlatButton].
ShapeBorder get shape {
if (_shape != null)
return _shape!;
......@@ -369,8 +366,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getFocusColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getFocusColor], which is used by [RaisedButton] and [FlatButton].
final Color? _focusColor;
/// The fill color of the button when a pointer is hovering over it.
......@@ -382,8 +378,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getHoverColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getHoverColor], which is used by [RaisedButton] and [FlatButton].
final Color? _hoverColor;
/// The color of the overlay that appears when a button is pressed.
......@@ -392,8 +387,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getHighlightColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getHighlightColor], which is used by [RaisedButton] and [FlatButton].
final Color? _highlightColor;
/// The color of the ink "splash" overlay that appears when a button is tapped.
......@@ -402,8 +396,7 @@ class ButtonThemeData with Diagnosticable {
///
/// See also:
///
/// * [getSplashColor], which is used by [RaisedButton], [OutlineButton]
/// and [FlatButton].
/// * [getSplashColor], which is used by [RaisedButton] and [FlatButton].
final Color? _splashColor;
/// A set of thirteen colors that can be used to derive the button theme's
......@@ -424,8 +417,7 @@ class ButtonThemeData with Diagnosticable {
//
// See also:
//
// * [getMaterialTargetTapSize], which is used by [RaisedButton],
// [OutlineButton] and [FlatButton].
// * [getMaterialTargetTapSize], which is used by [RaisedButton] and [FlatButton].
final MaterialTapTargetSize? _materialTapTargetSize;
/// The [button]'s overall brightness.
......@@ -489,7 +481,7 @@ class ButtonThemeData with Diagnosticable {
/// Otherwise, returns [MaterialButton.disabledColor] if it is non-null and
/// the button is disabled.
///
/// Otherwise, if button is a [FlatButton] or an [OutlineButton] then null is
/// Otherwise, if button is a [FlatButton] then null is
/// returned.
///
/// Otherwise, if button is a [RaisedButton], returns the `buttonColor`
......@@ -510,7 +502,7 @@ class ButtonThemeData with Diagnosticable {
if (fillColor != null)
return fillColor;
if (button is FlatButton || button is OutlineButton || button.runtimeType == MaterialButton)
if (button is FlatButton || button.runtimeType == MaterialButton)
return null;
if (button.enabled && button is RaisedButton && _buttonColor != null)
......@@ -542,7 +534,7 @@ class ButtonThemeData with Diagnosticable {
/// [getBrightness] resolves to [Brightness.light].
/// * [ButtonTextTheme.accent]: [ColorScheme.secondary] of [colorScheme].
/// * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
/// otherwise if [button] is a [FlatButton] or an [OutlineButton] then
/// otherwise if [button] is a [FlatButton] then
/// [ColorScheme.primary] of [colorScheme], otherwise [Colors.black].
Color getTextColor(MaterialButton button) {
if (!button.enabled)
......@@ -565,7 +557,7 @@ class ButtonThemeData with Diagnosticable {
: getBrightness(button) == Brightness.dark;
if (fillIsDark)
return Colors.white;
if (button is FlatButton || button is OutlineButton)
if (button is FlatButton)
return colorScheme!.primary;
return Colors.black;
}
......@@ -577,7 +569,7 @@ class ButtonThemeData with Diagnosticable {
/// Returns the button's [MaterialButton.splashColor] if it is non-null.
///
/// Otherwise, returns the value of the `splashColor` constructor parameter
/// it is non-null and [button] is a [RaisedButton] or an [OutlineButton].
/// it is non-null and [button] is a [RaisedButton].
///
/// Otherwise, returns the value of the `splashColor` constructor parameter
/// if it is non-null and [button] is a [FlatButton] and
......@@ -588,7 +580,7 @@ class ButtonThemeData with Diagnosticable {
if (button.splashColor != null)
return button.splashColor!;
if (_splashColor != null && (button is RaisedButton || button is OutlineButton))
if (_splashColor != null && button is RaisedButton)
return _splashColor!;
if (_splashColor != null && button is FlatButton) {
......@@ -669,15 +661,13 @@ class ButtonThemeData with Diagnosticable {
///
/// Returns the button's [MaterialButton.focusElevation] if it is non-null.
///
/// If button is a [FlatButton] or an [OutlineButton] then the focus
/// If button is a [FlatButton] then the focus
/// elevation is 0.0, otherwise the highlight elevation is 4.0.
double getFocusElevation(MaterialButton button) {
if (button.focusElevation != null)
return button.focusElevation!;
if (button is FlatButton)
return 0.0;
if (button is OutlineButton)
return 0.0;
return 4.0;
}
......@@ -685,15 +675,13 @@ class ButtonThemeData with Diagnosticable {
///
/// Returns the button's [MaterialButton.hoverElevation] if it is non-null.
///
/// If button is a [FlatButton] or an [OutlineButton] then the hover
/// If button is a [FlatButton] then the hover
/// elevation is 0.0, otherwise the highlight elevation is 4.0.
double getHoverElevation(MaterialButton button) {
if (button.hoverElevation != null)
return button.hoverElevation!;
if (button is FlatButton)
return 0.0;
if (button is OutlineButton)
return 0.0;
return 4.0;
}
......@@ -701,15 +689,13 @@ class ButtonThemeData with Diagnosticable {
///
/// Returns the button's [MaterialButton.highlightElevation] if it is non-null.
///
/// If button is a [FlatButton] or an [OutlineButton] then the highlight
/// If button is a [FlatButton] then the highlight
/// elevation is 0.0, otherwise the highlight elevation is 8.0.
double getHighlightElevation(MaterialButton button) {
if (button.highlightElevation != null)
return button.highlightElevation!;
if (button is FlatButton)
return 0.0;
if (button is OutlineButton)
return 0.0;
return 8.0;
}
......@@ -730,7 +716,7 @@ class ButtonThemeData with Diagnosticable {
/// Returns the button's [MaterialButton.padding] if it is non-null.
///
/// If this is a button constructed with [RaisedButton.icon] or
/// [FlatButton.icon] or [OutlineButton.icon] then the padding is:
/// [FlatButton.icon] then the padding is:
/// `EdgeInsetsDirectional.only(start: 12.0, end: 16.0)`.
///
/// Otherwise, returns [padding] if it is non-null.
......
......@@ -14,10 +14,10 @@ import 'theme_data.dart';
///
/// ### This class is deprecated, please use [TextButton] instead.
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// [TextButton], [ElevatedButton], and [OutlinedButton] respectively.
/// ButtonTheme has been replaced by [TextButtonTheme],
/// [ElevatedButtonTheme], and [OutlinedButtonTheme]. The original classes
/// FlatButton and RaisedButton have been replaced by
/// [TextButton] and [ElevatedButton] respectively.
/// ButtonTheme has been replaced by [TextButtonTheme] and
/// [ElevatedButtonTheme]. The original classes
/// will eventually be removed, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
......
......@@ -21,10 +21,10 @@ import 'theme_data.dart';
///
/// ### This class is obsolete.
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// TextButton, ElevatedButton, and OutlinedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The appearance of the
/// FlatButton and RaisedButton have been replaced by
/// TextButton and ElevatedButton respectively.
/// ButtonTheme has been replaced by TextButtonTheme and
/// ElevatedButtonTheme. The appearance of the
/// new widgets can be customized by specifying a [ButtonStyle]
/// or by creating a one-off style using a `styleFrom` method like
/// [TextButton.styleFrom]. The original button classes
......@@ -38,8 +38,8 @@ import 'theme_data.dart';
/// MaterialButtons whose [onPressed] and [onLongPress] callbacks are null will be disabled. To have
/// an enabled button, make sure to pass a non-null value for [onPressed] or [onLongPress].
///
/// Rather than using this class directly, consider using [FlatButton],
/// [OutlineButton], or [RaisedButton], which configure this class with
/// Rather than using this class directly, consider using [FlatButton]
/// or [RaisedButton], which configure this class with
/// appropriate defaults that match the material design specification.
///
/// To create a button directly, without inheriting theme defaults, use
......@@ -457,8 +457,8 @@ class MaterialButton extends StatelessWidget {
}
}
/// The type of [MaterialButton]s created with RaisedButton.icon, FlatButton.icon,
/// and OutlineButton.icon.
/// The type of [MaterialButton]s created with RaisedButton.icon] and
/// FlatButton.icon.
///
/// This mixin only exists to give the "label and icon" button widgets a distinct
/// type for the sake of [ButtonTheme].
......
// Copyright 2014 The Flutter 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'button_theme.dart';
import 'colors.dart';
import 'material_button.dart';
import 'material_state.dart';
import 'raised_button.dart';
import 'theme.dart';
import 'theme_data.dart';
// The total time to make the button's fill color opaque and change
// its elevation. Only applies when highlightElevation > 0.0.
const Duration _kPressDuration = Duration(milliseconds: 150);
// Half of _kPressDuration: just the time to change the button's
// elevation. Only applies when highlightElevation > 0.0.
const Duration _kElevationDuration = Duration(milliseconds: 75);
/// Similar to a [FlatButton] with a thin grey rounded rectangle border.
///
/// ### This class is deprecated, please use [OutlinedButton] instead.
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// [TextButton], [ElevatedButton], and [OutlinedButton] respectively.
/// ButtonTheme has been replaced by TextButtonTheme,
/// ElevatedButtonTheme, and OutlinedButtonTheme. The original classes
/// will eventually be removed, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
/// [flutter.dev/go/material-button-migration-guide](https://flutter.dev/go/material-button-migration-guide).
@Deprecated(
'Use OutlinedButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
'This feature was deprecated after v1.26.0-18.0.pre.',
)
class OutlineButton extends MaterialButton {
/// Create an outline button.
///
/// The [highlightElevation] argument must be null or a positive value
/// and the [autofocus] and [clipBehavior] arguments must not be null.
@Deprecated(
'Use OutlinedButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
'This feature was deprecated after v1.26.0-18.0.pre.',
)
const OutlineButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
MouseCursor? mouseCursor,
ButtonTextTheme? textTheme,
Color? textColor,
Color? disabledTextColor,
Color? color,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
double? highlightElevation,
this.borderSide,
this.disabledBorderColor,
this.highlightedBorderColor,
EdgeInsetsGeometry? padding,
VisualDensity? visualDensity,
ShapeBorder? shape,
Clip clipBehavior = Clip.none,
FocusNode? focusNode,
bool autofocus = false,
MaterialTapTargetSize? materialTapTargetSize,
Widget? child,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(clipBehavior != null),
assert(autofocus != null),
super(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
mouseCursor: mouseCursor,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
focusColor: focusColor,
hoverColor: hoverColor,
highlightColor: highlightColor,
splashColor: splashColor,
highlightElevation: highlightElevation,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
materialTapTargetSize: materialTapTargetSize,
autofocus: autofocus,
child: child,
);
/// Create an outline button from a pair of widgets that serve as the button's
/// [icon] and [label].
///
/// The icon and label are arranged in a row and padded by 12 logical pixels
/// at the start, and 16 at the end, with an 8 pixel gap in between.
///
/// The [highlightElevation] argument must be null or a positive value. The
/// [icon], [label], [autofocus], and [clipBehavior] arguments must not be null.
@Deprecated(
'Use OutlinedButton instead. See the migration guide in flutter.dev/go/material-button-migration-guide). '
'This feature was deprecated after v1.26.0-18.0.pre.',
)
factory OutlineButton.icon({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
MouseCursor? mouseCursor,
ButtonTextTheme? textTheme,
Color? textColor,
Color? disabledTextColor,
Color? color,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
double? highlightElevation,
Color? highlightedBorderColor,
Color? disabledBorderColor,
BorderSide? borderSide,
EdgeInsetsGeometry? padding,
VisualDensity? visualDensity,
ShapeBorder? shape,
Clip clipBehavior,
FocusNode? focusNode,
bool autofocus,
MaterialTapTargetSize? materialTapTargetSize,
required Widget icon,
required Widget label,
}) = _OutlineButtonWithIcon;
/// The outline border's color when the button is [enabled] and pressed.
///
/// By default the border's color does not change when the button
/// is pressed.
///
/// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
final Color? highlightedBorderColor;
/// The outline border's color when the button is not [enabled].
///
/// By default the outline border's color does not change when the
/// button is disabled.
///
/// This field is ignored if [BorderSide.color] is a [MaterialStateProperty<Color>].
final Color? disabledBorderColor;
/// Defines the color of the border when the button is enabled but not
/// pressed, and the border outline's width and style in general.
///
/// If the border side's [BorderSide.style] is [BorderStyle.none], then
/// an outline is not drawn.
///
/// If null the default border's style is [BorderStyle.solid], its
/// [BorderSide.width] is 1.0, and its color is a light shade of grey.
///
/// If [BorderSide.color] is a [MaterialStateProperty<Color>], [MaterialStateProperty.resolve]
/// is used in all states and both [highlightedBorderColor] and [disabledBorderColor]
/// are ignored.
final BorderSide? borderSide;
@override
Widget build(BuildContext context) {
final ButtonThemeData buttonTheme = ButtonTheme.of(context);
return _OutlineButton(
autofocus: autofocus,
onPressed: onPressed,
onLongPress: onLongPress,
mouseCursor: mouseCursor,
brightness: buttonTheme.getBrightness(this),
textTheme: textTheme,
textColor: buttonTheme.getTextColor(this),
disabledTextColor: buttonTheme.getDisabledTextColor(this),
color: color,
focusColor: buttonTheme.getFocusColor(this),
hoverColor: buttonTheme.getHoverColor(this),
highlightColor: buttonTheme.getHighlightColor(this),
splashColor: buttonTheme.getSplashColor(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
borderSide: borderSide,
disabledBorderColor: disabledBorderColor,
highlightedBorderColor: highlightedBorderColor ?? buttonTheme.colorScheme!.primary,
padding: buttonTheme.getPadding(this),
visualDensity: visualDensity,
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior,
focusNode: focusNode,
materialTapTargetSize: materialTapTargetSize,
child: child,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<BorderSide>('borderSide', borderSide, defaultValue: null));
properties.add(ColorProperty('disabledBorderColor', disabledBorderColor, defaultValue: null));
properties.add(ColorProperty('highlightedBorderColor', highlightedBorderColor, defaultValue: null));
}
}
// The type of OutlineButtons created with OutlineButton.icon.
//
// This class only exists to give OutlineButtons created with OutlineButton.icon
// a distinct class for the sake of ButtonTheme. It can not be instantiated.
class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMixin {
_OutlineButtonWithIcon({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
MouseCursor? mouseCursor,
ButtonTextTheme? textTheme,
Color? textColor,
Color? disabledTextColor,
Color? color,
Color? focusColor,
Color? hoverColor,
Color? highlightColor,
Color? splashColor,
double? highlightElevation,
Color? highlightedBorderColor,
Color? disabledBorderColor,
BorderSide? borderSide,
EdgeInsetsGeometry? padding,
VisualDensity? visualDensity,
ShapeBorder? shape,
Clip clipBehavior = Clip.none,
FocusNode? focusNode,
bool autofocus = false,
MaterialTapTargetSize? materialTapTargetSize,
required Widget icon,
required Widget label,
}) : assert(highlightElevation == null || highlightElevation >= 0.0),
assert(clipBehavior != null),
assert(autofocus != null),
assert(icon != null),
assert(label != null),
super(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
mouseCursor: mouseCursor,
textTheme: textTheme,
textColor: textColor,
disabledTextColor: disabledTextColor,
color: color,
focusColor: focusColor,
hoverColor: hoverColor,
highlightColor: highlightColor,
splashColor: splashColor,
highlightElevation: highlightElevation,
disabledBorderColor: disabledBorderColor,
highlightedBorderColor: highlightedBorderColor,
borderSide: borderSide,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
autofocus: autofocus,
materialTapTargetSize: materialTapTargetSize,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
class _OutlineButton extends StatefulWidget {
const _OutlineButton({
Key? key,
required this.onPressed,
this.onLongPress,
this.mouseCursor,
required this.brightness,
this.textTheme,
required this.textColor,
required this.disabledTextColor,
this.color,
required this.focusColor,
required this.hoverColor,
required this.highlightColor,
required this.splashColor,
required this.highlightElevation,
this.borderSide,
this.disabledBorderColor,
required this.highlightedBorderColor,
required this.padding,
this.visualDensity,
required this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
this.child,
this.materialTapTargetSize,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(highlightedBorderColor != null),
assert(clipBehavior != null),
assert(autofocus != null),
super(key: key);
final VoidCallback? onPressed;
final VoidCallback? onLongPress;
final MouseCursor? mouseCursor;
final Brightness brightness;
final ButtonTextTheme? textTheme;
final Color textColor;
final Color disabledTextColor;
final Color? color;
final Color splashColor;
final Color focusColor;
final Color hoverColor;
final Color highlightColor;
final double highlightElevation;
final BorderSide? borderSide;
final Color? disabledBorderColor;
final Color highlightedBorderColor;
final EdgeInsetsGeometry padding;
final VisualDensity? visualDensity;
final ShapeBorder shape;
final Clip clipBehavior;
final FocusNode? focusNode;
final bool autofocus;
final Widget? child;
final MaterialTapTargetSize? materialTapTargetSize;
bool get enabled => onPressed != null || onLongPress != null;
@override
_OutlineButtonState createState() => _OutlineButtonState();
}
class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fillAnimation;
late Animation<double> _elevationAnimation;
bool _pressed = false;
@override
void initState() {
super.initState();
// When highlightElevation > 0.0, the Material widget animates its
// shape (which includes the outline border) and elevation over
// _kElevationDuration. When pressed, the button makes its fill
// color opaque white first, and then sets its
// highlightElevation. We can't change the elevation while the
// button's fill is translucent, because the shadow fills the
// interior of the button.
_controller = AnimationController(
duration: _kPressDuration,
vsync: this,
);
_fillAnimation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5,
curve: Curves.fastOutSlowIn,
),
);
_elevationAnimation = CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 0.5),
reverseCurve: const Interval(1.0, 1.0),
);
}
@override
void didUpdateWidget(_OutlineButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (_pressed && !widget.enabled) {
_pressed = false;
_controller.reverse();
}
}
void _handleHighlightChanged(bool value) {
if (_pressed == value)
return;
setState(() {
_pressed = value;
if (value)
_controller.forward();
else
_controller.reverse();
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Color _getFillColor() {
if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
return Colors.transparent;
final Color color = widget.color ?? Theme.of(context).canvasColor;
final Tween<Color?> colorTween = ColorTween(
begin: color.withAlpha(0x00),
end: color.withAlpha(0xFF),
);
return colorTween.evaluate(_fillAnimation)!;
}
Color? get _outlineColor {
// If outline color is a `MaterialStateProperty`, it will be used in all
// states, otherwise we determine the outline color in the current state.
if (widget.borderSide?.color is MaterialStateProperty<Color?>)
return widget.borderSide!.color;
if (!widget.enabled)
return widget.disabledBorderColor;
if (_pressed)
return widget.highlightedBorderColor;
return widget.borderSide?.color;
}
BorderSide _getOutline() {
if (widget.borderSide?.style == BorderStyle.none)
return widget.borderSide!;
final Color themeColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.12);
return BorderSide(
color: _outlineColor ?? themeColor,
width: widget.borderSide?.width ?? 1.0,
);
}
double _getHighlightElevation() {
if (widget.highlightElevation == null || widget.highlightElevation == 0.0)
return 0.0;
return Tween<double>(
begin: 0.0,
end: widget.highlightElevation,
).evaluate(_elevationAnimation);
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
return RaisedButton(
autofocus: widget.autofocus,
textColor: widget.textColor,
disabledTextColor: widget.disabledTextColor,
color: _getFillColor(),
splashColor: widget.splashColor,
focusColor: widget.focusColor,
hoverColor: widget.hoverColor,
highlightColor: widget.highlightColor,
disabledColor: Colors.transparent,
onPressed: widget.onPressed,
onLongPress: widget.onLongPress,
mouseCursor: widget.mouseCursor,
elevation: 0.0,
disabledElevation: 0.0,
focusElevation: 0.0,
hoverElevation: 0.0,
highlightElevation: _getHighlightElevation(),
onHighlightChanged: _handleHighlightChanged,
padding: widget.padding,
visualDensity: widget.visualDensity ?? theme.visualDensity,
shape: _OutlineBorder(
shape: widget.shape,
side: _getOutline(),
),
clipBehavior: widget.clipBehavior,
focusNode: widget.focusNode,
animationDuration: _kElevationDuration,
materialTapTargetSize: widget.materialTapTargetSize,
child: widget.child,
);
},
);
}
}
// Render the button's outline border using using the OutlineButton's
// border parameters and the button or buttonTheme's shape.
class _OutlineBorder extends ShapeBorder implements MaterialStateProperty<ShapeBorder> {
const _OutlineBorder({
required this.shape,
required this.side,
}) : assert(shape != null),
assert(side != null);
final ShapeBorder shape;
final BorderSide side;
@override
EdgeInsetsGeometry get dimensions {
return EdgeInsets.all(side.width);
}
@override
ShapeBorder scale(double t) {
return _OutlineBorder(
shape: shape.scale(t),
side: side.scale(t),
);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
assert(t != null);
if (a is _OutlineBorder) {
return _OutlineBorder(
side: BorderSide.lerp(a.side, side, t),
shape: ShapeBorder.lerp(a.shape, shape, t)!,
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
assert(t != null);
if (b is _OutlineBorder) {
return _OutlineBorder(
side: BorderSide.lerp(side, b.side, t),
shape: ShapeBorder.lerp(shape, b.shape, t)!,
);
}
return super.lerpTo(b, t);
}
@override
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
return shape.getInnerPath(rect.deflate(side.width), textDirection: textDirection);
}
@override
Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
return shape.getOuterPath(rect, textDirection: textDirection);
}
@override
void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
switch (side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
canvas.drawPath(shape.getOuterPath(rect, textDirection: textDirection), side.toPaint());
}
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is _OutlineBorder
&& other.side == side
&& other.shape == shape;
}
@override
int get hashCode => Object.hash(side, shape);
@override
ShapeBorder resolve(Set<MaterialState> states) {
return _OutlineBorder(
shape: shape,
side: side.copyWith(color: MaterialStateProperty.resolveAs<Color>(side.color, states)),
);
}
}
......@@ -16,10 +16,10 @@ import 'theme_data.dart';
///
/// ### This class is deprecated, please use [ElevatedButton] instead.
///
/// FlatButton, RaisedButton, and OutlineButton have been replaced by
/// [TextButton], [ElevatedButton], and [OutlinedButton] respectively.
/// ButtonTheme has been replaced by [TextButtonTheme],
/// [ElevatedButtonTheme], and [OutlinedButtonTheme]. The original classes
/// FlatButton and RaisedButton have been replaced by
/// [TextButton] and [ElevatedButton] respectively.
/// ButtonTheme has been replaced by [TextButtonTheme] and
/// [ElevatedButtonTheme]. The original classes
/// will eventually be removed, please migrate code that uses them.
/// There's a detailed migration guide for the new button and button
/// theme classes in
......
......@@ -81,7 +81,6 @@ const Color _kDarkThemeSplashColor = Color(0x40CCCCCC);
/// * [OutlinedButton]
/// * [TextButton]
/// * [ElevatedButton]
/// * [OutlineButton]
/// * [FlatButton]
/// * [RaisedButton]
/// * The time picker widget ([showTimePicker])
......@@ -2469,7 +2468,6 @@ class _FifoCache<K, V> {
/// * [InputDecorator] (which gives density support to [TextField], etc.)
/// * [ListTile]
/// * [MaterialButton]
/// * [OutlineButton]
/// * [OutlinedButton]
/// * [Radio]
/// * [RawMaterialButton]
......
// Copyright 2014 The Flutter 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';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('OutlineButton defaults', (WidgetTester tester) async {
final Finder rawButtonMaterial = find.descendant(
of: find.byType(OutlineButton),
matching: find.byType(Material),
);
// Enabled OutlineButton
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
onPressed: () { },
child: const Text('button'),
),
),
);
Material material = tester.widget<Material>(rawButtonMaterial);
expect(material.animationDuration, const Duration(milliseconds: 75));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
expect(material.color, const Color(0x00000000));
expect(material.elevation, 0.0);
expect(material.shadowColor, null);
expect(material.textStyle!.color, const Color(0xdd000000));
expect(material.textStyle!.fontFamily, 'Roboto');
expect(material.textStyle!.fontSize, 14);
expect(material.textStyle!.fontWeight, FontWeight.w500);
expect(material.type, MaterialType.button);
final Offset center = tester.getCenter(find.byType(OutlineButton));
await tester.startGesture(center);
await tester.pumpAndSettle();
// No change vs enabled and not pressed.
material = tester.widget<Material>(rawButtonMaterial);
expect(material.animationDuration, const Duration(milliseconds: 75));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
expect(material.color, const Color(0x00000000));
expect(material.elevation, 0.0);
expect(material.shadowColor, null);
expect(material.textStyle!.color, const Color(0xdd000000));
expect(material.textStyle!.fontFamily, 'Roboto');
expect(material.textStyle!.fontSize, 14);
expect(material.textStyle!.fontWeight, FontWeight.w500);
expect(material.type, MaterialType.button);
// Disabled OutlineButton
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
onPressed: null,
child: Text('button'),
),
),
);
material = tester.widget<Material>(rawButtonMaterial);
expect(material.animationDuration, const Duration(milliseconds: 75));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
expect(material.color, const Color(0x00000000));
expect(material.elevation, 0.0);
expect(material.shadowColor, null);
expect(material.textStyle!.color, const Color(0x61000000));
expect(material.textStyle!.fontFamily, 'Roboto');
expect(material.textStyle!.fontSize, 14);
expect(material.textStyle!.fontWeight, FontWeight.w500);
expect(material.type, MaterialType.button);
});
testWidgets('Does OutlineButton work with hover', (WidgetTester tester) async {
const Color hoverColor = Color(0xff001122);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
hoverColor: hoverColor,
onPressed: () { },
child: const Text('button'),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(OutlineButton)));
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect(color: hoverColor));
await gesture.removePointer();
});
testWidgets('OutlineButton changes mouse cursor when hovered', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: OutlineButton.icon(
icon: const Icon(Icons.add),
label: const Text('Hello'),
onPressed: () {},
mouseCursor: SystemMouseCursors.text,
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: const Offset(1, 1));
addTearDown(gesture.removePointer);
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: OutlineButton(
onPressed: () {},
mouseCursor: SystemMouseCursors.text,
),
),
),
);
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
// Test default cursor
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: OutlineButton(
onPressed: () {},
),
),
),
);
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
// Test default cursor when disabled
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: MouseRegion(
cursor: SystemMouseCursors.forbidden,
child: OutlineButton(
onPressed: null,
),
),
),
);
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
});
testWidgets('Does OutlineButton work with focus', (WidgetTester tester) async {
const Color focusColor = Color(0xff001122);
final FocusNode focusNode = FocusNode(debugLabel: 'OutlineButton Node');
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
focusColor: focusColor,
focusNode: focusNode,
onPressed: () { },
child: const Text('button'),
),
),
);
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
focusNode.requestFocus();
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect(color: focusColor));
});
testWidgets('Does OutlineButton work with autofocus', (WidgetTester tester) async {
const Color focusColor = Color(0xff001122);
final FocusNode focusNode = FocusNode(debugLabel: 'OutlineButton Node');
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
autofocus: true,
focusColor: focusColor,
focusNode: focusNode,
onPressed: () { },
child: const Text('button'),
),
),
);
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
await tester.pumpAndSettle();
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
expect(inkFeatures, paints..rect(color: focusColor));
});
testWidgets('OutlineButton implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
OutlineButton(
onPressed: () {},
textColor: const Color(0xFF00FF00),
disabledTextColor: const Color(0xFFFF0000),
color: const Color(0xFF000000),
highlightColor: const Color(0xFF1565C0),
splashColor: const Color(0xFF9E9E9E),
child: const Text('Hello'),
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString()).toList();
expect(description, <String>[
'textColor: Color(0xff00ff00)',
'disabledTextColor: Color(0xffff0000)',
'color: Color(0xff000000)',
'highlightColor: Color(0xff1565c0)',
'splashColor: Color(0xff9e9e9e)',
]);
});
testWidgets('Default OutlineButton meets a11y contrast guidelines', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {},
focusNode: focusNode,
child: const Text('OutlineButton'),
),
),
),
),
);
// Default, not disabled.
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Hovered.
final Offset center = tester.getCenter(find.byType(OutlineButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
await expectLater(tester, meetsGuideline(textContrastGuideline));
},
skip: isBrowser, // https://github.com/flutter/flutter/issues/44115
);
testWidgets('OutlineButton with colored theme meets a11y contrast guidelines', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
final ColorScheme colorScheme = ColorScheme.fromSwatch();
Color getTextColor(Set<MaterialState> states) {
final Set<MaterialState> interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
};
if (states.any(interactiveStates.contains)) {
return Colors.blue[900]!;
}
return Colors.blue[800]!;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: ButtonTheme(
colorScheme: colorScheme,
textTheme: ButtonTextTheme.primary,
child: OutlineButton(
onPressed: () {},
focusNode: focusNode,
textColor: MaterialStateColor.resolveWith(getTextColor),
child: const Text('OutlineButton'),
),
),
),
),
),
);
// Default, not disabled.
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Hovered.
final Offset center = tester.getCenter(find.byType(OutlineButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
await expectLater(tester, meetsGuideline(textContrastGuideline));
},
skip: isBrowser, // https://github.com/flutter/flutter/issues/44115
);
testWidgets('OutlineButton uses stateful color for text color in different states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Color pressedColor = Color(0x00000001);
const Color hoverColor = Color(0x00000002);
const Color focusedColor = Color(0x00000003);
const Color defaultColor = Color(0x00000004);
Color getTextColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusedColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {},
focusNode: focusNode,
textColor: MaterialStateColor.resolveWith(getTextColor),
child: const Text('OutlineButton'),
),
),
),
),
);
Color? textColor() {
return tester.renderObject<RenderParagraph>(find.text('OutlineButton')).text.style!.color;
}
// Default, not disabled.
expect(textColor(), equals(defaultColor));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(textColor(), focusedColor);
// Hovered.
final Offset center = tester.getCenter(find.byType(OutlineButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(textColor(), hoverColor);
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
expect(textColor(), pressedColor);
});
testWidgets('OutlineButton uses stateful color for icon color in different states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
final Key buttonKey = UniqueKey();
const Color pressedColor = Color(0x00000001);
const Color hoverColor = Color(0x00000002);
const Color focusedColor = Color(0x00000003);
const Color defaultColor = Color(0x00000004);
Color getTextColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusedColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton.icon(
key: buttonKey,
icon: const Icon(Icons.add),
label: const Text('OutlineButton'),
onPressed: () {},
focusNode: focusNode,
textColor: MaterialStateColor.resolveWith(getTextColor),
),
),
),
),
);
Color? iconColor() => _iconStyle(tester, Icons.add).color;
// Default, not disabled.
expect(iconColor(), equals(defaultColor));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(iconColor(), focusedColor);
// Hovered.
final Offset center = tester.getCenter(find.byKey(buttonKey));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(iconColor(), hoverColor);
// Highlighted (pressed).
await gesture.down(center);
await tester.pump(); // Start the splash and highlight animations.
await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
expect(iconColor(), pressedColor);
});
testWidgets('OutlineButton ignores disabled text color if text color is stateful', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Color disabledColor = Color(0x00000001);
const Color defaultColor = Color(0x00000002);
const Color unusedDisabledTextColor = Color(0x00000003);
Color getTextColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: null,
focusNode: focusNode,
textColor: MaterialStateColor.resolveWith(getTextColor),
disabledTextColor: unusedDisabledTextColor,
child: const Text('OutlineButton'),
),
),
),
),
);
Color? textColor() {
return tester.renderObject<RenderParagraph>(find.text('OutlineButton')).text.style!.color;
}
// Disabled.
expect(textColor(), equals(disabledColor));
expect(textColor(), isNot(unusedDisabledTextColor));
});
testWidgets('OutlineButton uses stateful color for border color in different states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Color pressedColor = Color(0x00000001);
const Color hoverColor = Color(0x00000002);
const Color focusedColor = Color(0x00000003);
const Color defaultColor = Color(0x00000004);
Color getBorderColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusedColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {},
focusNode: focusNode,
borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)),
child: const Text('OutlineButton'),
),
),
),
),
);
final Finder outlineButton = find.byType(OutlineButton);
// Default, not disabled.
expect(outlineButton, paints..path(color: defaultColor));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(outlineButton, paints..path(color: focusedColor));
// Hovered.
final Offset center = tester.getCenter(find.byType(OutlineButton));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(outlineButton, paints..path(color: hoverColor));
// Highlighted (pressed).
await gesture.down(center);
await tester.pumpAndSettle();
expect(outlineButton, paints..path(color: pressedColor));
});
testWidgets('OutlineButton ignores highlightBorderColor if border color is stateful', (WidgetTester tester) async {
const Color pressedColor = Color(0x00000001);
const Color defaultColor = Color(0x00000002);
const Color ignoredPressedColor = Color(0x00000003);
Color getBorderColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {},
borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)),
highlightedBorderColor: ignoredPressedColor,
child: const Text('OutlineButton'),
),
),
),
),
);
final Finder outlineButton = find.byType(OutlineButton);
// Default, not disabled.
expect(outlineButton, paints..path(color: defaultColor));
// Highlighted (pressed).
await tester.press(outlineButton);
await tester.pumpAndSettle();
expect(outlineButton, paints..path(color: pressedColor));
});
testWidgets('OutlineButton ignores disabledBorderColor if border color is stateful', (WidgetTester tester) async {
const Color disabledColor = Color(0x00000001);
const Color defaultColor = Color(0x00000002);
const Color ignoredDisabledColor = Color(0x00000003);
Color getBorderColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: null,
borderSide: BorderSide(color: MaterialStateColor.resolveWith(getBorderColor)),
highlightedBorderColor: ignoredDisabledColor,
child: const Text('OutlineButton'),
),
),
),
),
);
// Disabled.
expect(find.byType(OutlineButton), paints..path(color: disabledColor));
});
testWidgets('OutlineButton onPressed and onLongPress callbacks are correctly called when non-null', (WidgetTester tester) async {
bool wasPressed;
Finder outlineButton;
Widget buildFrame({ VoidCallback? onPressed, VoidCallback? onLongPress }) {
return Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
onPressed: onPressed,
onLongPress: onLongPress,
child: const Text('button'),
),
);
}
// onPressed not null, onLongPress null.
wasPressed = false;
await tester.pumpWidget(
buildFrame(onPressed: () { wasPressed = true; }),
);
outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, true);
await tester.tap(outlineButton);
expect(wasPressed, true);
// onPressed null, onLongPress not null.
wasPressed = false;
await tester.pumpWidget(
buildFrame(onLongPress: () { wasPressed = true; }),
);
outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, true);
await tester.longPress(outlineButton);
expect(wasPressed, true);
// onPressed null, onLongPress null.
await tester.pumpWidget(
buildFrame(),
);
outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, false);
});
testWidgets("Outline button doesn't crash if disabled during a gesture", (WidgetTester tester) async {
Widget buildFrame(VoidCallback? onPressed) {
return Directionality(
textDirection: TextDirection.ltr,
child: Theme(
data: ThemeData(),
child: Center(
child: OutlineButton(onPressed: onPressed),
),
),
);
}
await tester.pumpWidget(buildFrame(() {}));
await tester.press(find.byType(OutlineButton));
await tester.pumpAndSettle();
await tester.pumpWidget(buildFrame(null));
await tester.pumpAndSettle();
});
testWidgets('OutlineButton shape and border component overrides', (WidgetTester tester) async {
const Color fillColor = Color(0xFF00FF00);
const Color borderColor = Color(0xFFFF0000);
const Color highlightedBorderColor = Color(0xFF0000FF);
const Color disabledBorderColor = Color(0xFFFF00FF);
const double borderWidth = 4.0;
Widget buildFrame({ VoidCallback? onPressed }) {
return Directionality(
textDirection: TextDirection.ltr,
child: Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Container(
alignment: Alignment.topLeft,
child: OutlineButton(
shape: const RoundedRectangleBorder(), // default border radius is 0
clipBehavior: Clip.antiAlias,
color: fillColor,
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
highlightedBorderColor: highlightedBorderColor,
disabledBorderColor: disabledBorderColor,
borderSide: const BorderSide(
width: borderWidth,
color: borderColor,
),
onPressed: onPressed,
child: const Text('button'),
),
),
),
);
}
const Rect clipRect = Rect.fromLTRB(0.0, 0.0, 116.0, 36.0);
final Path clipPath = Path()..addRect(clipRect);
final Finder outlineButton = find.byType(OutlineButton);
// Pump a button with a null onPressed callback to make it disabled.
await tester.pumpWidget(
buildFrame(),
);
// Expect that the button is disabled and painted with the disabled border color.
expect(tester.widget<OutlineButton>(outlineButton).enabled, false);
expect(
outlineButton,
paints..path(color: disabledBorderColor, strokeWidth: borderWidth),
);
_checkPhysicalLayer(
tester.element(outlineButton),
const Color(0x00000000),
clipPath: clipPath,
clipRect: clipRect,
);
// Pump a new button with a no-op onPressed callback to make it enabled.
await tester.pumpWidget(
buildFrame(onPressed: () {}),
);
// Wait for the border color to change from disabled to enabled.
await tester.pumpAndSettle();
// Expect that the button is enabled and painted with the enabled border color.
expect(tester.widget<OutlineButton>(outlineButton).enabled, true);
expect(
outlineButton,
paints..path(color: borderColor, strokeWidth: borderWidth),
);
// initially, the interior of the button is transparent
_checkPhysicalLayer(
tester.element(outlineButton),
fillColor.withAlpha(0x00),
clipPath: clipPath,
clipRect: clipRect,
);
final Offset center = tester.getCenter(outlineButton);
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
// Wait for the border's color to change to highlightedBorderColor and
// the fillColor to become opaque.
await tester.pump(const Duration(milliseconds: 200));
expect(
outlineButton,
paints..path(color: highlightedBorderColor, strokeWidth: borderWidth),
);
_checkPhysicalLayer(
tester.element(outlineButton),
fillColor.withAlpha(0xFF),
clipPath: clipPath,
clipRect: clipRect,
);
// Tap gesture completes, button returns to its initial configuration.
await gesture.up();
await tester.pumpAndSettle();
expect(
outlineButton,
paints..path(color: borderColor, strokeWidth: borderWidth),
);
_checkPhysicalLayer(
tester.element(outlineButton),
fillColor.withAlpha(0x00),
clipPath: clipPath,
clipRect: clipRect,
);
});
testWidgets('OutlineButton has no clip by default', (WidgetTester tester) async {
final GlobalKey buttonKey = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton(
key: buttonKey,
onPressed: () {},
child: const Text('ABC'),
),
),
),
);
expect(
tester.renderObject(find.byKey(buttonKey)),
paintsExactlyCountTimes(#clipPath, 0),
);
});
testWidgets('OutlineButton contributes semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton(
onPressed: () {},
child: const Text('ABC'),
),
),
),
);
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
actions: <SemanticsAction>[
SemanticsAction.tap,
],
label: 'ABC',
rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
transform: Matrix4.translationValues(356.0, 276.0, 0.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
),
],
),
ignoreId: true,
));
semantics.dispose();
});
testWidgets('OutlineButton scales textScaleFactor', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Center(
child: OutlineButton(
onPressed: () {},
child: const Text('ABC'),
),
),
),
),
);
expect(tester.getSize(find.byType(OutlineButton)), equals(const Size(88.0, 48.0)));
expect(tester.getSize(find.byType(Text)), equals(const Size(42.0, 14.0)));
// textScaleFactor expands text, but not button.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(textScaleFactor: 1.3),
child: Center(
child: FlatButton(
onPressed: () {},
child: const Text('ABC'),
),
),
),
),
);
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
// Scaled text rendering is different on Linux and Mac by one pixel.
// TODO(gspencergoog): Figure out why this is, and fix it. https://github.com/flutter/flutter/issues/12357
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[54.0, 55.0]));
expect(tester.getSize(find.byType(Text)).height, isIn(<double>[18.0, 19.0]));
// Set text scale large enough to expand text and button.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(textScaleFactor: 3.0),
child: Center(
child: FlatButton(
onPressed: () {},
child: const Text('ABC'),
),
),
),
),
);
// Scaled text rendering is different on Linux and Mac by one pixel.
// TODO(gspencergoog): Figure out why this is, and fix it. https://github.com/flutter/flutter/issues/12357
expect(tester.getSize(find.byType(FlatButton)).width, isIn(<double>[158.0, 159.0]));
expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0));
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[126.0, 127.0]));
expect(tester.getSize(find.byType(Text)).height, equals(42.0));
});
testWidgets('OutlineButton pressed fillColor default', (WidgetTester tester) async {
Widget buildFrame(ThemeData theme) {
return MaterialApp(
theme: theme,
home: Scaffold(
body: Center(
child: OutlineButton(
onPressed: () {},
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
child: const Text('Hello'),
),
),
),
);
}
await tester.pumpWidget(buildFrame(ThemeData.dark()));
final Finder button = find.byType(OutlineButton);
final Element buttonElement = tester.element(button);
final Offset center = tester.getCenter(button);
// Default value for dark Theme.of(context).canvasColor as well as
// the OutlineButton fill color when the button has been pressed.
Color fillColor = Colors.grey[850]!;
// Initially the interior of the button is transparent.
_checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00));
// Tap-press gesture on the button triggers the fill animation.
TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // Start the button fill animation.
await tester.pump(const Duration(milliseconds: 200)); // Animation is complete.
_checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF));
// Tap gesture completes, button returns to its initial configuration.
await gesture.up();
await tester.pumpAndSettle();
_checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00));
await tester.pumpWidget(buildFrame(ThemeData.light()));
await tester.pumpAndSettle(); // Finish the theme change animation.
// Default value for light Theme.of(context).canvasColor as well as
// the OutlineButton fill color when the button has been pressed.
fillColor = Colors.grey[50]!;
// Initially the interior of the button is transparent.
// expect(button, paints..path(color: fillColor.withAlpha(0x00)));
// Tap-press gesture on the button triggers the fill animation.
gesture = await tester.startGesture(center);
await tester.pump(); // Start the button fill animation.
await tester.pump(const Duration(milliseconds: 200)); // Animation is complete.
_checkPhysicalLayer(buttonElement, fillColor.withAlpha(0xFF));
// Tap gesture completes, button returns to its initial configuration.
await gesture.up();
await tester.pumpAndSettle();
_checkPhysicalLayer(buttonElement, fillColor.withAlpha(0x00));
});
testWidgets('OutlineButton respects the provided materialTapTargetSize', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton(
materialTapTargetSize: MaterialTapTargetSize.padded,
onPressed: () {},
child: const SizedBox(width: 50.0, height: 8.0),
),
),
),
);
// Default Width of OutlineButton with MaterialTapTargetSize (88)
expect(tester.getSize(find.byType(OutlineButton)), const Size(88.0, 48.0));
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {},
child: const SizedBox(width: 50.0, height: 8.0),
),
),
),
);
// Default Width of OutlineButton with MaterialTapTargetSize (88)
expect(tester.getSize(find.byType(OutlineButton)), const Size(88.0, 36.0));
final LocalKey key1 = UniqueKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton.icon(
key: key1,
materialTapTargetSize: MaterialTapTargetSize.padded,
icon: const Icon(Icons.add_alarm),
label: const SizedBox(width: 50.0, height: 8.0),
onPressed: () { },
),
),
),
);
final Size addAlarmIconSize = tester.getSize(find.byIcon(Icons.add_alarm));
// The expected width is the sum of:
// the width of the icon
// the gap between the icon and the label (8)
// the width of the label (50)
// the horizontal padding: start (12), end (16)
expect(tester.getSize(find.byKey(key1)), Size(86 + addAlarmIconSize.width, 48.0));
final LocalKey key2 = UniqueKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: OutlineButton.icon(
key: key2,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
icon: const Icon(Icons.add),
label: const SizedBox(width: 50.0, height: 8.0),
onPressed: () { },
),
),
),
);
// The expected width is the sum of:
// the width of the icon
// the gap between the icon and the label (8)
// the width of the label (50)
// the horizontal padding: start (12), end (16)
final Size addIconSize = tester.getSize(find.byIcon(Icons.add));
expect(tester.getSize(find.byKey(key2)), Size(86 + addIconSize.width, 36.0));
});
testWidgets('OutlineButton onPressed and onLongPress callbacks are distinctly recognized', (WidgetTester tester) async {
bool didPressButton = false;
bool didLongPressButton = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: OutlineButton(
onPressed: () {
didPressButton = true;
},
onLongPress: () {
didLongPressButton = true;
},
child: const Text('button'),
),
),
);
final Finder outlineButton = find.byType(OutlineButton);
expect(tester.widget<OutlineButton>(outlineButton).enabled, true);
expect(didPressButton, isFalse);
await tester.tap(outlineButton);
expect(didPressButton, isTrue);
expect(didLongPressButton, isFalse);
await tester.longPress(outlineButton);
expect(didLongPressButton, isTrue);
});
testWidgets('OutlineButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
const Key childKey = Key('test child');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: OutlineButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text', key: childKey) : Container(key: childKey, width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(VisualDensity.standard);
final RenderBox box = tester.renderObject(find.byKey(key));
Rect childRect = tester.getRect(find.byKey(childKey));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(132, 100)));
expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
childRect = tester.getRect(find.byKey(childKey));
expect(box.size, equals(const Size(156, 124)));
expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
childRect = tester.getRect(find.byKey(childKey));
expect(box.size, equals(const Size(108, 100)));
expect(childRect, equals(const Rect.fromLTRB(350, 250, 450, 350)));
await buildTest(VisualDensity.standard, useText: true);
await tester.pumpAndSettle();
childRect = tester.getRect(find.byKey(childKey));
expect(box.size, equals(const Size(88, 48)));
expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
childRect = tester.getRect(find.byKey(childKey));
expect(box.size, equals(const Size(112, 60)));
expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
childRect = tester.getRect(find.byKey(childKey));
expect(box.size, equals(const Size(76, 36)));
expect(childRect, equals(const Rect.fromLTRB(372.0, 293.0, 428.0, 307.0)));
});
}
PhysicalModelLayer _findPhysicalLayer(Element element) {
expect(element, isNotNull);
RenderObject? object = element.renderObject;
while (object != null && object is! RenderRepaintBoundary && object is! RenderView) {
object = object.parent as RenderObject?;
}
assert(object != null);
expect(object!.debugLayer, isNotNull);
expect(object.debugLayer!.firstChild, isA<PhysicalModelLayer>());
final PhysicalModelLayer layer = object.debugLayer!.firstChild! as PhysicalModelLayer;
final Layer child = layer.firstChild!;
return child is PhysicalModelLayer ? child : layer;
}
void _checkPhysicalLayer(Element element, Color expectedColor, { Path? clipPath, Rect? clipRect }) {
final PhysicalModelLayer expectedLayer = _findPhysicalLayer(element);
expect(expectedLayer.elevation, 0.0);
expect(expectedLayer.color, expectedColor);
if (clipPath != null) {
expect(clipRect, isNotNull);
expect(expectedLayer.clipPath, coversSameAreaAs(clipPath, areaToCompare: clipRect!.inflate(10.0)));
}
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
final RichText iconRichText = tester.widget<RichText>(
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
);
return iconRichText.text.style!;
}
......@@ -127,93 +127,6 @@ void main() {
});
});
group('OutlineButton', () {
testWidgets('theme: ThemeData.light(), enabled: true, highlightElevation: 2.0', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Center(
child: OutlineButton(
onPressed: () { }, // button.enabled == true
// Causes the button to be filled with the theme's canvasColor
// instead of Colors.transparent before the button material's
// elevation is animated to 2.0.
highlightElevation: 2.0,
child: const Text('button'),
),
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle!.color, const Color(0xdd000000));
expect(raw.fillColor, const Color(0x00fafafa));
expect(raw.highlightColor, const Color(0x29000000)); // Was Color(0x66bcbcbc)
expect(raw.splashColor, const Color(0x1f000000)); // Was Color(0x66c8c8c8)
expect(raw.elevation, 0.0);
expect(raw.highlightElevation, 0.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
// animationDuration can't be configured by the theme/constructor
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: Center(
child: OutlineButton(
onPressed: () { }, // button.enabled == true
child: const Text('button'),
),
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle!.color, const Color(0xdd000000));
expect(raw.fillColor, Colors.transparent);
expect(raw.highlightColor, const Color(0x29000000)); // Was Color(0x66bcbcbc)
expect(raw.splashColor, const Color(0x1f000000)); // Was Color(0x66c8c8c8)
expect(raw.elevation, 0.0);
expect(raw.highlightElevation, 0.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
// animationDuration can't be configured by the theme/constructor
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(),
home: const Center(
child: OutlineButton(
onPressed: null, // button.enabled == false
child: Text('button'),
),
),
),
);
final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton));
expect(raw.textStyle!.color, const Color(0x61000000));
expect(raw.fillColor, const Color(0x00000000));
// highlightColor, disabled button can't be pressed
// splashColor, disabled button doesn't splash
expect(raw.elevation, 0.0);
expect(raw.highlightElevation, 0.0);
expect(raw.disabledElevation, 0.0);
expect(raw.constraints, defaultButtonConstraints);
expect(raw.padding, defaultButtonPadding);
// animationDuration can't be configured by the theme/constructor
expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded);
});
});
group('FloatingActionButton', () {
const BoxConstraints defaultFABConstraints = BoxConstraints.tightFor(width: 56.0, height: 56.0);
const ShapeBorder defaultFABShape = CircleBorder();
......
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