Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
dc31d89c
Unverified
Commit
dc31d89c
authored
Jul 09, 2020
by
Hans Muller
Committed by
GitHub
Jul 09, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
New Button Universe (#59702)
parent
ea777fea
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
6334 additions
and
1 deletion
+6334
-1
material.dart
packages/flutter/lib/material.dart
+8
-0
button_style.dart
packages/flutter/lib/src/material/button_style.dart
+439
-0
button_style_button.dart
packages/flutter/lib/src/material/button_style_button.dart
+470
-0
contained_button.dart
packages/flutter/lib/src/material/contained_button.dart
+425
-0
contained_button_theme.dart
...ages/flutter/lib/src/material/contained_button_theme.dart
+127
-0
material_state.dart
packages/flutter/lib/src/material/material_state.dart
+3
-0
outlined_button.dart
packages/flutter/lib/src/material/outlined_button.dart
+349
-0
outlined_button_theme.dart
packages/flutter/lib/src/material/outlined_button_theme.dart
+127
-0
text_button.dart
packages/flutter/lib/src/material/text_button.dart
+387
-0
text_button_theme.dart
packages/flutter/lib/src/material/text_button_theme.dart
+127
-0
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+48
-1
button_style_test.dart
packages/flutter/test/material/button_style_test.dart
+153
-0
contained_button_test.dart
packages/flutter/test/material/contained_button_test.dart
+930
-0
contained_button_theme_test.dart
...es/flutter/test/material/contained_button_theme_test.dart
+182
-0
outlined_button_test.dart
packages/flutter/test/material/outlined_button_test.dart
+1204
-0
outlined_button_theme_test.dart
...ges/flutter/test/material/outlined_button_theme_test.dart
+183
-0
text_button_test.dart
packages/flutter/test/material/text_button_test.dart
+980
-0
text_button_theme_test.dart
packages/flutter/test/material/text_button_theme_test.dart
+180
-0
theme_data_test.dart
packages/flutter/test/material/theme_data_test.dart
+12
-0
No files found.
packages/flutter/lib/material.dart
View file @
dc31d89c
...
@@ -36,6 +36,8 @@ export 'src/material/bottom_sheet_theme.dart';
...
@@ -36,6 +36,8 @@ export 'src/material/bottom_sheet_theme.dart';
export
'src/material/button.dart'
;
export
'src/material/button.dart'
;
export
'src/material/button_bar.dart'
;
export
'src/material/button_bar.dart'
;
export
'src/material/button_bar_theme.dart'
;
export
'src/material/button_bar_theme.dart'
;
export
'src/material/button_style.dart'
;
export
'src/material/button_style_button.dart'
;
export
'src/material/button_theme.dart'
;
export
'src/material/button_theme.dart'
;
export
'src/material/card.dart'
;
export
'src/material/card.dart'
;
export
'src/material/card_theme.dart'
;
export
'src/material/card_theme.dart'
;
...
@@ -47,6 +49,8 @@ export 'src/material/circle_avatar.dart';
...
@@ -47,6 +49,8 @@ export 'src/material/circle_avatar.dart';
export
'src/material/color_scheme.dart'
;
export
'src/material/color_scheme.dart'
;
export
'src/material/colors.dart'
;
export
'src/material/colors.dart'
;
export
'src/material/constants.dart'
;
export
'src/material/constants.dart'
;
export
'src/material/contained_button.dart'
;
export
'src/material/contained_button_theme.dart'
;
export
'src/material/data_table.dart'
;
export
'src/material/data_table.dart'
;
export
'src/material/data_table_source.dart'
;
export
'src/material/data_table_source.dart'
;
export
'src/material/debug.dart'
;
export
'src/material/debug.dart'
;
...
@@ -88,6 +92,8 @@ export 'src/material/mergeable_material.dart';
...
@@ -88,6 +92,8 @@ export 'src/material/mergeable_material.dart';
export
'src/material/navigation_rail.dart'
;
export
'src/material/navigation_rail.dart'
;
export
'src/material/navigation_rail_theme.dart'
;
export
'src/material/navigation_rail_theme.dart'
;
export
'src/material/outline_button.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'
;
export
'src/material/page.dart'
;
export
'src/material/page_transitions_theme.dart'
;
export
'src/material/page_transitions_theme.dart'
;
export
'src/material/paginated_data_table.dart'
;
export
'src/material/paginated_data_table.dart'
;
...
@@ -117,6 +123,8 @@ export 'src/material/tab_bar_theme.dart';
...
@@ -117,6 +123,8 @@ export 'src/material/tab_bar_theme.dart';
export
'src/material/tab_controller.dart'
;
export
'src/material/tab_controller.dart'
;
export
'src/material/tab_indicator.dart'
;
export
'src/material/tab_indicator.dart'
;
export
'src/material/tabs.dart'
;
export
'src/material/tabs.dart'
;
export
'src/material/text_button.dart'
;
export
'src/material/text_button_theme.dart'
;
export
'src/material/text_field.dart'
;
export
'src/material/text_field.dart'
;
export
'src/material/text_form_field.dart'
;
export
'src/material/text_form_field.dart'
;
export
'src/material/text_selection.dart'
;
export
'src/material/text_selection.dart'
;
...
...
packages/flutter/lib/src/material/button_style.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'material_state.dart'
;
import
'theme_data.dart'
;
/// The visual properties that most buttons have in common.
///
/// Buttons and their themes have a ButtonStyle property which defines the visual
/// properties whose default values are to be overidden. The default values are
/// defined by the invidual button widgets and are typically based on overall
/// theme's [ThemeData.colorScheme] and [ThemeData.textTheme].
///
/// All of the ButtonStyle properties are null by default.
///
/// Many of the ButtonStyle properties are [MaterialStateProperty] objects which
/// resolve to different values depending on the button's state. For example
/// the [Color] properties are defined with `MaterialStateProperty<Color>` and
/// can resolve to different colors depending on if the button is pressed,
/// hovered, focused, disabled, etc.
///
/// These properties can override the default value for just one state or all of
/// them. For example to create a [ContainedButton] whose background color is the
/// color scheme’s primary color with 50% opacity, but only when the button is
/// pressed, one could write:
///
/// ```dart
/// ContainedButton(
/// style: ButtonStyle(
/// backgroundColor: MaterialStateProperty.resolveWith<Color>(
/// (Set<MaterialState> states) {
/// if (states.contains(MaterialState.pressed))
/// return Theme.of(context).colorScheme.primary.withOpacity(0.5);
/// return null; // Use the component's default.
/// },
/// ),
/// ),
/// )
///```
///
/// In this case the background color for all other button states would fallback
/// to the ContainedButton’s default values. To unconditionally set the button's
/// [backgroundColor] for all states one could write:
///
/// ```dart
/// ContainedButton(
/// style: ButtonStyle(
/// backgroundColor: MaterialStateProperty.all<Color>(Colors.green),
/// ),
/// )
///```
///
/// Configuring a ButtonStyle directly makes it possible to very
/// precisely control the button’s visual attributes for all states.
/// This level of control is typically required when a custom
/// “branded” look and feel is desirable. However, in many cases it’s
/// useful to make relatively sweeping changes based on a few initial
/// parameters with simple values. The button styleFrom() methods
/// enable such sweeping changes. See for example:
/// [TextButton.styleFrom], [ContainedButton.styleFrom],
/// [OutlinedButton.styleFrom].
///
/// For example, to override the default text and icon colors for a
/// [TextButton], as well as its overlay color, with all of the
/// standard opacity adjustments for the pressed, focused, and
/// hovered states, one could write:
///
/// ```dart
/// TextButton(
/// style: TextButton.styleFrom(primary: Colors.green),
/// )
///```
///
/// To configure all of the application's text buttons in the same
/// way, specify the overall theme's `textButtonTheme`:
/// ```dart
/// MaterialApp(
/// theme: ThemeData(
/// textButtonTheme: TextButtonThemeData(
/// style: TextButton.styleFrom(primary: Colors.green),
/// ),
/// ),
/// home: MyAppHome(),
/// )
///```
/// See also:
///
/// * [TextButtonTheme], the theme for [TextButton]s.
/// * [ContainedButtonTheme], the theme for [ContainedButton]s.
/// * [OutlinedButtonTheme], the theme for [OutlinedButton]s.
@immutable
class
ButtonStyle
with
Diagnosticable
{
/// Create a [ButtonStyle].
const
ButtonStyle
({
this
.
textStyle
,
this
.
backgroundColor
,
this
.
foregroundColor
,
this
.
overlayColor
,
this
.
shadowColor
,
this
.
elevation
,
this
.
padding
,
this
.
minimumSize
,
this
.
side
,
this
.
shape
,
this
.
mouseCursor
,
this
.
visualDensity
,
this
.
tapTargetSize
,
this
.
animationDuration
,
this
.
enableFeedback
,
});
/// The style for a button's [Text] widget descendants.
///
/// The color of the [textStyle] is typically not used directly, the
/// [foreground] color is used instead.
final
MaterialStateProperty
<
TextStyle
>
textStyle
;
/// The button's background fill color.
final
MaterialStateProperty
<
Color
>
backgroundColor
;
/// The color for the button's [Text] and [Icon] widget descendants.
///
/// This color is typically used instead of the color of the [textStyle]. All
/// of the components that compute defaults from [ButtonStyle] values
/// compute a default [foregroundColor] and use that instead of the
/// [textStyle]'s color.
final
MaterialStateProperty
<
Color
>
foregroundColor
;
/// The highlight color that's typically used to indicate that
/// the button is focused, hovered, or pressed.
final
MaterialStateProperty
<
Color
>
overlayColor
;
/// The shadow color of the button's [Material].
///
/// The material's elevation shadow can be difficult to see for
/// dark themes, so by default the button classes add a
/// semi-transparent overlay to indicate elevation. See
/// [ThemeData.applyElevationOverlayColor].
final
MaterialStateProperty
<
Color
>
shadowColor
;
/// The elevation of the button's [Material].
final
MaterialStateProperty
<
double
>
elevation
;
/// The padding between the button's boundary and its child.
final
MaterialStateProperty
<
EdgeInsetsGeometry
>
padding
;
/// The minimum size of the button itself.
///
/// The size of the rectangle the button lies within may be larger
/// per [tapTargetSize].
final
MaterialStateProperty
<
Size
>
minimumSize
;
/// The color and weight of the button's outline.
///
/// This value is combined with [shape] to create a shape decorated
/// with an outline.
final
MaterialStateProperty
<
BorderSide
>
side
;
/// The shape of the button's underlying [Material].
///
/// This shape is combined with [side] to create a shape decorated
/// with an outline.
final
MaterialStateProperty
<
OutlinedBorder
>
shape
;
/// The cursor for a mouse pointer when it enters or is hovering over
/// this button's [InkWell].
final
MaterialStateProperty
<
MouseCursor
>
mouseCursor
;
/// Defines how compact the button's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
/// within a [Theme].
final
VisualDensity
visualDensity
;
/// Configures the minimum size of the area within which the button may be pressed.
///
/// If the [tapTargetSize] is larger than [minimumSize], the button will include
/// a transparent margin that responds to taps.
///
/// Always defaults to [ThemeData.materialTapTargetSize].
final
MaterialTapTargetSize
tapTargetSize
;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// Typically the component default value is [kThemeChangeDuration].
final
Duration
animationDuration
;
/// Whether detected gestures should provide acoustic and/or haptic feedback.
///
/// For example, on Android a tap will produce a clicking sound and a
/// long-press will produce a short vibration, when feedback is enabled.
///
/// Typically the component default value is true.
///
/// See also:
///
/// * [Feedback] for providing platform-specific feedback to certain actions.
final
bool
enableFeedback
;
/// Returns a copy of this ButtonStyle with the given fields replaced with
/// the new values.
ButtonStyle
copyWith
({
MaterialStateProperty
<
TextStyle
>
textStyle
,
MaterialStateProperty
<
Color
>
backgroundColor
,
MaterialStateProperty
<
Color
>
foregroundColor
,
MaterialStateProperty
<
Color
>
overlayColor
,
MaterialStateProperty
<
Color
>
shadowColor
,
MaterialStateProperty
<
double
>
elevation
,
MaterialStateProperty
<
EdgeInsetsGeometry
>
padding
,
MaterialStateProperty
<
Size
>
minimumSize
,
MaterialStateProperty
<
BorderSide
>
side
,
MaterialStateProperty
<
OutlinedBorder
>
shape
,
MaterialStateProperty
<
MouseCursor
>
mouseCursor
,
VisualDensity
visualDensity
,
MaterialTapTargetSize
tapTargetSize
,
Duration
animationDuration
,
bool
enableFeedback
,
})
{
return
ButtonStyle
(
textStyle:
textStyle
??
this
.
textStyle
,
backgroundColor:
backgroundColor
??
this
.
backgroundColor
,
foregroundColor:
foregroundColor
??
this
.
foregroundColor
,
overlayColor:
overlayColor
??
this
.
overlayColor
,
shadowColor:
shadowColor
??
this
.
shadowColor
,
elevation:
elevation
??
this
.
elevation
,
padding:
padding
??
this
.
padding
,
minimumSize:
minimumSize
??
this
.
minimumSize
,
side:
side
??
this
.
side
,
shape:
shape
??
this
.
shape
,
mouseCursor:
mouseCursor
??
this
.
mouseCursor
,
visualDensity:
visualDensity
??
this
.
visualDensity
,
tapTargetSize:
tapTargetSize
??
this
.
tapTargetSize
,
animationDuration:
animationDuration
??
this
.
animationDuration
,
enableFeedback:
enableFeedback
??
this
.
enableFeedback
,
);
}
/// Returns a copy of this ButtonStyle where the non-null fields in [style]
/// have replaced the corresponding null fields in this ButtonStyle.
///
/// In other words, [style] is used to fill in unspecified (null) fields
/// this ButtonStyle.
ButtonStyle
merge
(
ButtonStyle
style
)
{
if
(
style
==
null
)
return
this
;
return
copyWith
(
textStyle:
textStyle
??
style
.
textStyle
,
backgroundColor:
backgroundColor
??
style
.
backgroundColor
,
foregroundColor:
foregroundColor
??
style
.
foregroundColor
,
overlayColor:
overlayColor
??
style
.
overlayColor
,
shadowColor:
shadowColor
??
style
.
shadowColor
,
elevation:
elevation
??
style
.
elevation
,
padding:
padding
??
style
.
padding
,
minimumSize:
minimumSize
??
style
.
minimumSize
,
side:
side
??
style
.
side
,
shape:
shape
??
style
.
shape
,
mouseCursor:
mouseCursor
??
style
.
mouseCursor
,
visualDensity:
visualDensity
??
style
.
visualDensity
,
tapTargetSize:
tapTargetSize
??
style
.
tapTargetSize
,
animationDuration:
animationDuration
??
style
.
animationDuration
,
enableFeedback:
enableFeedback
??
style
.
enableFeedback
,
);
}
@override
int
get
hashCode
{
return
hashValues
(
textStyle
,
backgroundColor
,
foregroundColor
,
overlayColor
,
shadowColor
,
elevation
,
padding
,
minimumSize
,
side
,
shape
,
mouseCursor
,
visualDensity
,
tapTargetSize
,
animationDuration
,
enableFeedback
,
);
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
ButtonStyle
&&
other
.
textStyle
==
textStyle
&&
other
.
backgroundColor
==
backgroundColor
&&
other
.
foregroundColor
==
foregroundColor
&&
other
.
overlayColor
==
overlayColor
&&
other
.
shadowColor
==
shadowColor
&&
other
.
elevation
==
elevation
&&
other
.
padding
==
padding
&&
other
.
minimumSize
==
minimumSize
&&
other
.
side
==
side
&&
other
.
shape
==
shape
&&
other
.
mouseCursor
==
mouseCursor
&&
other
.
visualDensity
==
visualDensity
&&
other
.
tapTargetSize
==
tapTargetSize
&&
other
.
animationDuration
==
animationDuration
&&
other
.
enableFeedback
==
enableFeedback
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
TextStyle
>>(
'textStyle'
,
textStyle
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
Color
>>(
'backgroundColor'
,
backgroundColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
Color
>>(
'foregroundColor'
,
foregroundColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
Color
>>(
'overlayColor'
,
overlayColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
Color
>>(
'shadowColor'
,
shadowColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
double
>>(
'elevation'
,
elevation
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
EdgeInsetsGeometry
>>(
'padding'
,
padding
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
Size
>>(
'minimumSize'
,
minimumSize
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
BorderSide
>>(
'side'
,
side
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
OutlinedBorder
>>(
'shape'
,
shape
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
MouseCursor
>>(
'mouseCursor'
,
mouseCursor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
VisualDensity
>(
'visualDensity'
,
visualDensity
,
defaultValue:
null
));
properties
.
add
(
EnumProperty
<
MaterialTapTargetSize
>(
'tapTargetSize'
,
tapTargetSize
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
Duration
>(
'animationDuration'
,
animationDuration
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'enableFeedback'
,
enableFeedback
,
defaultValue:
null
));
}
/// Linearly interpolate between two [ButtonStyle]s.
static
ButtonStyle
lerp
(
ButtonStyle
a
,
ButtonStyle
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
ButtonStyle
(
textStyle:
_lerpProperties
<
TextStyle
>(
a
?.
textStyle
,
b
?.
textStyle
,
t
,
TextStyle
.
lerp
),
backgroundColor:
_lerpProperties
<
Color
>(
a
?.
backgroundColor
,
b
?.
backgroundColor
,
t
,
Color
.
lerp
),
foregroundColor:
_lerpProperties
<
Color
>(
a
?.
foregroundColor
,
b
?.
foregroundColor
,
t
,
Color
.
lerp
),
overlayColor:
_lerpProperties
<
Color
>(
a
?.
overlayColor
,
b
?.
overlayColor
,
t
,
Color
.
lerp
),
shadowColor:
_lerpProperties
<
Color
>(
a
?.
shadowColor
,
b
?.
shadowColor
,
t
,
Color
.
lerp
),
elevation:
_lerpProperties
<
double
>(
a
?.
elevation
,
b
?.
elevation
,
t
,
lerpDouble
),
padding:
_lerpProperties
<
EdgeInsetsGeometry
>(
a
?.
padding
,
b
?.
padding
,
t
,
EdgeInsetsGeometry
.
lerp
),
minimumSize:
_lerpProperties
<
Size
>(
a
?.
minimumSize
,
b
?.
minimumSize
,
t
,
Size
.
lerp
),
side:
_lerpSides
(
a
?.
side
,
b
?.
side
,
t
),
shape:
_lerpShapes
(
a
?.
shape
,
b
?.
shape
,
t
),
mouseCursor:
t
<
0.5
?
a
.
mouseCursor
:
b
.
mouseCursor
,
visualDensity:
t
<
0.5
?
a
.
visualDensity
:
b
.
visualDensity
,
tapTargetSize:
t
<
0.5
?
a
.
tapTargetSize
:
b
.
tapTargetSize
,
animationDuration:
t
<
0.5
?
a
.
animationDuration
:
b
.
animationDuration
,
enableFeedback:
t
<
0.5
?
a
.
enableFeedback
:
b
.
enableFeedback
,
);
}
static
MaterialStateProperty
<
T
>
_lerpProperties
<
T
>(
MaterialStateProperty
<
T
>
a
,
MaterialStateProperty
<
T
>
b
,
double
t
,
T
Function
(
T
,
T
,
double
)
lerpFunction
)
{
// Avoid creating a _LerpProperties object for a common case.
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
_LerpProperties
<
T
>(
a
,
b
,
t
,
lerpFunction
);
}
// Special case because BorderSide.lerp() doesn't support null arguments
static
MaterialStateProperty
<
BorderSide
>
_lerpSides
(
MaterialStateProperty
<
BorderSide
>
a
,
MaterialStateProperty
<
BorderSide
>
b
,
double
t
)
{
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
_LerpSides
(
a
,
b
,
t
);
}
// TODO(hansmuller): OutlinedBorder needs a lerp method - https://github.com/flutter/flutter/issues/60555.
static
MaterialStateProperty
<
OutlinedBorder
>
_lerpShapes
(
MaterialStateProperty
<
OutlinedBorder
>
a
,
MaterialStateProperty
<
OutlinedBorder
>
b
,
double
t
)
{
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
_LerpShapes
(
a
,
b
,
t
);
}
}
class
_LerpProperties
<
T
>
implements
MaterialStateProperty
<
T
>
{
const
_LerpProperties
(
this
.
a
,
this
.
b
,
this
.
t
,
this
.
lerpFunction
);
final
MaterialStateProperty
<
T
>
a
;
final
MaterialStateProperty
<
T
>
b
;
final
double
t
;
final
T
Function
(
T
,
T
,
double
)
lerpFunction
;
@override
T
resolve
(
Set
<
MaterialState
>
states
)
{
final
T
resolvedA
=
a
?.
resolve
(
states
);
final
T
resolvedB
=
b
?.
resolve
(
states
);
return
lerpFunction
(
resolvedA
,
resolvedB
,
t
);
}
}
class
_LerpSides
implements
MaterialStateProperty
<
BorderSide
>
{
const
_LerpSides
(
this
.
a
,
this
.
b
,
this
.
t
);
final
MaterialStateProperty
<
BorderSide
>
a
;
final
MaterialStateProperty
<
BorderSide
>
b
;
final
double
t
;
@override
BorderSide
resolve
(
Set
<
MaterialState
>
states
)
{
final
BorderSide
resolvedA
=
a
?.
resolve
(
states
);
final
BorderSide
resolvedB
=
b
?.
resolve
(
states
);
if
(
resolvedA
==
null
&&
resolvedB
==
null
)
return
null
;
if
(
resolvedA
==
null
)
return
BorderSide
.
lerp
(
BorderSide
(
width:
0
,
color:
resolvedB
.
color
.
withAlpha
(
0
)),
resolvedB
,
t
);
if
(
resolvedB
==
null
)
return
BorderSide
.
lerp
(
BorderSide
(
width:
0
,
color:
resolvedA
.
color
.
withAlpha
(
0
)),
resolvedA
,
t
);
return
BorderSide
.
lerp
(
resolvedA
,
resolvedB
,
t
);
}
}
class
_LerpShapes
implements
MaterialStateProperty
<
OutlinedBorder
>
{
const
_LerpShapes
(
this
.
a
,
this
.
b
,
this
.
t
);
final
MaterialStateProperty
<
OutlinedBorder
>
a
;
final
MaterialStateProperty
<
OutlinedBorder
>
b
;
final
double
t
;
@override
OutlinedBorder
resolve
(
Set
<
MaterialState
>
states
)
{
final
OutlinedBorder
resolvedA
=
a
?.
resolve
(
states
);
final
OutlinedBorder
resolvedB
=
b
?.
resolve
(
states
);
return
ShapeBorder
.
lerp
(
resolvedA
,
resolvedB
,
t
)
as
OutlinedBorder
;
}
}
packages/flutter/lib/src/material/button_style_button.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'dart:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'ink_ripple.dart'
;
import
'ink_well.dart'
;
import
'material.dart'
;
import
'material_state.dart'
;
import
'theme_data.dart'
;
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
///
/// Concrete subclasses must override [defaultStyleOf] and [themeStyleOf].
///
/// See also:
///
/// * [TextButton], a simple ButtonStyleButton without a shadow.
/// * [ContainedButton], a filled ButtonStyleButton whose material elevates when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline.
abstract
class
ButtonStyleButton
extends
StatefulWidget
{
/// Create a [ButtonStyleButton].
const
ButtonStyleButton
({
Key
key
,
@required
this
.
onPressed
,
@required
this
.
onLongPress
,
@required
this
.
style
,
@required
this
.
focusNode
,
@required
this
.
autofocus
,
@required
this
.
clipBehavior
,
@required
this
.
child
,
})
:
assert
(
autofocus
!=
null
),
assert
(
clipBehavior
!=
null
),
super
(
key:
key
);
/// Called when the button is tapped or otherwise activated.
///
/// If this callback and [onLongPress] are null, then the button will be disabled.
///
/// See also:
///
/// * [enabled], which is true if the button is enabled.
final
VoidCallback
onPressed
;
/// Called when the button is long-pressed.
///
/// If this callback and [onPressed] are null, then the button will be disabled.
///
/// See also:
///
/// * [enabled], which is true if the button is enabled.
final
VoidCallback
onLongPress
;
/// Customizes this button's appearance.
///
/// Non-null properties of this style override the corresponding
/// properties in [themeStyleOf] and [defaultStyleOf]. [MaterialStateProperty]s
/// that resolve to non-null values will similarly override the corresponding
/// [MaterialStateProperty]s in [themeStyleOf] and [defaultStyleOf].
///
/// Null by default.
final
ButtonStyle
style
;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final
Clip
clipBehavior
;
/// {@macro flutter.widgets.Focus.focusNode}
final
FocusNode
focusNode
;
/// {@macro flutter.widgets.Focus.autofocus}
final
bool
autofocus
;
/// Typically the button's label.
final
Widget
child
;
/// Returns a non-null [ButtonStyle] that's based primarily on the [Theme]'s
/// [ThemeData.textTheme] and [ThemeData.colorScheme].
///
/// The returned style can be overriden by the [style] parameter and
/// by the style returned by [themeStyleOf]. For example the default
/// style of the [TextButton] subclass can be overidden with its
/// [TextButton.style] constructor parameter, or with a
/// [TextButtonTheme].
///
/// Concrete button subclasses should return a ButtonStyle that
/// has no null properties, and where all of the [MaterialStateProperty]
/// properties resolve to non-null values.
///
/// See also:
///
/// * [themeStyleOf], Returns the ButtonStyle of this button's component theme.
@protected
ButtonStyle
defaultStyleOf
(
BuildContext
context
);
/// Returns the ButtonStyle that belongs to the button's component theme.
///
/// The returned style can be overriden by the [style] parameter.
///
/// Concrete button subclasses should return the ButtonStyle for the
/// nearest subclass-specific inherited theme, and if no such theme
/// exists, then the same value from the overall [Theme].
///
/// See also:
///
/// * [defaultStyleOf], Returns the default [ButtonStyle] for this button.
@protected
ButtonStyle
themeStyleOf
(
BuildContext
context
);
/// Whether the button is enabled or disabled.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// or [onLongPress] properties to a non-null value.
bool
get
enabled
=>
onPressed
!=
null
||
onLongPress
!=
null
;
@override
_ButtonStyleState
createState
()
=>
_ButtonStyleState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
FlagProperty
(
'enabled'
,
value:
enabled
,
ifFalse:
'disabled'
));
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
FocusNode
>(
'focusNode'
,
focusNode
,
defaultValue:
null
));
}
/// Returns null if [value] is null, otherwise `MaterialStateProperty.all<T>(value)`.
///
/// A convenience method for subclasses.
static
MaterialStateProperty
<
T
>
allOrNull
<
T
>(
T
value
)
=>
value
==
null
?
null
:
MaterialStateProperty
.
all
<
T
>(
value
);
/// Returns an interpolated value based on the [textScaleFactor] parameter:
///
/// * 0 - 1 [geometry1x]
/// * 1 - 2 lerp([geometry1x], [geometry2x], [textScaleFactor] - 1)
/// * 2 - 3 lerp([geometry2x], [geometry3x], [textScaleFactor] - 2)
/// * otherwise [geometry3x]
///
/// A convenience method for subclasses.
static
EdgeInsetsGeometry
scaledPadding
(
EdgeInsetsGeometry
geometry1x
,
EdgeInsetsGeometry
geometry2x
,
EdgeInsetsGeometry
geometry3x
,
double
textScaleFactor
,
)
{
assert
(
geometry1x
!=
null
);
assert
(
geometry2x
!=
null
);
assert
(
geometry3x
!=
null
);
assert
(
textScaleFactor
!=
null
);
if
(
textScaleFactor
<=
1
)
{
return
geometry1x
;
}
else
if
(
textScaleFactor
>=
3
)
{
return
geometry3x
;
}
else
if
(
textScaleFactor
<=
2
)
{
return
EdgeInsetsGeometry
.
lerp
(
geometry1x
,
geometry2x
,
textScaleFactor
-
1
);
}
return
EdgeInsetsGeometry
.
lerp
(
geometry2x
,
geometry3x
,
textScaleFactor
-
2
);
}
}
/// The base [State] class for buttons whose style is defined by a [ButtonStyle] object.
///
/// See also:
///
/// * [ButtonStyleButton], the [StatefulWidget] subclass for which this class is the [State].
/// * [TextButton], a simple button without a shadow.
/// * [ContainedButton], a filled button whose material elevates when pressed.
/// * [OutlinedButton], similar to [TextButton], but with an outline.
class
_ButtonStyleState
extends
State
<
ButtonStyleButton
>
{
final
Set
<
MaterialState
>
_states
=
<
MaterialState
>{};
bool
get
_hovered
=>
_states
.
contains
(
MaterialState
.
hovered
);
bool
get
_focused
=>
_states
.
contains
(
MaterialState
.
focused
);
bool
get
_pressed
=>
_states
.
contains
(
MaterialState
.
pressed
);
bool
get
_disabled
=>
_states
.
contains
(
MaterialState
.
disabled
);
void
_updateState
(
MaterialState
state
,
bool
value
)
{
value
?
_states
.
add
(
state
)
:
_states
.
remove
(
state
);
}
void
_handleHighlightChanged
(
bool
value
)
{
if
(
_pressed
!=
value
)
{
setState
(()
{
_updateState
(
MaterialState
.
pressed
,
value
);
});
}
}
void
_handleHoveredChanged
(
bool
value
)
{
if
(
_hovered
!=
value
)
{
setState
(()
{
_updateState
(
MaterialState
.
hovered
,
value
);
});
}
}
void
_handleFocusedChanged
(
bool
value
)
{
if
(
_focused
!=
value
)
{
setState
(()
{
_updateState
(
MaterialState
.
focused
,
value
);
});
}
}
@override
void
initState
()
{
super
.
initState
();
_updateState
(
MaterialState
.
disabled
,
!
widget
.
enabled
);
}
@override
void
didUpdateWidget
(
ButtonStyleButton
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
_updateState
(
MaterialState
.
disabled
,
!
widget
.
enabled
);
// If the button is disabled while a press gesture is currently ongoing,
// InkWell makes a call to handleHighlightChanged. This causes an exception
// because it calls setState in the middle of a build. To preempt this, we
// manually update pressed to false when this situation occurs.
if
(
_disabled
&&
_pressed
)
{
_handleHighlightChanged
(
false
);
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
ButtonStyle
widgetStyle
=
widget
.
style
;
final
ButtonStyle
themeStyle
=
widget
.
themeStyleOf
(
context
);
final
ButtonStyle
defaultStyle
=
widget
.
defaultStyleOf
(
context
);
assert
(
defaultStyle
!=
null
);
T
effectiveValue
<
T
>(
T
Function
(
ButtonStyle
style
)
getProperty
)
{
final
T
widgetValue
=
getProperty
(
widgetStyle
);
final
T
themeValue
=
getProperty
(
themeStyle
);
final
T
defaultValue
=
getProperty
(
defaultStyle
);
return
widgetValue
??
themeValue
??
defaultValue
;
}
T
resolve
<
T
>(
MaterialStateProperty
<
T
>
Function
(
ButtonStyle
style
)
getProperty
)
{
return
effectiveValue
(
(
ButtonStyle
style
)
=>
getProperty
(
style
)?.
resolve
(
_states
),
);
}
final
TextStyle
resolvedTextStyle
=
resolve
<
TextStyle
>((
ButtonStyle
style
)
=>
style
?.
textStyle
);
final
Color
resolvedBackgroundColor
=
resolve
<
Color
>((
ButtonStyle
style
)
=>
style
?.
backgroundColor
);
final
Color
resolvedForegroundColor
=
resolve
<
Color
>((
ButtonStyle
style
)
=>
style
?.
foregroundColor
);
final
Color
resolvedShadowColor
=
resolve
<
Color
>((
ButtonStyle
style
)
=>
style
?.
shadowColor
);
final
double
resolvedElevation
=
resolve
<
double
>((
ButtonStyle
style
)
=>
style
?.
elevation
);
final
EdgeInsetsGeometry
resolvedPadding
=
resolve
<
EdgeInsetsGeometry
>((
ButtonStyle
style
)
=>
style
?.
padding
);
final
Size
resolvedMinimumSize
=
resolve
<
Size
>((
ButtonStyle
style
)
=>
style
?.
minimumSize
);
final
BorderSide
resolvedSide
=
resolve
<
BorderSide
>((
ButtonStyle
style
)
=>
style
?.
side
);
final
OutlinedBorder
resolvedShape
=
resolve
<
OutlinedBorder
>((
ButtonStyle
style
)
=>
style
?.
shape
);
final
MaterialStateMouseCursor
resolvedMouseCursor
=
_MouseCursor
(
(
Set
<
MaterialState
>
states
)
=>
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
mouseCursor
?.
resolve
(
states
)),
);
final
MaterialStateProperty
<
Color
>
overlayColor
=
MaterialStateProperty
.
resolveWith
<
Color
>(
(
Set
<
MaterialState
>
states
)
=>
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
overlayColor
?.
resolve
(
states
)),
);
final
VisualDensity
resolvedVisualDensity
=
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
visualDensity
);
final
MaterialTapTargetSize
resolvedTapTargetSize
=
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
tapTargetSize
);
final
Duration
resolvedAnimationDuration
=
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
animationDuration
);
final
bool
resolvedEnableFeedback
=
effectiveValue
((
ButtonStyle
style
)
=>
style
?.
enableFeedback
);
final
Offset
densityAdjustment
=
resolvedVisualDensity
.
baseSizeAdjustment
;
final
BoxConstraints
effectiveConstraints
=
resolvedVisualDensity
.
effectiveConstraints
(
BoxConstraints
(
minWidth:
resolvedMinimumSize
.
width
,
minHeight:
resolvedMinimumSize
.
height
,
),
);
final
EdgeInsetsGeometry
padding
=
resolvedPadding
.
add
(
EdgeInsets
.
only
(
left:
densityAdjustment
.
dx
,
top:
densityAdjustment
.
dy
,
right:
densityAdjustment
.
dx
,
bottom:
densityAdjustment
.
dy
,
),
).
clamp
(
EdgeInsets
.
zero
,
EdgeInsetsGeometry
.
infinity
);
final
Widget
result
=
ConstrainedBox
(
constraints:
effectiveConstraints
,
child:
Material
(
elevation:
resolvedElevation
,
textStyle:
resolvedTextStyle
?.
copyWith
(
color:
resolvedForegroundColor
),
shape:
resolvedShape
.
copyWith
(
side:
resolvedSide
),
color:
resolvedBackgroundColor
,
shadowColor:
resolvedShadowColor
,
type:
resolvedBackgroundColor
==
null
?
MaterialType
.
transparency
:
MaterialType
.
button
,
animationDuration:
resolvedAnimationDuration
,
clipBehavior:
widget
.
clipBehavior
,
child:
InkWell
(
onTap:
widget
.
onPressed
,
onLongPress:
widget
.
onLongPress
,
onHighlightChanged:
_handleHighlightChanged
,
onHover:
_handleHoveredChanged
,
mouseCursor:
resolvedMouseCursor
,
enableFeedback:
resolvedEnableFeedback
,
focusNode:
widget
.
focusNode
,
canRequestFocus:
widget
.
enabled
,
onFocusChange:
_handleFocusedChanged
,
autofocus:
widget
.
autofocus
,
splashFactory:
InkRipple
.
splashFactory
,
overlayColor:
overlayColor
,
highlightColor:
Colors
.
transparent
,
customBorder:
resolvedShape
,
child:
IconTheme
.
merge
(
data:
IconThemeData
(
color:
resolvedForegroundColor
),
child:
Padding
(
padding:
padding
,
child:
Center
(
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
widget
.
child
,
),
),
),
),
),
);
Size
minSize
;
switch
(
resolvedTapTargetSize
)
{
case
MaterialTapTargetSize
.
padded
:
minSize
=
Size
(
kMinInteractiveDimension
+
densityAdjustment
.
dx
,
kMinInteractiveDimension
+
densityAdjustment
.
dy
,
);
assert
(
minSize
.
width
>=
0.0
);
assert
(
minSize
.
height
>=
0.0
);
break
;
case
MaterialTapTargetSize
.
shrinkWrap
:
minSize
=
Size
.
zero
;
break
;
}
return
Semantics
(
container:
true
,
button:
true
,
enabled:
widget
.
enabled
,
child:
_InputPadding
(
minSize:
minSize
,
child:
result
,
),
);
}
}
class
_MouseCursor
extends
MaterialStateMouseCursor
{
const
_MouseCursor
(
this
.
resolveCallback
);
final
MaterialPropertyResolver
<
MouseCursor
>
resolveCallback
;
@override
MouseCursor
resolve
(
Set
<
MaterialState
>
states
)
=>
resolveCallback
(
states
);
@override
String
get
debugDescription
=>
'ButtonStyleButton_MouseCursor'
;
}
/// A widget to pad the area around a [MaterialButton]'s inner [Material].
///
/// Redirect taps that occur in the padded area around the child to the center
/// of the child. This increases the size of the button and the button's
/// "tap target", but not its material or its ink splashes.
class
_InputPadding
extends
SingleChildRenderObjectWidget
{
const
_InputPadding
({
Key
key
,
Widget
child
,
this
.
minSize
,
})
:
super
(
key:
key
,
child:
child
);
final
Size
minSize
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderInputPadding
(
minSize
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
covariant
_RenderInputPadding
renderObject
)
{
renderObject
.
minSize
=
minSize
;
}
}
class
_RenderInputPadding
extends
RenderShiftedBox
{
_RenderInputPadding
(
this
.
_minSize
,
[
RenderBox
child
])
:
super
(
child
);
Size
get
minSize
=>
_minSize
;
Size
_minSize
;
set
minSize
(
Size
value
)
{
if
(
_minSize
==
value
)
return
;
_minSize
=
value
;
markNeedsLayout
();
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
if
(
child
!=
null
)
return
math
.
max
(
child
.
getMinIntrinsicWidth
(
height
),
minSize
.
width
);
return
0.0
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
if
(
child
!=
null
)
return
math
.
max
(
child
.
getMinIntrinsicHeight
(
width
),
minSize
.
height
);
return
0.0
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
if
(
child
!=
null
)
return
math
.
max
(
child
.
getMaxIntrinsicWidth
(
height
),
minSize
.
width
);
return
0.0
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
if
(
child
!=
null
)
return
math
.
max
(
child
.
getMaxIntrinsicHeight
(
width
),
minSize
.
height
);
return
0.0
;
}
@override
void
performLayout
()
{
final
BoxConstraints
constraints
=
this
.
constraints
;
if
(
child
!=
null
)
{
child
.
layout
(
constraints
,
parentUsesSize:
true
);
final
double
height
=
math
.
max
(
child
.
size
.
width
,
minSize
.
width
);
final
double
width
=
math
.
max
(
child
.
size
.
height
,
minSize
.
height
);
size
=
constraints
.
constrain
(
Size
(
height
,
width
));
final
BoxParentData
childParentData
=
child
.
parentData
as
BoxParentData
;
childParentData
.
offset
=
Alignment
.
center
.
alongOffset
(
size
-
child
.
size
as
Offset
);
}
else
{
size
=
Size
.
zero
;
}
}
@override
bool
hitTest
(
BoxHitTestResult
result
,
{
Offset
position
})
{
if
(
super
.
hitTest
(
result
,
position:
position
))
{
return
true
;
}
final
Offset
center
=
child
.
size
.
center
(
Offset
.
zero
);
return
result
.
addWithRawTransform
(
transform:
MatrixUtils
.
forceToPoint
(
center
),
position:
center
,
hitTest:
(
BoxHitTestResult
result
,
Offset
position
)
{
assert
(
position
==
center
);
return
child
.
hitTest
(
result
,
position:
center
);
},
);
}
}
packages/flutter/lib/src/material/contained_button.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'button_style_button.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'contained_button_theme.dart'
;
import
'material_state.dart'
;
import
'theme.dart'
;
import
'theme_data.dart'
;
/// A Material Design "contained button".
///
/// Use contained buttons to add dimension to otherwise mostly flat
/// layouts, e.g. in long busy lists of content, or in wide
/// spaces. Avoid using contained buttons on already-contained content
/// such as dialogs or cards.
///
/// A contained button is a label [child] displayed on a [Material]
/// widget whose [Material.elevation] increases when the button is
/// pressed. The label's [Text] and [Icon] widgets are displayed in
/// [style]'s [ButtonStyle.onForegroundColor] and the button's filled
/// background is the [ButtonStyle.backgroundColor].
///
/// The contained button's default style is defined by
/// [defaultStyleOf]. The style of this contained button can be
/// overridden with its [style] parameter. The style of all contained
/// buttons in a subtree can be overridden with the
/// [ContainedButtonTheme], and the style of all of the contained
/// buttons in an app can be overridden with the [Theme]'s
/// [ThemeData.containedButtonTheme] property.
///
/// The static [styleFrom] method is a convenient way to create a
/// contained button [ButtonStyle] from simple values.
///
/// If [onPressed] and [onLongPress] callbacks are null, then the
/// button will be disabled.
///
/// See also:
///
/// * [TextButton], a simple flat button without a shadow.
/// * [OutlinedButton], a [TextButton] with a border outline.
/// * <https://material.io/design/components/buttons.html>
class
ContainedButton
extends
ButtonStyleButton
{
/// Create a ContainedButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const
ContainedButton
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
=
false
,
Clip
clipBehavior
=
Clip
.
none
,
@required
Widget
child
,
})
:
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
,
clipBehavior:
clipBehavior
,
child:
child
,
);
/// Create a contained 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 [icon] and [label] arguments must not be null.
factory
ContainedButton
.
icon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
=
_ContainedButtonWithIcon
;
/// A static convenience method that constructs a contained button
/// [ButtonStyle] given simple values.
///
/// The [onPrimary], and [onSurface] colors are used to to create a
/// [MaterialStateProperty] [foreground] value in the same way that
/// [defaultStyleOf] uses the [ColorScheme] colors with the same
/// names. Specify a value for [onPrimary] to specify the color of the
/// button's text and icons as well as the overlay colors used to
/// indicate the hover, focus, and pressed states. Use primary for
/// the button's background fill color and [onSurface]
/// to specify the button's disabled text, icon, and fill color.
///
/// The button's elevations are defined relative to the [elevation]
/// parameter. The disabled elevation is the same as the parameter
/// value, [elevation] + 2 is used when the button is hovered
/// or focused, and elevation + 6 is used when the button is pressed.
///
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
/// parameters are used to construct [ButtonStyle].mouseCursor.
///
/// All of the other parameters are either used directly or used to
/// create a [MaterialStateProperty] with a single value for all
/// states.
///
/// All parameters default to null, by default this method returns
/// a [ButtonStyle] that doesn't override anything.
///
/// For example, to override the default text and icon colors for a
/// [ContainedButton], as well as its overlay color, with all of the
/// standard opacity adjustments for the pressed, focused, and
/// hovered states, one could write:
///
/// ```dart
/// ContainedButton(
/// style: TextButton.styleFrom(primary: Colors.green),
/// )
/// ```
static
ButtonStyle
styleFrom
({
Color
primary
,
Color
onPrimary
,
Color
onSurface
,
Color
shadowColor
,
double
elevation
,
TextStyle
textStyle
,
EdgeInsetsGeometry
padding
,
Size
minimumSize
,
BorderSide
side
,
OutlinedBorder
shape
,
MouseCursor
enabledMouseCursor
,
MouseCursor
disabledMouseCursor
,
VisualDensity
visualDensity
,
MaterialTapTargetSize
tapTargetSize
,
Duration
animationDuration
,
bool
enableFeedback
,
})
{
final
MaterialStateProperty
<
Color
>
backgroundColor
=
(
onSurface
==
null
&&
primary
==
null
)
?
null
:
_ContainedButtonDefaultBackground
(
primary
,
onSurface
);
final
MaterialStateProperty
<
Color
>
foregroundColor
=
(
onSurface
==
null
&&
onPrimary
==
null
)
?
null
:
_ContainedButtonDefaultForeground
(
onPrimary
,
onSurface
);
final
MaterialStateProperty
<
Color
>
overlayColor
=
(
onPrimary
==
null
)
?
null
:
_ContainedButtonDefaultOverlay
(
onPrimary
);
final
MaterialStateProperty
<
double
>
elevationValue
=
(
elevation
==
null
)
?
null
:
_ContainedButtonDefaultElevation
(
elevation
);
final
MaterialStateProperty
<
MouseCursor
>
mouseCursor
=
(
enabledMouseCursor
==
null
&&
disabledMouseCursor
==
null
)
?
null
:
_ContainedButtonDefaultMouseCursor
(
enabledMouseCursor
,
disabledMouseCursor
);
return
ButtonStyle
(
textStyle:
MaterialStateProperty
.
all
<
TextStyle
>(
textStyle
),
backgroundColor:
backgroundColor
,
foregroundColor:
foregroundColor
,
overlayColor:
overlayColor
,
shadowColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
shadowColor
),
elevation:
elevationValue
,
padding:
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
padding
),
minimumSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
minimumSize
),
side:
ButtonStyleButton
.
allOrNull
<
BorderSide
>(
side
),
shape:
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
shape
),
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
}
/// Defines the button's default appearance.
///
/// The button [child]'s [Text] and [Icon] widgets are rendered with
/// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
/// the style's overlay color when the button is focused, hovered
/// or pressed. The button's background color becomes its [Material]
/// color.
///
/// All of the ButtonStyle's defaults appear below. In this list
/// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
/// scheme values like "onSurface(0.38)" are shorthand for
/// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
/// properties that are not followed by by a sublist have the same
/// value for all states, otherwise the values are as specified for
/// each state, and "others" means all other states.
///
/// The `textScaleFactor` is the value of
/// `MediaQuery.of(context).textScaleFactor` and the names of the
/// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
/// abbreviated for readability.
///
/// The color of the [textStyle] is not used, the [foreground] color
/// is used instead.
///
/// * `textStyle` - Theme.textTheme.button
/// * `backgroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.12)
/// * others - Theme.colorScheme.primary
/// * `foregroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.38)
/// * others - Theme.colorScheme.onPrimary
/// * `overlayColor`
/// * hovered - Theme.colorScheme.onPrimary(0.08)
/// * focused or pressed - Theme.colorScheme.onPrimary(0.24)
/// * `shadowColor` - Colors.black
/// * `elevation`
/// * disabled - 0
/// * hovered or focused - 2
/// * pressed - 6
/// * `padding`
/// * textScaleFactor <= 1 - horizontal(16)
/// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
/// * `minimumSize` - Size(64, 36)
/// * `side` - BorderSide.none
/// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
/// * `mouseCursor`
/// * disabled - SystemMouseCursors.forbidden
/// * others - SystemMouseCursors.click
/// * `visualDensity` - theme.visualDensity
/// * `tapTargetSize` - theme.materialTapTargetSize
/// * `animationDuration` - kThemeChangeDuration
/// * `enableFeedback` - true
///
/// The default padding values for the [ContainedButton.icon] factory are slightly different:
///
/// * `padding`
/// * `textScaleFactor <= 1` - start(12) end(16)
/// * `1 < textScaleFactor <= 2` - lerp(start(12) end(16), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ColorScheme
colorScheme
=
theme
.
colorScheme
;
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
,
);
return
styleFrom
(
primary:
colorScheme
.
primary
,
onPrimary:
colorScheme
.
onPrimary
,
onSurface:
colorScheme
.
onSurface
,
shadowColor:
Colors
.
black
,
elevation:
2
,
textStyle:
theme
.
textTheme
.
button
,
padding:
scaledPadding
,
minimumSize:
const
Size
(
64
,
36
),
side:
BorderSide
.
none
,
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4
))),
enabledMouseCursor:
SystemMouseCursors
.
click
,
disabledMouseCursor:
SystemMouseCursors
.
forbidden
,
visualDensity:
theme
.
visualDensity
,
tapTargetSize:
theme
.
materialTapTargetSize
,
animationDuration:
kThemeChangeDuration
,
enableFeedback:
true
,
);
}
/// Returns the [ContainedButtonThemeData.style] of the closest
/// [ContainedButtonTheme] ancestor.
@override
ButtonStyle
themeStyleOf
(
BuildContext
context
)
{
return
ContainedButtonTheme
.
of
(
context
)?.
style
;
}
}
@immutable
class
_ContainedButtonDefaultBackground
extends
MaterialStateProperty
<
Color
>
with
Diagnosticable
{
_ContainedButtonDefaultBackground
(
this
.
primary
,
this
.
onSurface
);
final
Color
primary
;
final
Color
onSurface
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
onSurface
?.
withOpacity
(
0.12
);
return
primary
;
}
}
@immutable
class
_ContainedButtonDefaultForeground
extends
MaterialStateProperty
<
Color
>
with
Diagnosticable
{
_ContainedButtonDefaultForeground
(
this
.
onPrimary
,
this
.
onSurface
);
final
Color
onPrimary
;
final
Color
onSurface
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
onSurface
?.
withOpacity
(
0.38
);
return
onPrimary
;
}
}
@immutable
class
_ContainedButtonDefaultOverlay
extends
MaterialStateProperty
<
Color
>
with
Diagnosticable
{
_ContainedButtonDefaultOverlay
(
this
.
onPrimary
);
final
Color
onPrimary
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
return
onPrimary
?.
withOpacity
(
0.08
);
if
(
states
.
contains
(
MaterialState
.
focused
)
||
states
.
contains
(
MaterialState
.
pressed
))
return
onPrimary
?.
withOpacity
(
0.24
);
return
null
;
}
}
@immutable
class
_ContainedButtonDefaultElevation
extends
MaterialStateProperty
<
double
>
with
Diagnosticable
{
_ContainedButtonDefaultElevation
(
this
.
elevation
);
final
double
elevation
;
@override
double
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
0
;
if
(
states
.
contains
(
MaterialState
.
hovered
))
return
elevation
+
2
;
if
(
states
.
contains
(
MaterialState
.
focused
))
return
elevation
+
2
;
if
(
states
.
contains
(
MaterialState
.
pressed
))
return
elevation
+
6
;
return
elevation
;
}
}
@immutable
class
_ContainedButtonDefaultMouseCursor
extends
MaterialStateProperty
<
MouseCursor
>
with
Diagnosticable
{
_ContainedButtonDefaultMouseCursor
(
this
.
enabledCursor
,
this
.
disabledCursor
);
final
MouseCursor
enabledCursor
;
final
MouseCursor
disabledCursor
;
@override
MouseCursor
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
disabledCursor
;
return
enabledCursor
;
}
}
class
_ContainedButtonWithIcon
extends
ContainedButton
{
_ContainedButtonWithIcon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
:
assert
(
icon
!=
null
),
assert
(
label
!=
null
),
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
??
false
,
clipBehavior:
clipBehavior
??
Clip
.
none
,
child:
_ContainedButtonWithIconChild
(
icon:
icon
,
label:
label
),
);
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsetsDirectional
.
fromSTEB
(
12
,
0
,
16
,
0
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsetsDirectional
.
fromSTEB
(
8
,
0
,
4
,
0
),
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
,
);
return
super
.
defaultStyleOf
(
context
).
copyWith
(
padding:
MaterialStateProperty
.
all
<
EdgeInsetsGeometry
>(
scaledPadding
)
);
}
}
class
_ContainedButtonWithIconChild
extends
StatelessWidget
{
const
_ContainedButtonWithIconChild
({
Key
key
,
this
.
label
,
this
.
icon
})
:
super
(
key:
key
);
final
Widget
label
;
final
Widget
icon
;
@override
Widget
build
(
BuildContext
context
)
{
final
double
scale
=
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
;
final
double
gap
=
scale
<=
1
?
8
:
lerpDouble
(
8
,
4
,
math
.
min
(
scale
-
1
,
1
));
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
icon
,
SizedBox
(
width:
gap
),
label
],
);
}
}
packages/flutter/lib/src/material/contained_button_theme.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'theme.dart'
;
/// A [ButtonStyle] that overrides the default appearance of
/// [ContainedButton]s when it's used with [ContainedButtonTheme] or with the
/// overall [Theme]'s [ThemeData.containedButtonTheme].
///
/// The [style]'s properties override [ContainedButton]'s default style,
/// i.e. the [ButtonStyle] returned by [ContainedButton.defaultStyleOf]. Only
/// the style's non-null property values or resolved non-null
/// [MaterialStateProperty] values are used.
///
/// See also:
///
/// * [ContainedButtonTheme], the theme which is configured with this class.
/// * [ContainedButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [ContainedButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [ContainedButton]'s defaults.
/// * [MaterialStateProperty.resolve], "resolve" a material state property
/// to a simple value based on a set of [MaterialState]s.
/// * [ThemeData.containedButtonTheme], which can be used to override the default
/// [ButtonStyle] for [ContainedButton]s below the overall [Theme].
@immutable
class
ContainedButtonThemeData
with
Diagnosticable
{
/// Creates a [ContainedButtonThemeData].
///
/// The [style] may be null.
const
ContainedButtonThemeData
({
this
.
style
});
/// Overrides for [ContainedButton]'s default style.
///
/// Non-null properties or non-null resolved [MaterialStateProperty]
/// values override the [ButtonStyle] returned by
/// [ContainedButton.defaultStyleOf].
///
/// If [style] is null, then this theme doesn't override anything.
final
ButtonStyle
style
;
/// Linearly interpolate between two contained button themes.
static
ContainedButtonThemeData
lerp
(
ContainedButtonThemeData
a
,
ContainedButtonThemeData
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
ContainedButtonThemeData
(
style:
ButtonStyle
.
lerp
(
a
?.
style
,
b
?.
style
,
t
),
);
}
@override
int
get
hashCode
{
return
style
.
hashCode
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
ContainedButtonThemeData
&&
other
.
style
==
style
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
}
}
/// Overrides the default [ButtonStyle] of its [ContainedButton] descendants.
///
/// See also:
///
/// * [ContainedButtonThemeData], which is used to configure this theme.
/// * [ContainedButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [ContainedButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [ContainedButton]'s defaults.
/// * [ThemeData.containedButtonTheme], which can be used to override the default
/// [ButtonStyle] for [ContainedButton]s below the overall [Theme].
class
ContainedButtonTheme
extends
InheritedTheme
{
/// Create a [ContainedButtonTheme].
///
/// The [data] parameter must not be null.
const
ContainedButtonTheme
({
Key
key
,
@required
this
.
data
,
Widget
child
,
})
:
assert
(
data
!=
null
),
super
(
key:
key
,
child:
child
);
/// The configuration of this theme.
final
ContainedButtonThemeData
data
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [ContainedButtonsTheme] widget, then
/// [ThemeData.containedButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// ContainedButtonTheme theme = ContainedButtonTheme.of(context);
/// ```
static
ContainedButtonThemeData
of
(
BuildContext
context
)
{
final
ContainedButtonTheme
buttonTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
ContainedButtonTheme
>();
return
buttonTheme
?.
data
??
Theme
.
of
(
context
).
containedButtonTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
final
ContainedButtonTheme
ancestorTheme
=
context
.
findAncestorWidgetOfExactType
<
ContainedButtonTheme
>();
return
identical
(
this
,
ancestorTheme
)
?
child
:
ContainedButtonTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
ContainedButtonTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/material_state.dart
View file @
dc31d89c
...
@@ -362,4 +362,7 @@ class _MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
...
@@ -362,4 +362,7 @@ class _MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
@override
@override
T
resolve
(
Set
<
MaterialState
>
states
)
=>
value
;
T
resolve
(
Set
<
MaterialState
>
states
)
=>
value
;
@override
String
toString
()
=>
'MaterialStateProperty.all(
$value
)'
;
}
}
packages/flutter/lib/src/material/outlined_button.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'button_style_button.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'material_state.dart'
;
import
'outlined_button_theme.dart'
;
import
'theme.dart'
;
import
'theme_data.dart'
;
/// A Material Design "Outlined Button"; essentially a [TextButton]
/// with an outlined border.
///
/// Outlined buttons are medium-emphasis buttons. They contain actions
/// that are important, but they aren’t the primary action in an app.
///
/// An outlined button is a label [child] displayed on a (zero
/// elevation) [Material] widget. The label's [Text] and [Icon]
/// widgets are displayed in the [style]'s
/// [ButtonStyle.foregroundColor] and the outline's weight and color
/// are defined by [ButtonStyle.side]. The button reacts to touches
/// by filling with the [style]'s [ButtonStyle.backgroundColor].
///
/// The outlined button's default style is defined by [defaultStyleOf].
/// The style of this outline button can be overridden with its [style]
/// parameter. The style of all text buttons in a subtree can be
/// overridden with the [OutlinedButtonTheme] and the style of all of the
/// outlined buttons in an app can be overridden with the [Theme]'s
/// [ThemeData.outlinedButtonTheme] property.
///
/// The static [styleFrom] method is a convenient way to create a
/// outlined button [ButtonStyle] from simple values.
///
/// See also:
///
/// * [ContainedButton], a filled material design button with a shadow.
/// * [TextButton], a material design button without a shadow.
/// * <https://material.io/design/components/buttons.html>
class
OutlinedButton
extends
ButtonStyleButton
{
/// Create an OutlinedButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const
OutlinedButton
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
=
false
,
Clip
clipBehavior
=
Clip
.
none
,
@required
Widget
child
,
})
:
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
,
clipBehavior:
clipBehavior
,
child:
child
,
);
/// Create a text 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 [icon] and [label] arguments must not be null.
factory
OutlinedButton
.
icon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
=
_OutlinedButtonWithIcon
;
/// A static convenience method that constructs an outlined button
/// [ButtonStyle] given simple values.
///
/// The [primary], and [onSurface] colors are used to to create a
/// [MaterialStateProperty] [foreground] value in the same way that
/// [defaultStyleOf] uses the [ColorScheme] colors with the same
/// names. Specify a value for [primary] to specify the color of the
/// button's text and icons as well as the overlay colors used to
/// indicate the hover, focus, and pressed states. Use [onSurface]
/// to specify the button's disabled text and icon color.
///
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
/// parameters are used to construct [ButtonStyle].mouseCursor.
///
/// All of the other parameters are either used directly or used to
/// create a [MaterialStateProperty] with a single value for all
/// states.
///
/// All parameters default to null, by default this method returns
/// a [ButtonStyle] that doesn't override anything.
///
/// For example, to override the default shape and outline for an
/// [OutlinedButton], one could write:
///
/// ```dart
/// OutlinedButton(
/// style: OutlinedButton.styleFrom(
/// shape: StadiumBorder(),
/// side: BorderSide(width: 2, color: Colors.green),
/// ),
/// )
/// ```
static
ButtonStyle
styleFrom
({
Color
primary
,
Color
onSurface
,
Color
backgroundColor
,
Color
shadowColor
,
double
elevation
,
TextStyle
textStyle
,
EdgeInsetsGeometry
padding
,
Size
minimumSize
,
BorderSide
side
,
OutlinedBorder
shape
,
MouseCursor
enabledMouseCursor
,
MouseCursor
disabledMouseCursor
,
VisualDensity
visualDensity
,
MaterialTapTargetSize
tapTargetSize
,
Duration
animationDuration
,
bool
enableFeedback
,
})
{
final
MaterialStateProperty
<
Color
>
foregroundColor
=
(
onSurface
==
null
&&
primary
==
null
)
?
null
:
_OutlinedButtonDefaultForeground
(
primary
,
onSurface
);
final
MaterialStateProperty
<
Color
>
overlayColor
=
(
primary
==
null
)
?
null
:
_OutlinedButtonDefaultOverlay
(
primary
);
final
MaterialStateProperty
<
MouseCursor
>
mouseCursor
=
(
enabledMouseCursor
==
null
&&
disabledMouseCursor
==
null
)
?
null
:
_OutlinedButtonDefaultMouseCursor
(
enabledMouseCursor
,
disabledMouseCursor
);
return
ButtonStyle
(
textStyle:
ButtonStyleButton
.
allOrNull
<
TextStyle
>(
textStyle
),
foregroundColor:
foregroundColor
,
backgroundColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
backgroundColor
),
overlayColor:
overlayColor
,
shadowColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
shadowColor
),
elevation:
ButtonStyleButton
.
allOrNull
<
double
>(
elevation
),
padding:
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
padding
),
minimumSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
minimumSize
),
side:
ButtonStyleButton
.
allOrNull
<
BorderSide
>(
side
),
shape:
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
shape
),
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
}
/// Defines the button's default appearance.
///
/// With the exception of [ButtonStyle.side], which defines the
/// outline, and [ButtonStyle.padding], the returned style is the
/// same as for [TextButton].
///
/// The button [child]'s [Text] and [Icon] widgets are rendered with
/// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
/// the style's overlay color when the button is focused, hovered
/// or pressed. The button's background color becomes its [Material]
/// color and is transparent by default.
///
/// All of the ButtonStyle's defaults appear below. In this list
/// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
/// scheme values like "onSurface(0.38)" are shorthand for
/// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
/// properties that are not followed by by a sublist have the same
/// value for all states, otherwise the values are as specified for
/// each state and "others" means all other states.
///
/// The color of the [textStyle] is not used, the [foreground] color
/// is used instead.
///
/// * `textStyle` - Theme.textTheme.button
/// * `backgroundColor` - transparent
/// * `foregroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.38)
/// * others - Theme.colorScheme.primary
/// * `overlayColor`
/// * hovered - Theme.colorScheme.primary(0.04)
/// * focused or pressed - Theme.colorScheme.primary(0.12)
/// * `shadowColor` - Colors.black
/// * `elevation` - 0
/// * `padding`
/// * `textScaleFactor <= 1` - horizontal(16)
/// * `1 < textScaleFactor <= 2` - lerp(horizontal(16), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
/// * `minimumSize` - Size(64, 36)
/// * `side` - BorderSide(width: 1, color: Theme.colorScheme.onSurface(0.12))
/// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
/// * `mouseCursor`
/// * disabled - SystemMouseCursors.forbidden
/// * others - SystemMouseCursors.click
/// * `visualDensity` - theme.visualDensity
/// * `tapTargetSize` - theme.materialTapTargetSize
/// * `animationDuration` - kThemeChangeDuration
/// * `enableFeedback` - true
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ColorScheme
colorScheme
=
theme
.
colorScheme
;
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsets
.
symmetric
(
horizontal:
16
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
,
);
return
styleFrom
(
primary:
colorScheme
.
primary
,
onSurface:
colorScheme
.
onSurface
,
backgroundColor:
Colors
.
transparent
,
shadowColor:
Colors
.
black
,
elevation:
0
,
textStyle:
theme
.
textTheme
.
button
,
padding:
scaledPadding
,
minimumSize:
const
Size
(
64
,
36
),
side:
BorderSide
(
color:
Theme
.
of
(
context
).
colorScheme
.
onSurface
.
withOpacity
(
0.12
),
width:
1
,
),
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4
))),
enabledMouseCursor:
SystemMouseCursors
.
click
,
disabledMouseCursor:
SystemMouseCursors
.
forbidden
,
visualDensity:
theme
.
visualDensity
,
tapTargetSize:
theme
.
materialTapTargetSize
,
animationDuration:
kThemeChangeDuration
,
enableFeedback:
true
,
);
}
@override
ButtonStyle
themeStyleOf
(
BuildContext
context
)
{
return
OutlinedButtonTheme
.
of
(
context
)?.
style
;
}
}
@immutable
class
_OutlinedButtonDefaultForeground
extends
MaterialStateProperty
<
Color
>
with
Diagnosticable
{
_OutlinedButtonDefaultForeground
(
this
.
primary
,
this
.
onSurface
);
final
Color
primary
;
final
Color
onSurface
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
onSurface
?.
withOpacity
(
0.38
);
return
primary
;
}
}
@immutable
class
_OutlinedButtonDefaultOverlay
extends
MaterialStateProperty
<
Color
>
with
Diagnosticable
{
_OutlinedButtonDefaultOverlay
(
this
.
primary
);
final
Color
primary
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
return
primary
?.
withOpacity
(
0.04
);
if
(
states
.
contains
(
MaterialState
.
focused
)
||
states
.
contains
(
MaterialState
.
pressed
))
return
primary
?.
withOpacity
(
0.12
);
return
null
;
}
}
@immutable
class
_OutlinedButtonDefaultMouseCursor
extends
MaterialStateProperty
<
MouseCursor
>
with
Diagnosticable
{
_OutlinedButtonDefaultMouseCursor
(
this
.
enabledCursor
,
this
.
disabledCursor
);
final
MouseCursor
enabledCursor
;
final
MouseCursor
disabledCursor
;
@override
MouseCursor
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
disabledCursor
;
return
enabledCursor
;
}
}
class
_OutlinedButtonWithIcon
extends
OutlinedButton
{
_OutlinedButtonWithIcon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
:
assert
(
icon
!=
null
),
assert
(
label
!=
null
),
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
??
false
,
clipBehavior:
clipBehavior
??
Clip
.
none
,
child:
_OutlinedButtonWithIconChild
(
icon:
icon
,
label:
label
),
);
}
class
_OutlinedButtonWithIconChild
extends
StatelessWidget
{
const
_OutlinedButtonWithIconChild
({
Key
key
,
this
.
label
,
this
.
icon
})
:
super
(
key:
key
);
final
Widget
label
;
final
Widget
icon
;
@override
Widget
build
(
BuildContext
context
)
{
final
double
scale
=
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
;
final
double
gap
=
scale
<=
1
?
8
:
lerpDouble
(
8
,
4
,
math
.
min
(
scale
-
1
,
1
));
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
icon
,
SizedBox
(
width:
gap
),
label
],
);
}
}
packages/flutter/lib/src/material/outlined_button_theme.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'theme.dart'
;
/// A [ButtonStyle] that overrides the default appearance of
/// [OutlinedButton]s when it's used with [OutlinedButtonTheme] or with the
/// overall [Theme]'s [ThemeData.outlinedButtonTheme].
///
/// The [style]'s properties override [OutlinedButton]'s default style,
/// i.e. the [ButtonStyle] returned by [OutlinedButton.defaultStyleOf]. Only
/// the style's non-null property values or resolved non-null
/// [MaterialStateProperty] values are used.
///
/// See also:
///
/// * [OutlinedButtonTheme], the theme which is configured with this class.
/// * [OutlinedButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [OutlinedButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [OutlinedButton]'s defaults.
/// * [MaterialStateProperty.resolve], "resolve" a material state property
/// to a simple value based on a set of [MaterialState]s.
/// * [ThemeData.outlinedButtonTheme], which can be used to override the default
/// [ButtonStyle] for [OutlinedButton]s below the overall [Theme].
@immutable
class
OutlinedButtonThemeData
with
Diagnosticable
{
/// Creates a [OutlinedButtonThemeData].
///
/// The [style] may be null.
const
OutlinedButtonThemeData
({
this
.
style
});
/// Overrides for [OutlinedButton]'s default style.
///
/// Non-null properties or non-null resolved [MaterialStateProperty]
/// values override the [ButtonStyle] returned by
/// [OutlinedButton.defaultStyleOf].
///
/// If [style] is null, then this theme doesn't override anything.
final
ButtonStyle
style
;
/// Linearly interpolate between two outlined button themes.
static
OutlinedButtonThemeData
lerp
(
OutlinedButtonThemeData
a
,
OutlinedButtonThemeData
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
OutlinedButtonThemeData
(
style:
ButtonStyle
.
lerp
(
a
?.
style
,
b
?.
style
,
t
),
);
}
@override
int
get
hashCode
{
return
style
.
hashCode
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
OutlinedButtonThemeData
&&
other
.
style
==
style
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
}
}
/// Overrides the default [ButtonStyle] of its [OutlinedButton] descendants.
///
/// See also:
///
/// * [OutlinedButtonThemeData], which is used to configure this theme.
/// * [OutlinedButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [OutlinedButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [OutlinedButton]'s defaults.
/// * [ThemeData.outlinedButtonTheme], which can be used to override the default
/// [ButtonStyle] for [OutlinedButton]s below the overall [Theme].
class
OutlinedButtonTheme
extends
InheritedTheme
{
/// Create a [OutlinedButtonTheme].
///
/// The [data] parameter must not be null.
const
OutlinedButtonTheme
({
Key
key
,
@required
this
.
data
,
Widget
child
,
})
:
assert
(
data
!=
null
),
super
(
key:
key
,
child:
child
);
/// The configuration of this theme.
final
OutlinedButtonThemeData
data
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [OutlinedButtonsTheme] widget, then
/// [ThemeData.outlinedButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// OutlinedButtonTheme theme = OutlinedButtonTheme.of(context);
/// ```
static
OutlinedButtonThemeData
of
(
BuildContext
context
)
{
final
OutlinedButtonTheme
buttonTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
OutlinedButtonTheme
>();
return
buttonTheme
?.
data
??
Theme
.
of
(
context
).
outlinedButtonTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
final
OutlinedButtonTheme
ancestorTheme
=
context
.
findAncestorWidgetOfExactType
<
OutlinedButtonTheme
>();
return
identical
(
this
,
ancestorTheme
)
?
child
:
OutlinedButtonTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
OutlinedButtonTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/text_button.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'button_style_button.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'constants.dart'
;
import
'material_state.dart'
;
import
'text_button_theme.dart'
;
import
'theme.dart'
;
import
'theme_data.dart'
;
/// A Material Design "Text Button".
///
/// Use text buttons on toolbars, in dialogs, or inline with other
/// content but offset from that content with padding so that the
/// button's presence is obvious. Text buttons do not have visible
/// borders and must therefore rely on their position relative to
/// other content for context. In dialogs and cards, they should be
/// grouped together in one of the bottom corners. Avoid using text
/// buttons where they would blend in with other content, for example
/// in the middle of lists.
///
/// A text button is a label [child] displayed on a (zero elevation)
/// [Material] widget. The label's [Text] and [Icon] widgets are
/// displayed in the [style]'s [ButtonStyle.foregroundColor]. The
/// button reacts to touches by filling with the [style]'s
/// [ButtonStyle.backgroundColor].
///
/// The text button's default style is defined by [defaultStyleOf].
/// The style of this text button can be overridden with its [style]
/// parameter. The style of all text buttons in a subtree can be
/// overridden with the [TextButtonTheme] and the style of all of the
/// text buttons in an app can be overridden with the [Theme]'s
/// [ThemeData.textButtonTheme] property.
///
/// The static [styleFrom] method is a convenient way to create a
/// text button [ButtonStyle] from simple values.
///
/// If the [onPressed] and [onLongPress] callbacks are null, then this
/// button will be disabled, it will not react to touch.
///
/// See also:
///
/// * [OutlinedButton], a [TextButton] with a border outline.
/// * [ContainedButton], a filled button whose material elevates when pressed.
/// * <https://material.io/design/components/buttons.html>
class
TextButton
extends
ButtonStyleButton
{
/// Create a TextButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const
TextButton
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
=
false
,
Clip
clipBehavior
=
Clip
.
none
,
@required
Widget
child
,
})
:
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
,
clipBehavior:
clipBehavior
,
child:
child
,
);
/// Create a text 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 8 logical pixels
/// at the ends, with an 8 pixel gap in between.
///
/// The [icon] and [label] arguments must not be null.
factory
TextButton
.
icon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
=
_TextButtonWithIcon
;
/// A static convenience method that constructs a text button
/// [ButtonStyle] given simple values.
///
/// The [primary], and [onSurface] colors are used to to create a
/// [MaterialStateProperty] [foreground] value in the same way that
/// [defaultStyleOf] uses the [ColorScheme] colors with the same
/// names. Specify a value for [primary] to specify the color of the
/// button's text and icons as well as the overlay colors used to
/// indicate the hover, focus, and pressed states. Use [onSurface]
/// to specify the button's disabled text and icon color.
///
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
/// parameters are used to construct [ButtonStyle].mouseCursor.
///
/// All of the other parameters are either used directly or used to
/// create a [MaterialStateProperty] with a single value for all
/// states.
///
/// All parameters default to null. By default this method returns
/// a [ButtonStyle] that doesn't override anything.
///
/// For example, to override the default text and icon colors for a
/// [TextButton], as well as its overlay color, with all of the
/// standard opacity adjustments for the pressed, focused, and
/// hovered states, one could write:
///
/// ```dart
/// TextButton(
/// style: TextButton.styleFrom(primary: Colors.green),
/// )
/// ```
static
ButtonStyle
styleFrom
({
Color
primary
,
Color
onSurface
,
Color
backgroundColor
,
Color
shadowColor
,
double
elevation
,
TextStyle
textStyle
,
EdgeInsetsGeometry
padding
,
Size
minimumSize
,
BorderSide
side
,
OutlinedBorder
shape
,
MouseCursor
enabledMouseCursor
,
MouseCursor
disabledMouseCursor
,
VisualDensity
visualDensity
,
MaterialTapTargetSize
tapTargetSize
,
Duration
animationDuration
,
bool
enableFeedback
,
})
{
final
MaterialStateProperty
<
Color
>
foregroundColor
=
(
onSurface
==
null
&&
primary
==
null
)
?
null
:
_TextButtonDefaultForeground
(
primary
,
onSurface
);
final
MaterialStateProperty
<
Color
>
overlayColor
=
(
primary
==
null
)
?
null
:
_TextButtonDefaultOverlay
(
primary
);
final
MaterialStateProperty
<
MouseCursor
>
mouseCursor
=
(
enabledMouseCursor
==
null
&&
disabledMouseCursor
==
null
)
?
null
:
_TextButtonDefaultMouseCursor
(
enabledMouseCursor
,
disabledMouseCursor
);
return
ButtonStyle
(
textStyle:
ButtonStyleButton
.
allOrNull
<
TextStyle
>(
textStyle
),
backgroundColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
backgroundColor
),
foregroundColor:
foregroundColor
,
overlayColor:
overlayColor
,
shadowColor:
ButtonStyleButton
.
allOrNull
<
Color
>(
shadowColor
),
elevation:
ButtonStyleButton
.
allOrNull
<
double
>(
elevation
),
padding:
ButtonStyleButton
.
allOrNull
<
EdgeInsetsGeometry
>(
padding
),
minimumSize:
ButtonStyleButton
.
allOrNull
<
Size
>(
minimumSize
),
side:
ButtonStyleButton
.
allOrNull
<
BorderSide
>(
side
),
shape:
ButtonStyleButton
.
allOrNull
<
OutlinedBorder
>(
shape
),
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
}
/// Defines the button's default appearance.
///
/// The button [child]'s [Text] and [Icon] widgets are rendered with
/// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
/// the style's overlay color when the button is focused, hovered
/// or pressed. The button's background color becomes its [Material]
/// color and is transparent by default.
///
/// All of the ButtonStyle's defaults appear below.
///
/// In this list "Theme.foo" is shorthand for
/// `Theme.of(context).foo`. Color scheme values like
/// "onSurface(0.38)" are shorthand for
/// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
/// properties that are not followed by by a sublist have the same
/// value for all states, otherwise the values are as specified for
/// each state and "others" means all other states.
///
/// The `textScaleFactor` is the value of
/// `MediaQuery.of(context).textScaleFactor` and the names of the
/// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
/// abbreviated for readability.
///
/// The color of the [textStyle] is not used, the [foreground] color
/// is used instead.
///
/// * `textStyle` - Theme.textTheme.button
/// * `backgroundColor` - transparent
/// * `foregroundColor`
/// * disabled - Theme.colorScheme.onSurface(0.38)
/// * others - Theme.colorScheme.primary
/// * `overlayColor`
/// * hovered - Theme.colorScheme.primary(0.04)
/// * focused or pressed - Theme.colorScheme.primary(0.12)
/// * `shadowColor` - Colors.black
/// * `elevation` - 0
/// * `padding`
/// * `textScaleFactor <= 1` - all(8)
/// * `1 < textScaleFactor <= 2` - lerp(all(8), horizontal(8))
/// * `2 < textScaleFactor <= 3` - lerp(horizontal(8), horizontal(4))
/// * `3 < textScaleFactor` - horizontal(4)
/// * `minimumSize` - Size(64, 36)
/// * `side` - BorderSide.none
/// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
/// * `mouseCursor`
/// * disabled - SystemMouseCursors.forbidden
/// * others - SystemMouseCursors.click
/// * `visualDensity` - theme.visualDensity
/// * `tapTargetSize` - theme.materialTapTargetSize
/// * `animationDuration` - kThemeChangeDuration
/// * `enableFeedback` - true
///
/// The default padding values for the [TextButton.icon] factory are slightly different:
///
/// * `padding`
/// * `textScaleFactor <= 1` - all(8)
/// * `1 < textScaleFactor <= 2 `- lerp(all(8), horizontal(4))
/// * `2 < textScaleFactor` - horizontal(4)
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ColorScheme
colorScheme
=
theme
.
colorScheme
;
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsets
.
all
(
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
,
);
return
styleFrom
(
primary:
colorScheme
.
primary
,
onSurface:
colorScheme
.
onSurface
,
backgroundColor:
Colors
.
transparent
,
shadowColor:
Colors
.
black
,
elevation:
0
,
textStyle:
theme
.
textTheme
.
button
,
padding:
scaledPadding
,
minimumSize:
const
Size
(
64
,
36
),
side:
BorderSide
.
none
,
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4
))),
enabledMouseCursor:
SystemMouseCursors
.
click
,
disabledMouseCursor:
SystemMouseCursors
.
forbidden
,
visualDensity:
theme
.
visualDensity
,
tapTargetSize:
theme
.
materialTapTargetSize
,
animationDuration:
kThemeChangeDuration
,
enableFeedback:
true
,
);
}
/// Returns the [TextButtonThemeData.style] of the closest
/// [TextButtonTheme] ancestor.
@override
ButtonStyle
themeStyleOf
(
BuildContext
context
)
{
return
TextButtonTheme
.
of
(
context
)?.
style
;
}
}
@immutable
class
_TextButtonDefaultForeground
extends
MaterialStateProperty
<
Color
>
{
_TextButtonDefaultForeground
(
this
.
primary
,
this
.
onSurface
);
final
Color
primary
;
final
Color
onSurface
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
onSurface
?.
withOpacity
(
0.38
);
return
primary
;
}
@override
String
toString
()
{
return
'{disabled:
${onSurface?.withOpacity(0.38)}
, otherwise:
$primary
}'
;
}
}
@immutable
class
_TextButtonDefaultOverlay
extends
MaterialStateProperty
<
Color
>
{
_TextButtonDefaultOverlay
(
this
.
primary
);
final
Color
primary
;
@override
Color
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
return
primary
?.
withOpacity
(
0.04
);
if
(
states
.
contains
(
MaterialState
.
focused
)
||
states
.
contains
(
MaterialState
.
pressed
))
return
primary
?.
withOpacity
(
0.12
);
return
null
;
}
@override
String
toString
()
{
return
'{hovered:
${primary?.withOpacity(0.04)}
, focused,pressed:
${primary?.withOpacity(0.12)}
, otherwise: null}'
;
}
}
@immutable
class
_TextButtonDefaultMouseCursor
extends
MaterialStateProperty
<
MouseCursor
>
with
Diagnosticable
{
_TextButtonDefaultMouseCursor
(
this
.
enabledCursor
,
this
.
disabledCursor
);
final
MouseCursor
enabledCursor
;
final
MouseCursor
disabledCursor
;
@override
MouseCursor
resolve
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
disabledCursor
;
return
enabledCursor
;
}
}
class
_TextButtonWithIcon
extends
TextButton
{
_TextButtonWithIcon
({
Key
key
,
@required
VoidCallback
onPressed
,
VoidCallback
onLongPress
,
ButtonStyle
style
,
FocusNode
focusNode
,
bool
autofocus
,
Clip
clipBehavior
,
@required
Widget
icon
,
@required
Widget
label
,
})
:
assert
(
icon
!=
null
),
assert
(
label
!=
null
),
super
(
key:
key
,
onPressed:
onPressed
,
onLongPress:
onLongPress
,
style:
style
,
focusNode:
focusNode
,
autofocus:
autofocus
??
false
,
clipBehavior:
clipBehavior
??
Clip
.
none
,
child:
_TextButtonWithIconChild
(
icon:
icon
,
label:
label
),
);
@override
ButtonStyle
defaultStyleOf
(
BuildContext
context
)
{
final
EdgeInsetsGeometry
scaledPadding
=
ButtonStyleButton
.
scaledPadding
(
const
EdgeInsets
.
all
(
8
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
const
EdgeInsets
.
symmetric
(
horizontal:
4
),
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
,
);
return
super
.
defaultStyleOf
(
context
).
copyWith
(
padding:
MaterialStateProperty
.
all
<
EdgeInsetsGeometry
>(
scaledPadding
)
);
}
}
class
_TextButtonWithIconChild
extends
StatelessWidget
{
const
_TextButtonWithIconChild
({
Key
key
,
this
.
label
,
this
.
icon
})
:
super
(
key:
key
);
final
Widget
label
;
final
Widget
icon
;
@override
Widget
build
(
BuildContext
context
)
{
final
double
scale
=
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1
;
final
double
gap
=
scale
<=
1
?
8
:
lerpDouble
(
8
,
4
,
math
.
min
(
scale
-
1
,
1
));
return
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
icon
,
SizedBox
(
width:
gap
),
label
],
);
}
}
packages/flutter/lib/src/material/text_button_theme.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'theme.dart'
;
/// A [ButtonStyle] that overrides the default appearance of
/// [TextButton]s when it's used with [TextButtonTheme] or with the
/// overall [Theme]'s [ThemeData.textButtonTheme].
///
/// The [style]'s properties override [TextButton]'s default style,
/// i.e. the [ButtonStyle] returned by [TextButton.defaultStyleOf]. Only
/// the style's non-null property values or resolved non-null
/// [MaterialStateProperty] values are used.
///
/// See also:
///
/// * [TextButtonTheme], the theme which is configured with this class.
/// * [TextButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [TextButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [TextButton]'s defaults.
/// * [MaterialStateProperty.resolve], "resolve" a material state property
/// to a simple value based on a set of [MaterialState]s.
/// * [ThemeData.textButtonTheme], which can be used to override the default
/// [ButtonStyle] for [TextButton]s below the overall [Theme].
@immutable
class
TextButtonThemeData
with
Diagnosticable
{
/// Creates a [TextButtonThemeData].
///
/// The [style] may be null.
const
TextButtonThemeData
({
this
.
style
});
/// Overrides for [TextButton]'s default style.
///
/// Non-null properties or non-null resolved [MaterialStateProperty]
/// values override the [ButtonStyle] returned by
/// [TextButton.defaultStyleOf].
///
/// If [style] is null, then this theme doesn't override anything.
final
ButtonStyle
style
;
/// Linearly interpolate between two text button themes.
static
TextButtonThemeData
lerp
(
TextButtonThemeData
a
,
TextButtonThemeData
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
TextButtonThemeData
(
style:
ButtonStyle
.
lerp
(
a
?.
style
,
b
?.
style
,
t
),
);
}
@override
int
get
hashCode
{
return
style
.
hashCode
;
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
TextButtonThemeData
&&
other
.
style
==
style
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
}
}
/// Overrides the default [ButtonStyle] of its [TextButton] descendants.
///
/// See also:
///
/// * [TextButtonThemeData], which is used to configure this theme.
/// * [TextButton.defaultStyleOf], which returns the default [ButtonStyle]
/// for text buttons.
/// * [TextButton.styleOf], which converts simple values into a
/// [ButtonStyle] that's consistent with [TextButton]'s defaults.
/// * [ThemeData.textButtonTheme], which can be used to override the default
/// [ButtonStyle] for [TextButton]s below the overall [Theme].
class
TextButtonTheme
extends
InheritedTheme
{
/// Create a [TextButtonTheme].
///
/// The [data] parameter must not be null.
const
TextButtonTheme
({
Key
key
,
@required
this
.
data
,
Widget
child
,
})
:
assert
(
data
!=
null
),
super
(
key:
key
,
child:
child
);
/// The configuration of this theme.
final
TextButtonThemeData
data
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [TextButtonsTheme] widget, then
/// [ThemeData.textButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// TextButtonTheme theme = TextButtonTheme.of(context);
/// ```
static
TextButtonThemeData
of
(
BuildContext
context
)
{
final
TextButtonTheme
buttonTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
TextButtonTheme
>();
return
buttonTheme
?.
data
??
Theme
.
of
(
context
).
textButtonTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
final
TextButtonTheme
ancestorTheme
=
context
.
findAncestorWidgetOfExactType
<
TextButtonTheme
>();
return
identical
(
this
,
ancestorTheme
)
?
child
:
TextButtonTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
TextButtonTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/theme_data.dart
View file @
dc31d89c
...
@@ -22,6 +22,7 @@ import 'card_theme.dart';
...
@@ -22,6 +22,7 @@ import 'card_theme.dart';
import
'chip_theme.dart'
;
import
'chip_theme.dart'
;
import
'color_scheme.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'colors.dart'
;
import
'contained_button_theme.dart'
;
import
'dialog_theme.dart'
;
import
'dialog_theme.dart'
;
import
'divider_theme.dart'
;
import
'divider_theme.dart'
;
import
'floating_action_button_theme.dart'
;
import
'floating_action_button_theme.dart'
;
...
@@ -29,11 +30,13 @@ import 'ink_splash.dart';
...
@@ -29,11 +30,13 @@ import 'ink_splash.dart';
import
'ink_well.dart'
show
InteractiveInkFeatureFactory
;
import
'ink_well.dart'
show
InteractiveInkFeatureFactory
;
import
'input_decorator.dart'
;
import
'input_decorator.dart'
;
import
'navigation_rail_theme.dart'
;
import
'navigation_rail_theme.dart'
;
import
'outlined_button_theme.dart'
;
import
'page_transitions_theme.dart'
;
import
'page_transitions_theme.dart'
;
import
'popup_menu_theme.dart'
;
import
'popup_menu_theme.dart'
;
import
'slider_theme.dart'
;
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_button_theme.dart'
;
import
'text_theme.dart'
;
import
'text_theme.dart'
;
import
'time_picker_theme.dart'
;
import
'time_picker_theme.dart'
;
import
'toggle_buttons_theme.dart'
;
import
'toggle_buttons_theme.dart'
;
...
@@ -271,6 +274,9 @@ class ThemeData with Diagnosticable {
...
@@ -271,6 +274,9 @@ class ThemeData with Diagnosticable {
ButtonBarThemeData
buttonBarTheme
,
ButtonBarThemeData
buttonBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
TimePickerThemeData
timePickerTheme
,
TimePickerThemeData
timePickerTheme
,
TextButtonThemeData
textButtonTheme
,
ContainedButtonThemeData
containedButtonTheme
,
OutlinedButtonThemeData
outlinedButtonTheme
,
bool
fixTextFieldOutlineLabel
,
bool
fixTextFieldOutlineLabel
,
})
{
})
{
assert
(
colorScheme
?.
brightness
==
null
||
brightness
==
null
||
colorScheme
.
brightness
==
brightness
);
assert
(
colorScheme
?.
brightness
==
null
||
brightness
==
null
||
colorScheme
.
brightness
==
brightness
);
...
@@ -383,7 +389,9 @@ class ThemeData with Diagnosticable {
...
@@ -383,7 +389,9 @@ class ThemeData with Diagnosticable {
buttonBarTheme
??=
const
ButtonBarThemeData
();
buttonBarTheme
??=
const
ButtonBarThemeData
();
bottomNavigationBarTheme
??=
const
BottomNavigationBarThemeData
();
bottomNavigationBarTheme
??=
const
BottomNavigationBarThemeData
();
timePickerTheme
??=
const
TimePickerThemeData
();
timePickerTheme
??=
const
TimePickerThemeData
();
textButtonTheme
??=
const
TextButtonThemeData
();
containedButtonTheme
??=
const
ContainedButtonThemeData
();
outlinedButtonTheme
??=
const
OutlinedButtonThemeData
();
fixTextFieldOutlineLabel
??=
false
;
fixTextFieldOutlineLabel
??=
false
;
return
ThemeData
.
raw
(
return
ThemeData
.
raw
(
...
@@ -452,6 +460,9 @@ class ThemeData with Diagnosticable {
...
@@ -452,6 +460,9 @@ class ThemeData with Diagnosticable {
buttonBarTheme:
buttonBarTheme
,
buttonBarTheme:
buttonBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
,
timePickerTheme:
timePickerTheme
,
timePickerTheme:
timePickerTheme
,
textButtonTheme:
textButtonTheme
,
containedButtonTheme:
containedButtonTheme
,
outlinedButtonTheme:
outlinedButtonTheme
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -532,6 +543,9 @@ class ThemeData with Diagnosticable {
...
@@ -532,6 +543,9 @@ class ThemeData with Diagnosticable {
@required
this
.
buttonBarTheme
,
@required
this
.
buttonBarTheme
,
@required
this
.
bottomNavigationBarTheme
,
@required
this
.
bottomNavigationBarTheme
,
@required
this
.
timePickerTheme
,
@required
this
.
timePickerTheme
,
@required
this
.
textButtonTheme
,
@required
this
.
containedButtonTheme
,
@required
this
.
outlinedButtonTheme
,
@required
this
.
fixTextFieldOutlineLabel
,
@required
this
.
fixTextFieldOutlineLabel
,
})
:
assert
(
visualDensity
!=
null
),
})
:
assert
(
visualDensity
!=
null
),
assert
(
primaryColor
!=
null
),
assert
(
primaryColor
!=
null
),
...
@@ -595,6 +609,9 @@ class ThemeData with Diagnosticable {
...
@@ -595,6 +609,9 @@ class ThemeData with Diagnosticable {
assert
(
buttonBarTheme
!=
null
),
assert
(
buttonBarTheme
!=
null
),
assert
(
bottomNavigationBarTheme
!=
null
),
assert
(
bottomNavigationBarTheme
!=
null
),
assert
(
timePickerTheme
!=
null
),
assert
(
timePickerTheme
!=
null
),
assert
(
textButtonTheme
!=
null
),
assert
(
containedButtonTheme
!=
null
),
assert
(
outlinedButtonTheme
!=
null
),
assert
(
fixTextFieldOutlineLabel
!=
null
);
assert
(
fixTextFieldOutlineLabel
!=
null
);
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
...
@@ -1044,6 +1061,18 @@ class ThemeData with Diagnosticable {
...
@@ -1044,6 +1061,18 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of time picker widgets.
/// A theme for customizing the appearance and layout of time picker widgets.
final
TimePickerThemeData
timePickerTheme
;
final
TimePickerThemeData
timePickerTheme
;
/// A theme for customizing the appearance and internal layout of
/// [TextButton]s.
final
TextButtonThemeData
textButtonTheme
;
/// A theme for customizing the appearance and internal layout of
/// [ContainedButton]s
final
ContainedButtonThemeData
containedButtonTheme
;
/// A theme for customizing the appearance and internal layout of
/// [OutlinedButton]s.
final
OutlinedButtonThemeData
outlinedButtonTheme
;
/// A temporary flag to allow apps to opt-in to a
/// A temporary flag to allow apps to opt-in to a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
...
@@ -1126,6 +1155,9 @@ class ThemeData with Diagnosticable {
...
@@ -1126,6 +1155,9 @@ class ThemeData with Diagnosticable {
ButtonBarThemeData
buttonBarTheme
,
ButtonBarThemeData
buttonBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
BottomNavigationBarThemeData
bottomNavigationBarTheme
,
TimePickerThemeData
timePickerTheme
,
TimePickerThemeData
timePickerTheme
,
TextButtonThemeData
textButtonTheme
,
ContainedButtonThemeData
containedButtonTheme
,
OutlinedButtonThemeData
outlinedButtonTheme
,
bool
fixTextFieldOutlineLabel
,
bool
fixTextFieldOutlineLabel
,
})
{
})
{
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
...
@@ -1195,6 +1227,9 @@ class ThemeData with Diagnosticable {
...
@@ -1195,6 +1227,9 @@ class ThemeData with Diagnosticable {
buttonBarTheme:
buttonBarTheme
??
this
.
buttonBarTheme
,
buttonBarTheme:
buttonBarTheme
??
this
.
buttonBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
??
this
.
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
bottomNavigationBarTheme
??
this
.
bottomNavigationBarTheme
,
timePickerTheme:
timePickerTheme
??
this
.
timePickerTheme
,
timePickerTheme:
timePickerTheme
??
this
.
timePickerTheme
,
textButtonTheme:
textButtonTheme
??
this
.
textButtonTheme
,
containedButtonTheme:
containedButtonTheme
??
this
.
containedButtonTheme
,
outlinedButtonTheme:
outlinedButtonTheme
??
this
.
outlinedButtonTheme
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
??
this
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
fixTextFieldOutlineLabel
??
this
.
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -1342,6 +1377,9 @@ class ThemeData with Diagnosticable {
...
@@ -1342,6 +1377,9 @@ class ThemeData with Diagnosticable {
buttonBarTheme:
ButtonBarThemeData
.
lerp
(
a
.
buttonBarTheme
,
b
.
buttonBarTheme
,
t
),
buttonBarTheme:
ButtonBarThemeData
.
lerp
(
a
.
buttonBarTheme
,
b
.
buttonBarTheme
,
t
),
bottomNavigationBarTheme:
BottomNavigationBarThemeData
.
lerp
(
a
.
bottomNavigationBarTheme
,
b
.
bottomNavigationBarTheme
,
t
),
bottomNavigationBarTheme:
BottomNavigationBarThemeData
.
lerp
(
a
.
bottomNavigationBarTheme
,
b
.
bottomNavigationBarTheme
,
t
),
timePickerTheme:
TimePickerThemeData
.
lerp
(
a
.
timePickerTheme
,
b
.
timePickerTheme
,
t
),
timePickerTheme:
TimePickerThemeData
.
lerp
(
a
.
timePickerTheme
,
b
.
timePickerTheme
,
t
),
textButtonTheme:
TextButtonThemeData
.
lerp
(
a
.
textButtonTheme
,
b
.
textButtonTheme
,
t
),
containedButtonTheme:
ContainedButtonThemeData
.
lerp
(
a
.
containedButtonTheme
,
b
.
containedButtonTheme
,
t
),
outlinedButtonTheme:
OutlinedButtonThemeData
.
lerp
(
a
.
outlinedButtonTheme
,
b
.
outlinedButtonTheme
,
t
),
fixTextFieldOutlineLabel:
t
<
0.5
?
a
.
fixTextFieldOutlineLabel
:
b
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
t
<
0.5
?
a
.
fixTextFieldOutlineLabel
:
b
.
fixTextFieldOutlineLabel
,
);
);
}
}
...
@@ -1417,6 +1455,9 @@ class ThemeData with Diagnosticable {
...
@@ -1417,6 +1455,9 @@ class ThemeData with Diagnosticable {
&&
other
.
buttonBarTheme
==
buttonBarTheme
&&
other
.
buttonBarTheme
==
buttonBarTheme
&&
other
.
bottomNavigationBarTheme
==
bottomNavigationBarTheme
&&
other
.
bottomNavigationBarTheme
==
bottomNavigationBarTheme
&&
other
.
timePickerTheme
==
timePickerTheme
&&
other
.
timePickerTheme
==
timePickerTheme
&&
other
.
textButtonTheme
==
textButtonTheme
&&
other
.
containedButtonTheme
==
containedButtonTheme
&&
other
.
outlinedButtonTheme
==
outlinedButtonTheme
&&
other
.
fixTextFieldOutlineLabel
==
fixTextFieldOutlineLabel
;
&&
other
.
fixTextFieldOutlineLabel
==
fixTextFieldOutlineLabel
;
}
}
...
@@ -1491,6 +1532,9 @@ class ThemeData with Diagnosticable {
...
@@ -1491,6 +1532,9 @@ class ThemeData with Diagnosticable {
buttonBarTheme
,
buttonBarTheme
,
bottomNavigationBarTheme
,
bottomNavigationBarTheme
,
timePickerTheme
,
timePickerTheme
,
textButtonTheme
,
containedButtonTheme
,
outlinedButtonTheme
,
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel
,
];
];
return
hashList
(
values
);
return
hashList
(
values
);
...
@@ -1562,6 +1606,9 @@ class ThemeData with Diagnosticable {
...
@@ -1562,6 +1606,9 @@ class ThemeData with Diagnosticable {
properties
.
add
(
DiagnosticsProperty
<
ButtonBarThemeData
>(
'buttonBarTheme'
,
buttonBarTheme
,
defaultValue:
defaultData
.
buttonBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ButtonBarThemeData
>(
'buttonBarTheme'
,
buttonBarTheme
,
defaultValue:
defaultData
.
buttonBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
TimePickerThemeData
>(
'timePickerTheme'
,
timePickerTheme
,
defaultValue:
defaultData
.
timePickerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
TimePickerThemeData
>(
'timePickerTheme'
,
timePickerTheme
,
defaultValue:
defaultData
.
timePickerTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
BottomNavigationBarThemeData
>(
'bottomNavigationBarTheme'
,
bottomNavigationBarTheme
,
defaultValue:
defaultData
.
bottomNavigationBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
BottomNavigationBarThemeData
>(
'bottomNavigationBarTheme'
,
bottomNavigationBarTheme
,
defaultValue:
defaultData
.
bottomNavigationBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
TextButtonThemeData
>(
'textButtonTheme'
,
textButtonTheme
,
defaultValue:
defaultData
.
textButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ContainedButtonThemeData
>(
'containedButtonTheme'
,
containedButtonTheme
,
defaultValue:
defaultData
.
containedButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
OutlinedButtonThemeData
>(
'outlinedButtonTheme'
,
outlinedButtonTheme
,
defaultValue:
defaultData
.
outlinedButtonTheme
,
level:
DiagnosticLevel
.
debug
));
}
}
}
}
...
...
packages/flutter/test/material/button_style_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
test
(
'ButtonStyle copyWith, merge, ==, hashCode basics'
,
()
{
expect
(
const
ButtonStyle
(),
const
ButtonStyle
().
copyWith
());
expect
(
const
ButtonStyle
().
merge
(
const
ButtonStyle
()),
const
ButtonStyle
());
expect
(
const
ButtonStyle
().
hashCode
,
const
ButtonStyle
().
copyWith
().
hashCode
);
});
test
(
'ButtonStyle defaults'
,
()
{
const
ButtonStyle
style
=
ButtonStyle
();
expect
(
style
.
textStyle
,
null
);
expect
(
style
.
backgroundColor
,
null
);
expect
(
style
.
foregroundColor
,
null
);
expect
(
style
.
overlayColor
,
null
);
expect
(
style
.
elevation
,
null
);
expect
(
style
.
padding
,
null
);
expect
(
style
.
minimumSize
,
null
);
expect
(
style
.
side
,
null
);
expect
(
style
.
shape
,
null
);
expect
(
style
.
mouseCursor
,
null
);
expect
(
style
.
visualDensity
,
null
);
expect
(
style
.
tapTargetSize
,
null
);
expect
(
style
.
animationDuration
,
null
);
expect
(
style
.
enableFeedback
,
null
);
});
testWidgets
(
'Default ButtonStyle debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
ButtonStyle
().
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
(
'ButtonStyle debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
ButtonStyle
(
textStyle:
MaterialStateProperty
.
all
<
TextStyle
>(
const
TextStyle
(
fontSize:
10.0
)),
backgroundColor:
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff1
)),
foregroundColor:
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff2
)),
overlayColor:
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff3
)),
elevation:
MaterialStateProperty
.
all
<
double
>(
1.5
),
padding:
MaterialStateProperty
.
all
<
EdgeInsets
>(
const
EdgeInsets
.
all
(
1.0
)),
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
1.0
,
2.0
)),
side:
MaterialStateProperty
.
all
<
BorderSide
>(
const
BorderSide
(
width:
4.0
,
color:
Color
(
0xfffffff4
))),
shape:
MaterialStateProperty
.
all
<
OutlinedBorder
>(
const
StadiumBorder
()),
mouseCursor:
MaterialStateProperty
.
all
<
MouseCursor
>(
SystemMouseCursors
.
forbidden
),
tapTargetSize:
MaterialTapTargetSize
.
shrinkWrap
,
animationDuration:
const
Duration
(
seconds:
1
),
enableFeedback:
true
,
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
=>
!
node
.
isFiltered
(
DiagnosticLevel
.
info
))
.
map
((
DiagnosticsNode
node
)
=>
node
.
toString
())
.
toList
();
expect
(
description
,
<
String
>[
'textStyle: MaterialStateProperty.all(TextStyle(inherit: true, size: 10.0))'
,
'backgroundColor: MaterialStateProperty.all(Color(0xfffffff1))'
,
'foregroundColor: MaterialStateProperty.all(Color(0xfffffff2))'
,
'overlayColor: MaterialStateProperty.all(Color(0xfffffff3))'
,
'elevation: MaterialStateProperty.all(1.5)'
,
'padding: MaterialStateProperty.all(EdgeInsets.all(1.0))'
,
'minimumSize: MaterialStateProperty.all(Size(1.0, 2.0))'
,
'side: MaterialStateProperty.all(BorderSide(Color(0xfffffff4), 4.0, BorderStyle.solid))'
,
'shape: MaterialStateProperty.all(StadiumBorder(BorderSide(Color(0xff000000), 0.0, BorderStyle.none)))'
,
'mouseCursor: MaterialStateProperty.all(SystemMouseCursor(forbidden))'
,
'tapTargetSize: shrinkWrap'
,
'animationDuration: 0:00:01.000000'
,
'enableFeedback: true'
,
]);
});
testWidgets
(
'ButtonStyle copyWith, merge'
,
(
WidgetTester
tester
)
async
{
final
MaterialStateProperty
<
TextStyle
>
textStyle
=
MaterialStateProperty
.
all
<
TextStyle
>(
const
TextStyle
(
fontSize:
10
));
final
MaterialStateProperty
<
Color
>
backgroundColor
=
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff1
));
final
MaterialStateProperty
<
Color
>
foregroundColor
=
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff2
));
final
MaterialStateProperty
<
Color
>
overlayColor
=
MaterialStateProperty
.
all
<
Color
>(
const
Color
(
0xfffffff3
));
final
MaterialStateProperty
<
double
>
elevation
=
MaterialStateProperty
.
all
<
double
>(
1
);
final
MaterialStateProperty
<
EdgeInsets
>
padding
=
MaterialStateProperty
.
all
<
EdgeInsets
>(
const
EdgeInsets
.
all
(
1
));
final
MaterialStateProperty
<
Size
>
minimumSize
=
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
1
,
2
));
final
MaterialStateProperty
<
BorderSide
>
side
=
MaterialStateProperty
.
all
<
BorderSide
>(
const
BorderSide
());
final
MaterialStateProperty
<
OutlinedBorder
>
shape
=
MaterialStateProperty
.
all
<
OutlinedBorder
>(
const
StadiumBorder
());
final
MaterialStateProperty
<
MouseCursor
>
mouseCursor
=
MaterialStateProperty
.
all
<
MouseCursor
>(
SystemMouseCursors
.
forbidden
);
const
VisualDensity
visualDensity
=
VisualDensity
.
compact
;
const
MaterialTapTargetSize
tapTargetSize
=
MaterialTapTargetSize
.
shrinkWrap
;
const
Duration
animationDuration
=
Duration
(
seconds:
1
);
const
bool
enableFeedback
=
true
;
final
ButtonStyle
style
=
ButtonStyle
(
textStyle:
textStyle
,
backgroundColor:
backgroundColor
,
foregroundColor:
foregroundColor
,
overlayColor:
overlayColor
,
elevation:
elevation
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
expect
(
style
,
const
ButtonStyle
().
copyWith
(
textStyle:
textStyle
,
backgroundColor:
backgroundColor
,
foregroundColor:
foregroundColor
,
overlayColor:
overlayColor
,
elevation:
elevation
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
mouseCursor:
mouseCursor
,
visualDensity:
visualDensity
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
),
);
expect
(
style
,
const
ButtonStyle
().
merge
(
style
),
);
expect
(
style
.
copyWith
(),
style
.
merge
(
const
ButtonStyle
())
);
});
}
packages/flutter/test/material/contained_button_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
void
main
(
)
{
testWidgets
(
'ContainedButton defaults'
,
(
WidgetTester
tester
)
async
{
final
Finder
rawButtonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
ContainedButton
),
matching:
find
.
byType
(
Material
),
);
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
// Enabled ContainedButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Center
(
child:
ContainedButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
Material
material
=
tester
.
widget
<
Material
>(
rawButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
2
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onPrimary
);
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
(
ContainedButton
));
await
tester
.
startGesture
(
center
);
await
tester
.
pumpAndSettle
();
// Only elevation changes when enabled and pressed.
material
=
tester
.
widget
<
Material
>(
rawButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
8
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onPrimary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled ContainedButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
const
Center
(
child:
ContainedButton
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
material
=
tester
.
widget
<
Material
>(
rawButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.12
));
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
});
testWidgets
(
'Default ContainedButton meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Scaffold
(
body:
Center
(
child:
ContainedButton
(
child:
const
Text
(
'ContainedButton'
),
onPressed:
()
{
},
focusNode:
focusNode
,
),
),
),
),
);
// 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
(
ContainedButton
));
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
));
},
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/44115
semanticsEnabled:
true
,
);
testWidgets
(
'ContainedButton 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:
ContainedButtonTheme
(
data:
ContainedButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
ContainedButton
(
child:
const
Text
(
'ContainedButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
);
},
),
),
),
),
),
);
Color
textColor
()
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
'ContainedButton'
)).
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
(
ContainedButton
));
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
(
'ContainedButton 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:
ContainedButtonTheme
(
data:
ContainedButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
ContainedButton
.
icon
(
key:
buttonKey
,
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'ContainedButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
);
},
),
),
),
),
),
);
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
(
'ContainedButton onPressed and onLongPress callbacks are correctly called when non-null'
,
(
WidgetTester
tester
)
async
{
bool
wasPressed
;
Finder
containedButton
;
Widget
buildFrame
({
VoidCallback
onPressed
,
VoidCallback
onLongPress
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ContainedButton
(
child:
const
Text
(
'button'
),
onPressed:
onPressed
,
onLongPress:
onLongPress
,
),
);
}
// onPressed not null, onLongPress null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
()
{
wasPressed
=
true
;
},
onLongPress:
null
),
);
containedButton
=
find
.
byType
(
ContainedButton
);
expect
(
tester
.
widget
<
ContainedButton
>(
containedButton
).
enabled
,
true
);
await
tester
.
tap
(
containedButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress not null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
()
{
wasPressed
=
true
;
}),
);
containedButton
=
find
.
byType
(
ContainedButton
);
expect
(
tester
.
widget
<
ContainedButton
>(
containedButton
).
enabled
,
true
);
await
tester
.
longPress
(
containedButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress null.
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
null
),
);
containedButton
=
find
.
byType
(
ContainedButton
);
expect
(
tester
.
widget
<
ContainedButton
>(
containedButton
).
enabled
,
false
);
});
testWidgets
(
'ContainedButton onPressed and onLongPress callbacks are distinctly recognized'
,
(
WidgetTester
tester
)
async
{
bool
didPressButton
=
false
;
bool
didLongPressButton
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ContainedButton
(
onPressed:
()
{
didPressButton
=
true
;
},
onLongPress:
()
{
didLongPressButton
=
true
;
},
child:
const
Text
(
'button'
),
),
),
);
final
Finder
containedButton
=
find
.
byType
(
ContainedButton
);
expect
(
tester
.
widget
<
ContainedButton
>(
containedButton
).
enabled
,
true
);
expect
(
didPressButton
,
isFalse
);
await
tester
.
tap
(
containedButton
);
expect
(
didPressButton
,
isTrue
);
expect
(
didLongPressButton
,
isFalse
);
await
tester
.
longPress
(
containedButton
);
expect
(
didLongPressButton
,
isTrue
);
});
testWidgets
(
'Does ContainedButton work with hover'
,
(
WidgetTester
tester
)
async
{
const
Color
hoverColor
=
Color
(
0xff001122
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ContainedButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
hovered
)
?
hoverColor
:
null
;
}),
),
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
ContainedButton
)));
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
(
'Does ContainedButton work with focus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'ContainedButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ContainedButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>((
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}),
),
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 ContainedButton work with autofocus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'ContainedButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ContainedButton
(
autofocus:
true
,
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
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
(
'Does ContainedButton contribute semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
ContainedButton
(
style:
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the semantics tree's rect and transform
// match the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
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
(
'ContainedButton size is configurable by ThemeData.materialTapTargetSize'
,
(
WidgetTester
tester
)
async
{
final
ButtonStyle
style
=
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
);
Widget
buildFrame
(
MaterialTapTargetSize
tapTargetSize
,
Key
key
)
{
return
Theme
(
data:
ThemeData
(
materialTapTargetSize:
tapTargetSize
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
ContainedButton
(
key:
key
,
style:
style
,
child:
const
SizedBox
(
width:
50.0
,
height:
8.0
),
onPressed:
()
{
},
),
),
),
),
);
}
final
Key
key1
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
padded
,
key1
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key1
)),
const
Size
(
88.0
,
48.0
));
final
Key
key2
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
shrinkWrap
,
key2
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key2
)),
const
Size
(
88.0
,
36.0
));
});
testWidgets
(
'ContainedButton has no clip by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
ContainedButton
(
onPressed:
()
{
/* to make sure the button is enabled */
},
child:
const
Text
(
'button'
),
),
),
),
);
expect
(
tester
.
renderObject
(
find
.
byType
(
ContainedButton
)),
paintsExactlyCountTimes
(
#clipPath
,
0
),
);
});
testWidgets
(
'ContainedButton 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
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
Center
(
child:
ContainedButton
(
style:
ButtonStyle
(
visualDensity:
visualDensity
,
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
key:
key
,
onPressed:
()
{},
child:
useText
?
const
Text
(
'Text'
,
key:
childKey
)
:
Container
(
key:
childKey
,
width:
100
,
height:
100
,
color:
const
Color
(
0xffff0000
)),
),
),
),
),
);
}
await
buildTest
(
const
VisualDensity
());
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
(
const
VisualDensity
(),
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
)));
});
testWidgets
(
'ContainedButton.icon responds to applied padding'
,
(
WidgetTester
tester
)
async
{
const
Key
buttonKey
=
Key
(
'test'
);
const
Key
labelKey
=
Key
(
'label'
);
await
tester
.
pumpWidget
(
// When textDirection is set to TextDirection.ltr, the label appears on the
// right side of the icon. This is important in determining whether the
// horizontal padding is applied correctly later on
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
ContainedButton
.
icon
(
key:
buttonKey
,
style:
ButtonStyle
(
padding:
MaterialStateProperty
.
all
<
EdgeInsets
>(
const
EdgeInsets
.
fromLTRB
(
16
,
5
,
10
,
12
)),
),
onPressed:
()
{},
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'Hello'
,
key:
labelKey
,
),
),
),
),
),
);
final
Rect
paddingRect
=
tester
.
getRect
(
find
.
byType
(
Padding
));
final
Rect
labelRect
=
tester
.
getRect
(
find
.
byKey
(
labelKey
));
final
Rect
iconRect
=
tester
.
getRect
(
find
.
byType
(
Icon
));
// The right padding should be applied on the right of the label, whereas the
// left padding should be applied on the left side of the icon.
expect
(
paddingRect
.
right
,
labelRect
.
right
+
10
);
expect
(
paddingRect
.
left
,
iconRect
.
left
-
16
);
// Use the taller widget to check the top and bottom padding.
final
Rect
tallerWidget
=
iconRect
.
height
>
labelRect
.
height
?
iconRect
:
labelRect
;
expect
(
paddingRect
.
top
,
tallerWidget
.
top
-
5
);
expect
(
paddingRect
.
bottom
,
tallerWidget
.
bottom
+
12
);
});
group
(
'Default ContainedButton padding for textScaleFactor, textDirection'
,
()
{
const
ValueKey
<
String
>
buttonKey
=
ValueKey
<
String
>(
'button'
);
const
ValueKey
<
String
>
labelKey
=
ValueKey
<
String
>(
'label'
);
const
ValueKey
<
String
>
iconKey
=
ValueKey
<
String
>(
'icon'
);
const
List
<
double
>
textScaleFactorOptions
=
<
double
>[
0.5
,
1.0
,
1.25
,
1.5
,
2.0
,
2.5
,
3.0
,
4.0
];
const
List
<
TextDirection
>
textDirectionOptions
=
<
TextDirection
>[
TextDirection
.
ltr
,
TextDirection
.
rtl
];
const
List
<
Widget
>
iconOptions
=
<
Widget
>[
null
,
Icon
(
Icons
.
add
,
size:
18
,
key:
iconKey
)];
// Expected values for each textScaleFactor.
final
Map
<
double
,
double
>
paddingWithoutIconStart
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithoutIconEnd
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithIconStart
=
<
double
,
double
>{
0.5
:
12
,
1
:
12
,
1.25
:
11
,
1.5
:
10
,
2
:
8
,
2.5
:
8
,
3
:
8
,
4
:
8
,
};
final
Map
<
double
,
double
>
paddingWithIconEnd
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingWithIconGap
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
7
,
1.5
:
6
,
2
:
4
,
2.5
:
4
,
3
:
4
,
4
:
4
,
};
Rect
globalBounds
(
RenderBox
renderBox
)
{
final
Offset
topLeft
=
renderBox
.
localToGlobal
(
Offset
.
zero
);
return
topLeft
&
renderBox
.
size
;
}
/// Computes the padding between two [Rect]s, one inside the other.
EdgeInsets
paddingBetween
({
Rect
parent
,
Rect
child
})
{
assert
(
parent
.
intersect
(
child
)
==
child
);
return
EdgeInsets
.
fromLTRB
(
child
.
left
-
parent
.
left
,
child
.
top
-
parent
.
top
,
parent
.
right
-
child
.
right
,
parent
.
bottom
-
child
.
bottom
,
);
}
for
(
final
double
textScaleFactor
in
textScaleFactorOptions
)
{
for
(
final
TextDirection
textDirection
in
textDirectionOptions
)
{
for
(
final
Widget
icon
in
iconOptions
)
{
final
String
testName
=
'ContainedButton'
', text scale
$textScaleFactor
'
'
${icon != null ? ", with icon" : ""}
'
'
${textDirection == TextDirection.rtl ? ", RTL" : ""}
'
;
testWidgets
(
testName
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
textScaleFactor
,
),
child:
Directionality
(
textDirection:
textDirection
,
child:
Scaffold
(
body:
Center
(
child:
icon
==
null
?
ContainedButton
(
key:
buttonKey
,
onPressed:
()
{},
child:
const
Text
(
'button'
,
key:
labelKey
),
)
:
ContainedButton
.
icon
(
key:
buttonKey
,
onPressed:
()
{},
icon:
icon
,
label:
const
Text
(
'button'
,
key:
labelKey
),
),
),
),
),
);
},
),
),
);
final
Element
paddingElement
=
tester
.
element
(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
Directionality
.
of
(
paddingElement
),
textDirection
);
final
Padding
paddingWidget
=
paddingElement
.
widget
as
Padding
;
// Compute expected padding, and check.
final
double
expectedStart
=
icon
!=
null
?
paddingWithIconStart
[
textScaleFactor
]
:
paddingWithoutIconStart
[
textScaleFactor
];
final
double
expectedEnd
=
icon
!=
null
?
paddingWithIconEnd
[
textScaleFactor
]
:
paddingWithoutIconEnd
[
textScaleFactor
];
final
EdgeInsets
expectedPadding
=
EdgeInsetsDirectional
.
fromSTEB
(
expectedStart
,
0
,
expectedEnd
,
0
)
.
resolve
(
textDirection
);
expect
(
paddingWidget
.
padding
.
resolve
(
textDirection
),
expectedPadding
);
// Measure padding in terms of the difference between the button and its label child
// and check that.
final
RenderBox
labelRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
labelKey
));
final
Rect
labelBounds
=
globalBounds
(
labelRenderBox
);
final
RenderBox
iconRenderBox
=
icon
==
null
?
null
:
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
iconKey
));
final
Rect
iconBounds
=
icon
==
null
?
null
:
globalBounds
(
iconRenderBox
);
final
Rect
childBounds
=
icon
==
null
?
labelBounds
:
labelBounds
.
expandToInclude
(
iconBounds
);
// We measure the `InkResponse` descendant of the button
// element, because the button has a larger `RenderBox`
// which accommodates the minimum tap target with a height
// of 48.
final
RenderBox
buttonRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
is
InkResponse
,
),
),
);
final
Rect
buttonBounds
=
globalBounds
(
buttonRenderBox
);
final
EdgeInsets
visuallyMeasuredPadding
=
paddingBetween
(
parent:
buttonBounds
,
child:
childBounds
,
);
// Since there is a requirement of a minimum width of 64
// and a minimum height of 36 on material buttons, the visual
// padding of smaller buttons may not match their settings.
// Therefore, we only test buttons that are large enough.
if
(
buttonBounds
.
width
>
64
)
{
expect
(
visuallyMeasuredPadding
.
left
,
expectedPadding
.
left
,
);
expect
(
visuallyMeasuredPadding
.
right
,
expectedPadding
.
right
,
);
}
if
(
buttonBounds
.
height
>
36
)
{
expect
(
visuallyMeasuredPadding
.
top
,
expectedPadding
.
top
,
);
expect
(
visuallyMeasuredPadding
.
bottom
,
expectedPadding
.
bottom
,
);
}
// Check the gap between the icon and the label
if
(
icon
!=
null
)
{
final
double
gapWidth
=
textDirection
==
TextDirection
.
ltr
?
labelBounds
.
left
-
iconBounds
.
right
:
iconBounds
.
left
-
labelBounds
.
right
;
expect
(
gapWidth
,
paddingWithIconGap
[
textScaleFactor
]);
}
// Check the text's height - should be consistent with the textScaleFactor.
final
RenderBox
textRenderObject
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
labelKey
),
matching:
find
.
byElementPredicate
(
(
Element
element
)
=>
element
.
widget
is
RichText
,
),
),
);
final
double
textHeight
=
textRenderObject
.
paintBounds
.
size
.
height
;
final
double
expectedTextHeight
=
14
*
textScaleFactor
;
expect
(
textHeight
,
moreOrLessEquals
(
expectedTextHeight
,
epsilon:
0.5
));
});
}
}
}
});
testWidgets
(
'Override ContainedButton default padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
2
,
),
child:
Scaffold
(
body:
Center
(
child:
ContainedButton
(
style:
ContainedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
all
(
22
)),
onPressed:
()
{},
child:
const
Text
(
'ContainedButton'
)
),
),
),
);
},
),
),
);
final
Padding
paddingWidget
=
tester
.
widget
<
Padding
>(
find
.
descendant
(
of:
find
.
byType
(
ContainedButton
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
paddingWidget
.
padding
,
const
EdgeInsets
.
all
(
22
));
});
}
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
;
}
packages/flutter/test/material/contained_button_theme_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Passing no ContainedButtonTheme returns defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Scaffold
(
body:
Center
(
child:
ContainedButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
ContainedButton
),
matching:
find
.
byType
(
Material
),
);
final
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
colorScheme
.
primary
);
expect
(
material
.
elevation
,
2
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4.0
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onPrimary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
});
group
(
'[Theme, TextTheme, ContainedButton style overrides]'
,
()
{
const
Color
primaryColor
=
Color
(
0xff000001
);
const
Color
onSurfaceColor
=
Color
(
0xff000002
);
const
Color
shadowColor
=
Color
(
0xff000004
);
const
Color
onPrimaryColor
=
Color
(
0xff000005
);
const
double
elevation
=
1
;
const
TextStyle
textStyle
=
TextStyle
(
fontSize:
12.0
);
const
EdgeInsets
padding
=
EdgeInsets
.
all
(
3
);
const
Size
minimumSize
=
Size
(
200
,
200
);
const
BorderSide
side
=
BorderSide
(
color:
Colors
.
green
,
width:
2
);
const
OutlinedBorder
shape
=
RoundedRectangleBorder
(
side:
side
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
2
)));
const
MouseCursor
enabledMouseCursor
=
SystemMouseCursors
.
text
;
const
MouseCursor
disabledMouseCursor
=
SystemMouseCursors
.
grab
;
const
MaterialTapTargetSize
tapTargetSize
=
MaterialTapTargetSize
.
shrinkWrap
;
const
Duration
animationDuration
=
Duration
(
milliseconds:
25
);
const
bool
enableFeedback
=
false
;
final
ButtonStyle
style
=
ContainedButton
.
styleFrom
(
primary:
primaryColor
,
onPrimary:
onPrimaryColor
,
onSurface:
onSurfaceColor
,
shadowColor:
shadowColor
,
elevation:
elevation
,
textStyle:
textStyle
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
enabledMouseCursor:
enabledMouseCursor
,
disabledMouseCursor:
disabledMouseCursor
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
Widget
buildFrame
({
ButtonStyle
buttonStyle
,
ButtonStyle
themeStyle
,
ButtonStyle
overallStyle
})
{
final
Widget
child
=
Builder
(
builder:
(
BuildContext
context
)
{
return
ContainedButton
(
style:
buttonStyle
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
);
},
);
return
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()).
copyWith
(
containedButtonTheme:
ContainedButtonThemeData
(
style:
overallStyle
),
),
home:
Scaffold
(
body:
Center
(
// If the ContainedButtonTheme widget is present, it's used
// instead of the Theme's ThemeData.containedButtonTheme.
child:
themeStyle
==
null
?
child
:
ContainedButtonTheme
(
data:
ContainedButtonThemeData
(
style:
themeStyle
),
child:
child
,
),
),
),
);
}
final
Finder
findMaterial
=
find
.
descendant
(
of:
find
.
byType
(
ContainedButton
),
matching:
find
.
byType
(
Material
),
);
final
Finder
findInkWell
=
find
.
descendant
(
of:
find
.
byType
(
ContainedButton
),
matching:
find
.
byType
(
InkWell
),
);
const
Set
<
MaterialState
>
enabled
=
<
MaterialState
>{};
const
Set
<
MaterialState
>
disabled
=
<
MaterialState
>{
MaterialState
.
disabled
};
const
Set
<
MaterialState
>
hovered
=
<
MaterialState
>{
MaterialState
.
hovered
};
const
Set
<
MaterialState
>
focused
=
<
MaterialState
>{
MaterialState
.
focused
};
const
Set
<
MaterialState
>
pressed
=
<
MaterialState
>{
MaterialState
.
pressed
};
void
checkButton
(
WidgetTester
tester
)
{
final
Material
material
=
tester
.
widget
<
Material
>(
findMaterial
);
final
InkWell
inkWell
=
tester
.
widget
<
InkWell
>(
findInkWell
);
expect
(
material
.
textStyle
.
color
,
onPrimaryColor
);
expect
(
material
.
textStyle
.
fontSize
,
12
);
expect
(
material
.
color
,
primaryColor
);
expect
(
material
.
shadowColor
,
shadowColor
);
expect
(
material
.
elevation
,
elevation
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
enabled
),
enabledMouseCursor
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
disabled
),
disabledMouseCursor
);
expect
(
inkWell
.
overlayColor
.
resolve
(
hovered
),
onPrimaryColor
.
withOpacity
(
0.08
));
expect
(
inkWell
.
overlayColor
.
resolve
(
focused
),
onPrimaryColor
.
withOpacity
(
0.24
));
expect
(
inkWell
.
overlayColor
.
resolve
(
pressed
),
onPrimaryColor
.
withOpacity
(
0.24
));
expect
(
inkWell
.
enableFeedback
,
enableFeedback
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
shape
,
shape
);
expect
(
material
.
animationDuration
,
animationDuration
);
expect
(
tester
.
getSize
(
find
.
byType
(
ContainedButton
)),
const
Size
(
200
,
200
));
}
testWidgets
(
'Button style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
themeStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
// Same as the previous tests with empty ButtonStyle's instead of null.
testWidgets
(
'Button style overrides defaults, empty theme and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
,
themeStyle:
const
ButtonStyle
(),
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults, empty button and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
style
,
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults, null theme and empty overall style'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
null
,
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
});
}
packages/flutter/test/material/outlined_button_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
void
main
(
)
{
testWidgets
(
'OutlinedButton defaults'
,
(
WidgetTester
tester
)
async
{
final
Finder
rawButtonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
OutlinedButton
),
matching:
find
.
byType
(
Material
),
);
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
// Enabled OutlinedButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Center
(
child:
OutlinedButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
Material
material
=
tester
.
widget
<
Material
>(
rawButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
side:
BorderSide
(
width:
1
,
color:
colorScheme
.
onSurface
.
withOpacity
(
0.12
),
),
borderRadius:
BorderRadius
.
circular
(
4.0
),
));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
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
(
OutlinedButton
));
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:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
side:
BorderSide
(
width:
1
,
color:
colorScheme
.
onSurface
.
withOpacity
(
0.12
),
),
borderRadius:
BorderRadius
.
circular
(
4.0
),
));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled OutlinedButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
const
Center
(
child:
OutlinedButton
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
material
=
tester
.
widget
<
Material
>(
rawButtonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
side:
BorderSide
(
width:
1
,
color:
colorScheme
.
onSurface
.
withOpacity
(
0.12
),
),
borderRadius:
BorderRadius
.
circular
(
4.0
),
));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
});
testWidgets
(
'Does OutlinedButton work with hover'
,
(
WidgetTester
tester
)
async
{
const
Color
hoverColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
hovered
)
?
hoverColor
:
null
;
}
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
OutlinedButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
OutlinedButton
)));
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
hoverColor
));
gesture
.
removePointer
();
});
testWidgets
(
'Does OutlinedButton work with focus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'OutlinedButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
OutlinedButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
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 OutlinedButton work with autofocus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'OutlinedButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
OutlinedButton
(
autofocus:
true
,
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
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
(
'Default OutlinedButton meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
child:
const
Text
(
'OutlinedButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
),
),
),
),
);
// 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
(
OutlinedButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
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
));
await
gesture
.
removePointer
();
},
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/44115
semanticsEnabled:
true
,
);
testWidgets
(
'OutlinedButton with colored theme meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
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
(
theme:
ThemeData
.
from
(
colorScheme:
ColorScheme
.
fromSwatch
(
primarySwatch:
Colors
.
blue
)),
home:
Scaffold
(
backgroundColor:
Colors
.
white
,
body:
Center
(
child:
OutlinedButtonTheme
(
data:
OutlinedButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
OutlinedButton
(
child:
const
Text
(
'OutlinedButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
);
},
),
),
),
),
),
);
// 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
(
OutlinedButton
));
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
semanticsEnabled:
true
,
);
testWidgets
(
'OutlinedButton 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:
OutlinedButton
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
onPressed:
()
{},
focusNode:
focusNode
,
child:
const
Text
(
'OutlinedButton'
),
),
),
),
),
);
Color
textColor
()
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
'OutlinedButton'
)).
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
(
OutlinedButton
));
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
(
'OutlinedButton 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
getIconColor
(
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:
OutlinedButton
.
icon
(
key:
buttonKey
,
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getIconColor
),
),
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'OutlinedButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
),
),
),
),
);
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
(
'OutlinedButton 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
);
BorderSide
getBorderSide
(
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
const
BorderSide
(
color:
pressedColor
,
width:
1
);
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
const
BorderSide
(
color:
hoverColor
,
width:
1
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
const
BorderSide
(
color:
focusedColor
,
width:
1
);
}
return
const
BorderSide
(
color:
defaultColor
,
width:
1
);
}
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
style:
ButtonStyle
(
side:
MaterialStateProperty
.
resolveWith
<
BorderSide
>(
getBorderSide
),
),
onPressed:
()
{},
focusNode:
focusNode
,
child:
const
Text
(
'OutlinedButton'
),
),
),
),
),
);
final
Finder
outlinedButton
=
find
.
byType
(
OutlinedButton
);
// Default, not disabled.
expect
(
outlinedButton
,
paints
..
drrect
(
color:
defaultColor
));
// Focused.
focusNode
.
requestFocus
();
await
tester
.
pumpAndSettle
();
expect
(
outlinedButton
,
paints
..
drrect
(
color:
focusedColor
));
// Hovered.
final
Offset
center
=
tester
.
getCenter
(
find
.
byType
(
OutlinedButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
addTearDown
(
gesture
.
removePointer
);
await
gesture
.
moveTo
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
outlinedButton
,
paints
..
drrect
(
color:
hoverColor
));
// Highlighted (pressed).
await
gesture
.
down
(
center
);
await
tester
.
pumpAndSettle
();
expect
(
outlinedButton
,
paints
..
drrect
(
color:
pressedColor
));
});
testWidgets
(
'OutlinedButton onPressed and onLongPress callbacks are correctly called when non-null'
,
(
WidgetTester
tester
)
async
{
bool
wasPressed
;
Finder
outlinedButton
;
Widget
buildFrame
({
VoidCallback
onPressed
,
VoidCallback
onLongPress
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
OutlinedButton
(
child:
const
Text
(
'button'
),
onPressed:
onPressed
,
onLongPress:
onLongPress
,
),
);
}
// onPressed not null, onLongPress null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
()
{
wasPressed
=
true
;
},
onLongPress:
null
),
);
outlinedButton
=
find
.
byType
(
OutlinedButton
);
expect
(
tester
.
widget
<
OutlinedButton
>(
outlinedButton
).
enabled
,
true
);
await
tester
.
tap
(
outlinedButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress not null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
()
{
wasPressed
=
true
;
}),
);
outlinedButton
=
find
.
byType
(
OutlinedButton
);
expect
(
tester
.
widget
<
OutlinedButton
>(
outlinedButton
).
enabled
,
true
);
await
tester
.
longPress
(
outlinedButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress null.
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
null
),
);
outlinedButton
=
find
.
byType
(
OutlinedButton
);
expect
(
tester
.
widget
<
OutlinedButton
>(
outlinedButton
).
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:
OutlinedButton
(
onPressed:
onPressed
,
child:
const
Text
(
'button'
)),
),
),
);
}
await
tester
.
pumpWidget
(
buildFrame
(()
{}));
await
tester
.
press
(
find
.
byType
(
OutlinedButton
));
await
tester
.
pumpAndSettle
();
await
tester
.
pumpWidget
(
buildFrame
(
null
));
await
tester
.
pumpAndSettle
();
});
testWidgets
(
'OutlinedButton shape and border component overrides'
,
(
WidgetTester
tester
)
async
{
const
Color
fillColor
=
Color
(
0xFF00FF00
);
const
BorderSide
disabledBorderSide
=
BorderSide
(
color:
Color
(
0xFFFF0000
),
width:
3
);
const
BorderSide
enabledBorderSide
=
BorderSide
(
color:
Color
(
0xFFFF00FF
),
width:
4
);
const
BorderSide
pressedBorderSide
=
BorderSide
(
color:
Color
(
0xFF0000FF
),
width:
5
);
Widget
buildFrame
({
VoidCallback
onPressed
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Theme
(
data:
ThemeData
(
materialTapTargetSize:
MaterialTapTargetSize
.
shrinkWrap
),
child:
Container
(
alignment:
Alignment
.
topLeft
,
child:
OutlinedButton
(
style:
OutlinedButton
.
styleFrom
(
shape:
const
RoundedRectangleBorder
(),
// default border radius is 0
backgroundColor:
fillColor
,
).
copyWith
(
side:
MaterialStateProperty
.
resolveWith
<
BorderSide
>((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
return
disabledBorderSide
;
if
(
states
.
contains
(
MaterialState
.
pressed
))
return
pressedBorderSide
;
return
enabledBorderSide
;
}),
),
clipBehavior:
Clip
.
antiAlias
,
onPressed:
onPressed
,
child:
const
Text
(
'button'
),
),
),
),
);
}
// 116 = 16 + 'button'.length * 14 + 16, horizontal padding = 16
const
Rect
clipRect
=
Rect
.
fromLTRB
(
0.0
,
0.0
,
116.0
,
36.0
);
final
Path
clipPath
=
Path
()..
addRect
(
clipRect
);
final
Finder
outlinedButton
=
find
.
byType
(
OutlinedButton
);
BorderSide
getBorderSide
()
{
final
OutlinedBorder
border
=
tester
.
widget
<
Material
>(
find
.
descendant
(
of:
outlinedButton
,
matching:
find
.
byType
(
Material
))
).
shape
as
OutlinedBorder
;
return
border
.
side
;
}
// Pump a button with a null onPressed callback to make it disabled.
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
),
);
// Expect that the button is disabled and painted with the disabled border color.
expect
(
tester
.
widget
<
OutlinedButton
>(
outlinedButton
).
enabled
,
false
);
expect
(
getBorderSide
(),
disabledBorderSide
);
_checkPhysicalLayer
(
tester
.
element
(
outlinedButton
),
fillColor
,
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
(
getBorderSide
(),
enabledBorderSide
);
final
Offset
center
=
tester
.
getCenter
(
outlinedButton
);
final
TestGesture
gesture
=
await
tester
.
startGesture
(
center
);
await
tester
.
pump
();
// start gesture
// Wait for the border's color to change to pressed
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
getBorderSide
(),
pressedBorderSide
);
_checkPhysicalLayer
(
tester
.
element
(
outlinedButton
),
fillColor
,
clipPath:
clipPath
,
clipRect:
clipRect
,
);
// Tap gesture completes, button returns to its initial configuration.
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
getBorderSide
(),
enabledBorderSide
);
_checkPhysicalLayer
(
tester
.
element
(
outlinedButton
),
fillColor
,
clipPath:
clipPath
,
clipRect:
clipRect
,
);
});
testWidgets
(
'OutlinedButton has no clip by default'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
buttonKey
=
GlobalKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
OutlinedButton
(
key:
buttonKey
,
onPressed:
()
{},
child:
const
Text
(
'ABC'
),
),
),
),
),
);
expect
(
tester
.
renderObject
(
find
.
byKey
(
buttonKey
)),
paintsExactlyCountTimes
(
#clipPath
,
0
),
);
});
testWidgets
(
'OutlinedButton contributes semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
OutlinedButton
(
style:
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
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
(
'OutlinedButton scales textScaleFactor'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
1.0
),
child:
Center
(
child:
OutlinedButton
(
style:
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
onPressed:
()
{},
child:
const
Text
(
'ABC'
),
),
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
OutlinedButton
)),
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:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
1.3
),
child:
Center
(
child:
OutlinedButton
(
style:
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the corresponding button size matches
// the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
onPressed:
()
{},
child:
const
Text
(
'ABC'
),
),
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
OutlinedButton
)),
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:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
3.0
),
child:
Center
(
child:
OutlinedButton
(
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
(
OutlinedButton
)).
width
,
isIn
(<
double
>[
133.0
,
134.0
]));
expect
(
tester
.
getSize
(
find
.
byType
(
OutlinedButton
)).
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
(
'OutlinedButton onPressed and onLongPress callbacks are distinctly recognized'
,
(
WidgetTester
tester
)
async
{
bool
didPressButton
=
false
;
bool
didLongPressButton
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
OutlinedButton
(
onPressed:
()
{
didPressButton
=
true
;
},
onLongPress:
()
{
didLongPressButton
=
true
;
},
child:
const
Text
(
'button'
),
),
),
);
final
Finder
outlinedButton
=
find
.
byType
(
OutlinedButton
);
expect
(
tester
.
widget
<
OutlinedButton
>(
outlinedButton
).
enabled
,
true
);
expect
(
didPressButton
,
isFalse
);
await
tester
.
tap
(
outlinedButton
);
expect
(
didPressButton
,
isTrue
);
expect
(
didLongPressButton
,
isFalse
);
await
tester
.
longPress
(
outlinedButton
);
expect
(
didLongPressButton
,
isTrue
);
});
testWidgets
(
'OutlinedButton 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
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
Center
(
child:
OutlinedButton
(
style:
ButtonStyle
(
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
(
const
VisualDensity
());
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
(
const
VisualDensity
(),
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
(
64
,
36
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
372.0
,
293.0
,
428.0
,
307.0
)));
});
group
(
'Default OutlinedButton padding for textScaleFactor, textDirection'
,
()
{
const
ValueKey
<
String
>
buttonKey
=
ValueKey
<
String
>(
'button'
);
const
ValueKey
<
String
>
labelKey
=
ValueKey
<
String
>(
'label'
);
const
ValueKey
<
String
>
iconKey
=
ValueKey
<
String
>(
'icon'
);
const
List
<
double
>
textScaleFactorOptions
=
<
double
>[
0.5
,
1.0
,
1.25
,
1.5
,
2.0
,
2.5
,
3.0
,
4.0
];
const
List
<
TextDirection
>
textDirectionOptions
=
<
TextDirection
>[
TextDirection
.
ltr
,
TextDirection
.
rtl
];
const
List
<
Widget
>
iconOptions
=
<
Widget
>[
null
,
Icon
(
Icons
.
add
,
size:
18
,
key:
iconKey
)];
// Expected values for each textScaleFactor.
final
Map
<
double
,
double
>
paddingVertical
=
<
double
,
double
>{
0.5
:
0
,
1
:
0
,
1.25
:
0
,
1.5
:
0
,
2
:
0
,
2.5
:
0
,
3
:
0
,
4
:
0
,
};
final
Map
<
double
,
double
>
paddingWithIconGap
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
7
,
1.5
:
6
,
2
:
4
,
2.5
:
4
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
paddingHorizontal
=
<
double
,
double
>{
0.5
:
16
,
1
:
16
,
1.25
:
14
,
1.5
:
12
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
Rect
globalBounds
(
RenderBox
renderBox
)
{
final
Offset
topLeft
=
renderBox
.
localToGlobal
(
Offset
.
zero
);
return
topLeft
&
renderBox
.
size
;
}
/// Computes the padding between two [Rect]s, one inside the other.
EdgeInsets
paddingBetween
({
Rect
parent
,
Rect
child
})
{
assert
(
parent
.
intersect
(
child
)
==
child
);
return
EdgeInsets
.
fromLTRB
(
child
.
left
-
parent
.
left
,
child
.
top
-
parent
.
top
,
parent
.
right
-
child
.
right
,
parent
.
bottom
-
child
.
bottom
,
);
}
for
(
final
double
textScaleFactor
in
textScaleFactorOptions
)
{
for
(
final
TextDirection
textDirection
in
textDirectionOptions
)
{
for
(
final
Widget
icon
in
iconOptions
)
{
final
String
testName
=
'OutlinedButton'
', text scale
$textScaleFactor
'
'
${icon != null ? ", with icon" : ""}
'
'
${textDirection == TextDirection.rtl ? ", RTL" : ""}
'
;
testWidgets
(
testName
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
textScaleFactor
,
),
child:
Directionality
(
textDirection:
textDirection
,
child:
Scaffold
(
body:
Center
(
child:
icon
==
null
?
OutlinedButton
(
key:
buttonKey
,
onPressed:
()
{},
child:
const
Text
(
'button'
,
key:
labelKey
),
)
:
OutlinedButton
.
icon
(
key:
buttonKey
,
onPressed:
()
{},
icon:
icon
,
label:
const
Text
(
'button'
,
key:
labelKey
),
),
),
),
),
);
},
),
),
);
final
Element
paddingElement
=
tester
.
element
(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
Directionality
.
of
(
paddingElement
),
textDirection
);
final
Padding
paddingWidget
=
paddingElement
.
widget
as
Padding
;
// Compute expected padding, and check.
final
double
expectedPaddingTop
=
paddingVertical
[
textScaleFactor
];
final
double
expectedPaddingBottom
=
paddingVertical
[
textScaleFactor
];
final
double
expectedPaddingStart
=
paddingHorizontal
[
textScaleFactor
];
final
double
expectedPaddingEnd
=
expectedPaddingStart
;
final
EdgeInsets
expectedPadding
=
EdgeInsetsDirectional
.
fromSTEB
(
expectedPaddingStart
,
expectedPaddingTop
,
expectedPaddingEnd
,
expectedPaddingBottom
,
).
resolve
(
textDirection
);
expect
(
paddingWidget
.
padding
.
resolve
(
textDirection
),
expectedPadding
);
// Measure padding in terms of the difference between the button and its label child
// and check that.
final
RenderBox
labelRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
labelKey
));
final
Rect
labelBounds
=
globalBounds
(
labelRenderBox
);
final
RenderBox
iconRenderBox
=
icon
==
null
?
null
:
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
iconKey
));
final
Rect
iconBounds
=
icon
==
null
?
null
:
globalBounds
(
iconRenderBox
);
final
Rect
childBounds
=
icon
==
null
?
labelBounds
:
labelBounds
.
expandToInclude
(
iconBounds
);
// We measure the `InkResponse` descendant of the button
// element, because the button has a larger `RenderBox`
// which accommodates the minimum tap target with a height
// of 48.
final
RenderBox
buttonRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
is
InkResponse
,
),
),
);
final
Rect
buttonBounds
=
globalBounds
(
buttonRenderBox
);
final
EdgeInsets
visuallyMeasuredPadding
=
paddingBetween
(
parent:
buttonBounds
,
child:
childBounds
,
);
// Since there is a requirement of a minimum width of 64
// and a minimum height of 36 on material buttons, the visual
// padding of smaller buttons may not match their settings.
// Therefore, we only test buttons that are large enough.
if
(
buttonBounds
.
width
>
64
)
{
expect
(
visuallyMeasuredPadding
.
left
,
expectedPadding
.
left
,
);
expect
(
visuallyMeasuredPadding
.
right
,
expectedPadding
.
right
,
);
}
if
(
buttonBounds
.
height
>
36
)
{
expect
(
visuallyMeasuredPadding
.
top
,
expectedPadding
.
top
,
);
expect
(
visuallyMeasuredPadding
.
bottom
,
expectedPadding
.
bottom
,
);
}
// Check the gap between the icon and the label
if
(
icon
!=
null
)
{
final
double
gapWidth
=
textDirection
==
TextDirection
.
ltr
?
labelBounds
.
left
-
iconBounds
.
right
:
iconBounds
.
left
-
labelBounds
.
right
;
expect
(
gapWidth
,
paddingWithIconGap
[
textScaleFactor
]);
}
// Check the text's height - should be consistent with the textScaleFactor.
final
RenderBox
textRenderObject
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
labelKey
),
matching:
find
.
byElementPredicate
(
(
Element
element
)
=>
element
.
widget
is
RichText
,
),
),
);
final
double
textHeight
=
textRenderObject
.
paintBounds
.
size
.
height
;
final
double
expectedTextHeight
=
14
*
textScaleFactor
;
expect
(
textHeight
,
moreOrLessEquals
(
expectedTextHeight
,
epsilon:
0.5
));
});
}
}
}
});
testWidgets
(
'Override OutlinedButton default padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
2
,
),
child:
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
all
(
22
)),
onPressed:
()
{},
child:
const
Text
(
'OutlinedButton'
)
),
),
),
);
},
),
),
);
final
Padding
paddingWidget
=
tester
.
widget
<
Padding
>(
find
.
descendant
(
of:
find
.
byType
(
OutlinedButton
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
paddingWidget
.
padding
,
const
EdgeInsets
.
all
(
22
));
});
}
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
;
}
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
;
}
packages/flutter/test/material/outlined_button_theme_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Passing no OutlinedButtonTheme returns defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Scaffold
(
body:
Center
(
child:
OutlinedButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
OutlinedButton
),
matching:
find
.
byType
(
Material
),
);
final
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
side:
BorderSide
(
width:
1
,
color:
colorScheme
.
onSurface
.
withOpacity
(
0.12
)),
borderRadius:
BorderRadius
.
circular
(
4.0
),
));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
});
group
(
'[Theme, TextTheme, OutlinedButton style overrides]'
,
()
{
const
Color
primaryColor
=
Color
(
0xff000001
);
const
Color
onSurfaceColor
=
Color
(
0xff000002
);
const
Color
backgroundColor
=
Color
(
0xff000003
);
const
Color
shadowColor
=
Color
(
0xff000004
);
const
double
elevation
=
3
;
const
TextStyle
textStyle
=
TextStyle
(
fontSize:
12.0
);
const
EdgeInsets
padding
=
EdgeInsets
.
all
(
3
);
const
Size
minimumSize
=
Size
(
200
,
200
);
const
BorderSide
side
=
BorderSide
(
color:
Colors
.
green
,
width:
2
);
const
OutlinedBorder
shape
=
RoundedRectangleBorder
(
side:
side
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
2
)));
const
MouseCursor
enabledMouseCursor
=
SystemMouseCursors
.
text
;
const
MouseCursor
disabledMouseCursor
=
SystemMouseCursors
.
grab
;
const
MaterialTapTargetSize
tapTargetSize
=
MaterialTapTargetSize
.
shrinkWrap
;
const
Duration
animationDuration
=
Duration
(
milliseconds:
25
);
const
bool
enableFeedback
=
false
;
final
ButtonStyle
style
=
OutlinedButton
.
styleFrom
(
primary:
primaryColor
,
onSurface:
onSurfaceColor
,
backgroundColor:
backgroundColor
,
shadowColor:
shadowColor
,
elevation:
elevation
,
textStyle:
textStyle
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
enabledMouseCursor:
enabledMouseCursor
,
disabledMouseCursor:
disabledMouseCursor
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
Widget
buildFrame
({
ButtonStyle
buttonStyle
,
ButtonStyle
themeStyle
,
ButtonStyle
overallStyle
})
{
final
Widget
child
=
Builder
(
builder:
(
BuildContext
context
)
{
return
OutlinedButton
(
style:
buttonStyle
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
);
},
);
return
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()).
copyWith
(
outlinedButtonTheme:
OutlinedButtonThemeData
(
style:
overallStyle
),
),
home:
Scaffold
(
body:
Center
(
// If the OutlinedButtonTheme widget is present, it's used
// instead of the Theme's ThemeData.outlinedButtonTheme.
child:
themeStyle
==
null
?
child
:
OutlinedButtonTheme
(
data:
OutlinedButtonThemeData
(
style:
themeStyle
),
child:
child
,
),
),
),
);
}
final
Finder
findMaterial
=
find
.
descendant
(
of:
find
.
byType
(
OutlinedButton
),
matching:
find
.
byType
(
Material
),
);
final
Finder
findInkWell
=
find
.
descendant
(
of:
find
.
byType
(
OutlinedButton
),
matching:
find
.
byType
(
InkWell
),
);
const
Set
<
MaterialState
>
enabled
=
<
MaterialState
>{};
const
Set
<
MaterialState
>
disabled
=
<
MaterialState
>{
MaterialState
.
disabled
};
const
Set
<
MaterialState
>
hovered
=
<
MaterialState
>{
MaterialState
.
hovered
};
const
Set
<
MaterialState
>
focused
=
<
MaterialState
>{
MaterialState
.
focused
};
void
checkButton
(
WidgetTester
tester
)
{
final
Material
material
=
tester
.
widget
<
Material
>(
findMaterial
);
final
InkWell
inkWell
=
tester
.
widget
<
InkWell
>(
findInkWell
);
expect
(
material
.
textStyle
.
color
,
primaryColor
);
expect
(
material
.
textStyle
.
fontSize
,
12
);
expect
(
material
.
color
,
backgroundColor
);
expect
(
material
.
shadowColor
,
shadowColor
);
expect
(
material
.
elevation
,
elevation
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
enabled
),
enabledMouseCursor
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
disabled
),
disabledMouseCursor
);
expect
(
inkWell
.
overlayColor
.
resolve
(
hovered
),
primaryColor
.
withOpacity
(
0.04
));
expect
(
inkWell
.
overlayColor
.
resolve
(
focused
),
primaryColor
.
withOpacity
(
0.12
));
expect
(
inkWell
.
enableFeedback
,
enableFeedback
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
shape
,
shape
);
expect
(
material
.
animationDuration
,
animationDuration
);
expect
(
tester
.
getSize
(
find
.
byType
(
OutlinedButton
)),
const
Size
(
200
,
200
));
}
testWidgets
(
'Button style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
themeStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
// Same as the previous tests with empty ButtonStyle's instead of null.
testWidgets
(
'Button style overrides defaults, empty theme and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
,
themeStyle:
const
ButtonStyle
(),
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults, empty button and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
style
,
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults, null theme and empty overall style'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
null
,
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
});
}
packages/flutter/test/material/text_button_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
import
'../widgets/semantics_tester.dart'
;
void
main
(
)
{
testWidgets
(
'TextButton defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
// Enabled TextButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Center
(
child:
TextButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
TextButton
),
matching:
find
.
byType
(
Material
),
);
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4.0
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
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
(
TextButton
));
await
tester
.
startGesture
(
center
);
await
tester
.
pumpAndSettle
();
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
// No change vs enabled and not pressed.
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4.0
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
// Disabled TextButton
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
const
Center
(
child:
TextButton
(
onPressed:
null
,
child:
Text
(
'button'
),
),
),
),
);
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderOnForeground
,
true
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
clipBehavior
,
Clip
.
none
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4.0
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
expect
(
material
.
type
,
MaterialType
.
button
);
});
testWidgets
(
'Default TextButton meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Scaffold
(
body:
Center
(
child:
TextButton
(
child:
const
Text
(
'TextButton'
),
onPressed:
()
{
},
focusNode:
focusNode
,
),
),
),
),
);
// 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
(
TextButton
));
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
addPointer
();
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
));
await
gesture
.
removePointer
();
},
skip:
isBrowser
,
// https://github.com/flutter/flutter/issues/44115
semanticsEnabled:
true
,
);
testWidgets
(
'TextButton with colored theme meets a11y contrast guidelines'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
();
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:
TextButtonTheme
(
data:
TextButtonThemeData
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
),
child:
Builder
(
builder:
(
BuildContext
context
)
{
return
TextButton
(
child:
const
Text
(
'TextButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
);
},
),
),
),
),
),
);
// 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
(
TextButton
));
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
semanticsEnabled:
true
,
);
testWidgets
(
'TextButton 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:
TextButton
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
onPressed:
()
{},
focusNode:
focusNode
,
child:
const
Text
(
'TextButton'
),
),
),
),
),
);
Color
textColor
()
{
return
tester
.
renderObject
<
RenderParagraph
>(
find
.
text
(
'TextButton'
)).
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
(
TextButton
));
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
(
'TextButton 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:
TextButton
.
icon
(
style:
ButtonStyle
(
foregroundColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getTextColor
),
),
key:
buttonKey
,
icon:
const
Icon
(
Icons
.
add
),
label:
const
Text
(
'TextButton'
),
onPressed:
()
{},
focusNode:
focusNode
,
),
),
),
),
);
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
(
'TextButton has no clip by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
TextButton
(
child:
Container
(),
onPressed:
()
{
/* to make sure the button is enabled */
},
),
),
),
);
expect
(
tester
.
renderObject
(
find
.
byType
(
TextButton
)),
paintsExactlyCountTimes
(
#clipPath
,
0
),
);
});
testWidgets
(
'Does TextButton work with hover'
,
(
WidgetTester
tester
)
async
{
const
Color
hoverColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
hovered
)
?
hoverColor
:
null
;
}
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
TextButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
child:
Container
(),
onPressed:
()
{
/* to make sure the button is enabled */
},
),
),
),
);
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
await
gesture
.
addPointer
();
addTearDown
(
gesture
.
removePointer
);
await
gesture
.
moveTo
(
tester
.
getCenter
(
find
.
byType
(
TextButton
)));
await
tester
.
pumpAndSettle
();
final
RenderObject
inkFeatures
=
tester
.
allRenderObjects
.
firstWhere
((
RenderObject
object
)
=>
object
.
runtimeType
.
toString
()
==
'_RenderInkFeatures'
);
expect
(
inkFeatures
,
paints
..
rect
(
color:
hoverColor
));
});
testWidgets
(
'Does TextButton work with focus'
,
(
WidgetTester
tester
)
async
{
const
Color
focusColor
=
Color
(
0xff001122
);
Color
getOverlayColor
(
Set
<
MaterialState
>
states
)
{
return
states
.
contains
(
MaterialState
.
focused
)
?
focusColor
:
null
;
}
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'TextButton Node'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TextButton
(
style:
ButtonStyle
(
overlayColor:
MaterialStateProperty
.
resolveWith
<
Color
>(
getOverlayColor
),
),
focusNode:
focusNode
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
);
WidgetsBinding
.
instance
.
focusManager
.
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 TextButton contribute semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
TextButton
(
style:
ButtonStyle
(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the semantics tree's rect and transform
// match the original version of this test.
minimumSize:
MaterialStateProperty
.
all
<
Size
>(
const
Size
(
88
,
36
)),
),
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
(
'Does TextButton scale with font scale changes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
1.0
),
child:
Center
(
child:
TextButton
(
onPressed:
()
{
},
child:
const
Text
(
'ABC'
),
),
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
TextButton
)),
equals
(
const
Size
(
64.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:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
1.3
),
child:
Center
(
child:
TextButton
(
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
(
TextButton
)).
width
,
isIn
(<
double
>[
70.0
,
71.0
]));
expect
(
tester
.
getSize
(
find
.
byType
(
TextButton
)).
height
,
isIn
(<
double
>[
47.0
,
48.0
]));
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:
Material
(
child:
MediaQuery
(
data:
const
MediaQueryData
(
textScaleFactor:
3.0
),
child:
Center
(
child:
TextButton
(
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
(
TextButton
)).
width
,
isIn
(<
double
>[
133.0
,
134.0
]));
expect
(
tester
.
getSize
(
find
.
byType
(
TextButton
)).
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
(
'TextButton size is configurable by ThemeData.materialTapTargetSize'
,
(
WidgetTester
tester
)
async
{
Widget
buildFrame
(
MaterialTapTargetSize
tapTargetSize
,
Key
key
)
{
return
Theme
(
data:
ThemeData
(
materialTapTargetSize:
tapTargetSize
),
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Material
(
child:
Center
(
child:
TextButton
(
key:
key
,
child:
const
SizedBox
(
width:
50.0
,
height:
8.0
),
onPressed:
()
{
},
),
),
),
),
);
}
final
Key
key1
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
padded
,
key1
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key1
)),
const
Size
(
66.0
,
48.0
));
final
Key
key2
=
UniqueKey
();
await
tester
.
pumpWidget
(
buildFrame
(
MaterialTapTargetSize
.
shrinkWrap
,
key2
));
expect
(
tester
.
getSize
(
find
.
byKey
(
key2
)),
const
Size
(
66.0
,
36.0
));
});
testWidgets
(
'TextButton onPressed and onLongPress callbacks are correctly called when non-null'
,
(
WidgetTester
tester
)
async
{
bool
wasPressed
;
Finder
textButton
;
Widget
buildFrame
({
VoidCallback
onPressed
,
VoidCallback
onLongPress
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TextButton
(
child:
const
Text
(
'button'
),
onPressed:
onPressed
,
onLongPress:
onLongPress
,
),
);
}
// onPressed not null, onLongPress null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
()
{
wasPressed
=
true
;
},
onLongPress:
null
),
);
textButton
=
find
.
byType
(
TextButton
);
expect
(
tester
.
widget
<
TextButton
>(
textButton
).
enabled
,
true
);
await
tester
.
tap
(
textButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress not null.
wasPressed
=
false
;
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
()
{
wasPressed
=
true
;
}),
);
textButton
=
find
.
byType
(
TextButton
);
expect
(
tester
.
widget
<
TextButton
>(
textButton
).
enabled
,
true
);
await
tester
.
longPress
(
textButton
);
expect
(
wasPressed
,
true
);
// onPressed null, onLongPress null.
await
tester
.
pumpWidget
(
buildFrame
(
onPressed:
null
,
onLongPress:
null
),
);
textButton
=
find
.
byType
(
TextButton
);
expect
(
tester
.
widget
<
TextButton
>(
textButton
).
enabled
,
false
);
});
testWidgets
(
'TextButton onPressed and onLongPress callbacks are distinctly recognized'
,
(
WidgetTester
tester
)
async
{
bool
didPressButton
=
false
;
bool
didLongPressButton
=
false
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
TextButton
(
onPressed:
()
{
didPressButton
=
true
;
},
onLongPress:
()
{
didLongPressButton
=
true
;
},
child:
const
Text
(
'button'
),
),
),
);
final
Finder
textButton
=
find
.
byType
(
TextButton
);
expect
(
tester
.
widget
<
TextButton
>(
textButton
).
enabled
,
true
);
expect
(
didPressButton
,
isFalse
);
await
tester
.
tap
(
textButton
);
expect
(
didPressButton
,
isTrue
);
expect
(
didLongPressButton
,
isFalse
);
await
tester
.
longPress
(
textButton
);
expect
(
didLongPressButton
,
isTrue
);
});
testWidgets
(
'TextButton 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
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
Center
(
child:
TextButton
(
style:
ButtonStyle
(
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
(
const
VisualDensity
());
final
RenderBox
box
=
tester
.
renderObject
(
find
.
byKey
(
key
));
Rect
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
await
tester
.
pumpAndSettle
();
expect
(
box
.
size
,
equals
(
const
Size
(
116
,
116
)));
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
(
140
,
140
)));
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
(
100
,
100
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
350
,
250
,
450
,
350
)));
await
buildTest
(
const
VisualDensity
(),
useText:
true
);
await
tester
.
pumpAndSettle
();
childRect
=
tester
.
getRect
(
find
.
byKey
(
childKey
));
expect
(
box
.
size
,
equals
(
const
Size
(
72
,
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
(
96
,
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
(
56
,
36
)));
expect
(
childRect
,
equals
(
const
Rect
.
fromLTRB
(
372.0
,
293.0
,
428.0
,
307.0
)));
});
group
(
'Default TextButton padding for textScaleFactor, textDirection'
,
()
{
const
ValueKey
<
String
>
buttonKey
=
ValueKey
<
String
>(
'button'
);
const
ValueKey
<
String
>
labelKey
=
ValueKey
<
String
>(
'label'
);
const
ValueKey
<
String
>
iconKey
=
ValueKey
<
String
>(
'icon'
);
const
List
<
double
>
textScaleFactorOptions
=
<
double
>[
0.5
,
1.0
,
1.25
,
1.5
,
2.0
,
2.5
,
3.0
,
4.0
];
const
List
<
TextDirection
>
textDirectionOptions
=
<
TextDirection
>[
TextDirection
.
ltr
,
TextDirection
.
rtl
];
const
List
<
Widget
>
iconOptions
=
<
Widget
>[
null
,
Icon
(
Icons
.
add
,
size:
18
,
key:
iconKey
)];
// Expected values for each textScaleFactor.
final
Map
<
double
,
double
>
paddingVertical
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
6
,
1.5
:
4
,
2
:
0
,
2.5
:
0
,
3
:
0
,
4
:
0
,
};
final
Map
<
double
,
double
>
paddingWithIconGap
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
7
,
1.5
:
6
,
2
:
4
,
2.5
:
4
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
textPaddingWithoutIconHorizontal
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
8
,
1.5
:
8
,
2
:
8
,
2.5
:
6
,
3
:
4
,
4
:
4
,
};
final
Map
<
double
,
double
>
textPaddingWithIconHorizontal
=
<
double
,
double
>{
0.5
:
8
,
1
:
8
,
1.25
:
7
,
1.5
:
6
,
2
:
4
,
2.5
:
4
,
3
:
4
,
4
:
4
,
};
Rect
globalBounds
(
RenderBox
renderBox
)
{
final
Offset
topLeft
=
renderBox
.
localToGlobal
(
Offset
.
zero
);
return
topLeft
&
renderBox
.
size
;
}
/// Computes the padding between two [Rect]s, one inside the other.
EdgeInsets
paddingBetween
({
Rect
parent
,
Rect
child
})
{
assert
(
parent
.
intersect
(
child
)
==
child
);
return
EdgeInsets
.
fromLTRB
(
child
.
left
-
parent
.
left
,
child
.
top
-
parent
.
top
,
parent
.
right
-
child
.
right
,
parent
.
bottom
-
child
.
bottom
,
);
}
for
(
final
double
textScaleFactor
in
textScaleFactorOptions
)
{
for
(
final
TextDirection
textDirection
in
textDirectionOptions
)
{
for
(
final
Widget
icon
in
iconOptions
)
{
final
String
testName
=
'TextButton'
', text scale
$textScaleFactor
'
'
${icon != null ? ", with icon" : ""}
'
'
${textDirection == TextDirection.rtl ? ", RTL" : ""}
'
;
testWidgets
(
testName
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
textScaleFactor
,
),
child:
Directionality
(
textDirection:
textDirection
,
child:
Scaffold
(
body:
Center
(
child:
icon
==
null
?
TextButton
(
key:
buttonKey
,
onPressed:
()
{},
child:
const
Text
(
'button'
,
key:
labelKey
),
)
:
TextButton
.
icon
(
key:
buttonKey
,
onPressed:
()
{},
icon:
icon
,
label:
const
Text
(
'button'
,
key:
labelKey
),
),
),
),
),
);
},
),
),
);
final
Element
paddingElement
=
tester
.
element
(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
Directionality
.
of
(
paddingElement
),
textDirection
);
final
Padding
paddingWidget
=
paddingElement
.
widget
as
Padding
;
// Compute expected padding, and check.
final
double
expectedPaddingTop
=
paddingVertical
[
textScaleFactor
];
final
double
expectedPaddingBottom
=
paddingVertical
[
textScaleFactor
];
final
double
expectedPaddingStart
=
icon
!=
null
?
textPaddingWithIconHorizontal
[
textScaleFactor
]
:
textPaddingWithoutIconHorizontal
[
textScaleFactor
];
final
double
expectedPaddingEnd
=
expectedPaddingStart
;
final
EdgeInsets
expectedPadding
=
EdgeInsetsDirectional
.
fromSTEB
(
expectedPaddingStart
,
expectedPaddingTop
,
expectedPaddingEnd
,
expectedPaddingBottom
,
).
resolve
(
textDirection
);
expect
(
paddingWidget
.
padding
.
resolve
(
textDirection
),
expectedPadding
);
// Measure padding in terms of the difference between the button and its label child
// and check that.
final
RenderBox
labelRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
labelKey
));
final
Rect
labelBounds
=
globalBounds
(
labelRenderBox
);
final
RenderBox
iconRenderBox
=
icon
==
null
?
null
:
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
iconKey
));
final
Rect
iconBounds
=
icon
==
null
?
null
:
globalBounds
(
iconRenderBox
);
final
Rect
childBounds
=
icon
==
null
?
labelBounds
:
labelBounds
.
expandToInclude
(
iconBounds
);
// We measure the `InkResponse` descendant of the button
// element, because the button has a larger `RenderBox`
// which accommodates the minimum tap target with a height
// of 48.
final
RenderBox
buttonRenderBox
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
buttonKey
),
matching:
find
.
byWidgetPredicate
(
(
Widget
widget
)
=>
widget
is
InkResponse
,
),
),
);
final
Rect
buttonBounds
=
globalBounds
(
buttonRenderBox
);
final
EdgeInsets
visuallyMeasuredPadding
=
paddingBetween
(
parent:
buttonBounds
,
child:
childBounds
,
);
// Since there is a requirement of a minimum width of 64
// and a minimum height of 36 on material buttons, the visual
// padding of smaller buttons may not match their settings.
// Therefore, we only test buttons that are large enough.
if
(
buttonBounds
.
width
>
64
)
{
expect
(
visuallyMeasuredPadding
.
left
,
expectedPadding
.
left
,
);
expect
(
visuallyMeasuredPadding
.
right
,
expectedPadding
.
right
,
);
}
if
(
buttonBounds
.
height
>
36
)
{
expect
(
visuallyMeasuredPadding
.
top
,
expectedPadding
.
top
,
);
expect
(
visuallyMeasuredPadding
.
bottom
,
expectedPadding
.
bottom
,
);
}
// Check the gap between the icon and the label
if
(
icon
!=
null
)
{
final
double
gapWidth
=
textDirection
==
TextDirection
.
ltr
?
labelBounds
.
left
-
iconBounds
.
right
:
iconBounds
.
left
-
labelBounds
.
right
;
expect
(
gapWidth
,
paddingWithIconGap
[
textScaleFactor
]);
}
// Check the text's height - should be consistent with the textScaleFactor.
final
RenderBox
textRenderObject
=
tester
.
renderObject
<
RenderBox
>(
find
.
descendant
(
of:
find
.
byKey
(
labelKey
),
matching:
find
.
byElementPredicate
(
(
Element
element
)
=>
element
.
widget
is
RichText
,
),
),
);
final
double
textHeight
=
textRenderObject
.
paintBounds
.
size
.
height
;
final
double
expectedTextHeight
=
14
*
textScaleFactor
;
expect
(
textHeight
,
moreOrLessEquals
(
expectedTextHeight
,
epsilon:
0.5
));
});
}
}
}
});
testWidgets
(
'Override TextButton default padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()),
home:
Builder
(
builder:
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
2
,
),
child:
Scaffold
(
body:
Center
(
child:
TextButton
(
style:
TextButton
.
styleFrom
(
padding:
const
EdgeInsets
.
all
(
22
)),
onPressed:
()
{},
child:
const
Text
(
'TextButton'
)
),
),
),
);
},
),
),
);
final
Padding
paddingWidget
=
tester
.
widget
<
Padding
>(
find
.
descendant
(
of:
find
.
byType
(
TextButton
),
matching:
find
.
byType
(
Padding
),
),
);
expect
(
paddingWidget
.
padding
,
const
EdgeInsets
.
all
(
22
));
});
}
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
;
}
packages/flutter/test/material/text_button_theme_test.dart
0 → 100644
View file @
dc31d89c
// 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.
// @dart = 2.8
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Passing no TextButtonTheme returns defaults'
,
(
WidgetTester
tester
)
async
{
const
ColorScheme
colorScheme
=
ColorScheme
.
light
();
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
colorScheme
),
home:
Scaffold
(
body:
Center
(
child:
TextButton
(
onPressed:
()
{
},
child:
const
Text
(
'button'
),
),
),
),
),
);
final
Finder
buttonMaterial
=
find
.
descendant
(
of:
find
.
byType
(
TextButton
),
matching:
find
.
byType
(
Material
),
);
final
Material
material
=
tester
.
widget
<
Material
>(
buttonMaterial
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
elevation
,
0.0
);
expect
(
material
.
shadowColor
,
const
Color
(
0xff000000
));
expect
(
material
.
shape
,
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
4.0
)));
expect
(
material
.
textStyle
.
color
,
colorScheme
.
primary
);
expect
(
material
.
textStyle
.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
.
fontSize
,
14
);
expect
(
material
.
textStyle
.
fontWeight
,
FontWeight
.
w500
);
});
group
(
'[Theme, TextTheme, TextButton style overrides]'
,
()
{
const
Color
primaryColor
=
Color
(
0xff000001
);
const
Color
onSurfaceColor
=
Color
(
0xff000002
);
const
Color
backgroundColor
=
Color
(
0xff000003
);
const
Color
shadowColor
=
Color
(
0xff000004
);
const
double
elevation
=
3
;
const
TextStyle
textStyle
=
TextStyle
(
fontSize:
12.0
);
const
EdgeInsets
padding
=
EdgeInsets
.
all
(
3
);
const
Size
minimumSize
=
Size
(
200
,
200
);
const
BorderSide
side
=
BorderSide
(
color:
Colors
.
green
,
width:
2
);
const
OutlinedBorder
shape
=
RoundedRectangleBorder
(
side:
side
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
2
)));
const
MouseCursor
enabledMouseCursor
=
SystemMouseCursors
.
text
;
const
MouseCursor
disabledMouseCursor
=
SystemMouseCursors
.
grab
;
const
MaterialTapTargetSize
tapTargetSize
=
MaterialTapTargetSize
.
shrinkWrap
;
const
Duration
animationDuration
=
Duration
(
milliseconds:
25
);
const
bool
enableFeedback
=
false
;
final
ButtonStyle
style
=
TextButton
.
styleFrom
(
primary:
primaryColor
,
onSurface:
onSurfaceColor
,
backgroundColor:
backgroundColor
,
shadowColor:
shadowColor
,
elevation:
elevation
,
textStyle:
textStyle
,
padding:
padding
,
minimumSize:
minimumSize
,
side:
side
,
shape:
shape
,
enabledMouseCursor:
enabledMouseCursor
,
disabledMouseCursor:
disabledMouseCursor
,
tapTargetSize:
tapTargetSize
,
animationDuration:
animationDuration
,
enableFeedback:
enableFeedback
,
);
Widget
buildFrame
({
ButtonStyle
buttonStyle
,
ButtonStyle
themeStyle
,
ButtonStyle
overallStyle
})
{
final
Widget
child
=
Builder
(
builder:
(
BuildContext
context
)
{
return
TextButton
(
style:
buttonStyle
,
onPressed:
()
{
},
child:
const
Text
(
'button'
),
);
},
);
return
MaterialApp
(
theme:
ThemeData
.
from
(
colorScheme:
const
ColorScheme
.
light
()).
copyWith
(
textButtonTheme:
TextButtonThemeData
(
style:
overallStyle
),
),
home:
Scaffold
(
body:
Center
(
// If the TextButtonTheme widget is present, it's used
// instead of the Theme's ThemeData.textButtonTheme.
child:
themeStyle
==
null
?
child
:
TextButtonTheme
(
data:
TextButtonThemeData
(
style:
themeStyle
),
child:
child
,
),
),
),
);
}
final
Finder
findMaterial
=
find
.
descendant
(
of:
find
.
byType
(
TextButton
),
matching:
find
.
byType
(
Material
),
);
final
Finder
findInkWell
=
find
.
descendant
(
of:
find
.
byType
(
TextButton
),
matching:
find
.
byType
(
InkWell
),
);
const
Set
<
MaterialState
>
enabled
=
<
MaterialState
>{};
const
Set
<
MaterialState
>
disabled
=
<
MaterialState
>{
MaterialState
.
disabled
};
const
Set
<
MaterialState
>
hovered
=
<
MaterialState
>{
MaterialState
.
hovered
};
const
Set
<
MaterialState
>
focused
=
<
MaterialState
>{
MaterialState
.
focused
};
void
checkButton
(
WidgetTester
tester
)
{
final
Material
material
=
tester
.
widget
<
Material
>(
findMaterial
);
final
InkWell
inkWell
=
tester
.
widget
<
InkWell
>(
findInkWell
);
expect
(
material
.
textStyle
.
color
,
primaryColor
);
expect
(
material
.
textStyle
.
fontSize
,
12
);
expect
(
material
.
color
,
backgroundColor
);
expect
(
material
.
shadowColor
,
shadowColor
);
expect
(
material
.
elevation
,
elevation
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
enabled
),
enabledMouseCursor
);
expect
(
MaterialStateProperty
.
resolveAs
<
MouseCursor
>(
inkWell
.
mouseCursor
,
disabled
),
disabledMouseCursor
);
expect
(
inkWell
.
overlayColor
.
resolve
(
hovered
),
primaryColor
.
withOpacity
(
0.04
));
expect
(
inkWell
.
overlayColor
.
resolve
(
focused
),
primaryColor
.
withOpacity
(
0.12
));
expect
(
inkWell
.
enableFeedback
,
enableFeedback
);
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
shape
,
shape
);
expect
(
material
.
animationDuration
,
animationDuration
);
expect
(
tester
.
getSize
(
find
.
byType
(
TextButton
)),
const
Size
(
200
,
200
));
}
testWidgets
(
'Button style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
themeStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
checkButton
(
tester
);
});
// Same as the previous tests with empty ButtonStyle's instead of null.
testWidgets
(
'Button style overrides defaults, empty theme and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
style
,
themeStyle:
const
ButtonStyle
(),
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Button theme style overrides defaults, empty button and overall styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
style
,
overallStyle:
const
ButtonStyle
()));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
testWidgets
(
'Overall Theme button theme style overrides defaults, null theme and empty overall style'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildFrame
(
buttonStyle:
const
ButtonStyle
(),
themeStyle:
null
,
overallStyle:
style
));
await
tester
.
pumpAndSettle
();
// allow the animations to finish
checkButton
(
tester
);
});
});
}
packages/flutter/test/material/theme_data_test.dart
View file @
dc31d89c
...
@@ -283,6 +283,9 @@ void main() {
...
@@ -283,6 +283,9 @@ void main() {
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
start
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
start
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
fixed
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
fixed
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
black
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
black
),
textButtonTheme:
TextButtonThemeData
(
style:
TextButton
.
styleFrom
(
primary:
Colors
.
red
)),
containedButtonTheme:
ContainedButtonThemeData
(
style:
ContainedButton
.
styleFrom
(
primary:
Colors
.
green
)),
outlinedButtonTheme:
OutlinedButtonThemeData
(
style:
OutlinedButton
.
styleFrom
(
primary:
Colors
.
blue
)),
fixTextFieldOutlineLabel:
false
,
fixTextFieldOutlineLabel:
false
,
);
);
...
@@ -365,6 +368,9 @@ void main() {
...
@@ -365,6 +368,9 @@ void main() {
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
end
),
buttonBarTheme:
const
ButtonBarThemeData
(
alignment:
MainAxisAlignment
.
end
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
shifting
),
bottomNavigationBarTheme:
const
BottomNavigationBarThemeData
(
type:
BottomNavigationBarType
.
shifting
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
white
),
timePickerTheme:
const
TimePickerThemeData
(
backgroundColor:
Colors
.
white
),
textButtonTheme:
const
TextButtonThemeData
(),
containedButtonTheme:
const
ContainedButtonThemeData
(),
outlinedButtonTheme:
const
OutlinedButtonThemeData
(),
fixTextFieldOutlineLabel:
true
,
fixTextFieldOutlineLabel:
true
,
);
);
...
@@ -433,6 +439,9 @@ void main() {
...
@@ -433,6 +439,9 @@ void main() {
buttonBarTheme:
otherTheme
.
buttonBarTheme
,
buttonBarTheme:
otherTheme
.
buttonBarTheme
,
bottomNavigationBarTheme:
otherTheme
.
bottomNavigationBarTheme
,
bottomNavigationBarTheme:
otherTheme
.
bottomNavigationBarTheme
,
timePickerTheme:
otherTheme
.
timePickerTheme
,
timePickerTheme:
otherTheme
.
timePickerTheme
,
textButtonTheme:
otherTheme
.
textButtonTheme
,
containedButtonTheme:
otherTheme
.
containedButtonTheme
,
outlinedButtonTheme:
otherTheme
.
outlinedButtonTheme
,
fixTextFieldOutlineLabel:
otherTheme
.
fixTextFieldOutlineLabel
,
fixTextFieldOutlineLabel:
otherTheme
.
fixTextFieldOutlineLabel
,
);
);
...
@@ -503,6 +512,9 @@ void main() {
...
@@ -503,6 +512,9 @@ void main() {
expect
(
themeDataCopy
.
buttonBarTheme
,
equals
(
otherTheme
.
buttonBarTheme
));
expect
(
themeDataCopy
.
buttonBarTheme
,
equals
(
otherTheme
.
buttonBarTheme
));
expect
(
themeDataCopy
.
bottomNavigationBarTheme
,
equals
(
otherTheme
.
bottomNavigationBarTheme
));
expect
(
themeDataCopy
.
bottomNavigationBarTheme
,
equals
(
otherTheme
.
bottomNavigationBarTheme
));
expect
(
themeDataCopy
.
timePickerTheme
,
equals
(
otherTheme
.
timePickerTheme
));
expect
(
themeDataCopy
.
timePickerTheme
,
equals
(
otherTheme
.
timePickerTheme
));
expect
(
themeDataCopy
.
textButtonTheme
,
equals
(
otherTheme
.
textButtonTheme
));
expect
(
themeDataCopy
.
containedButtonTheme
,
equals
(
otherTheme
.
containedButtonTheme
));
expect
(
themeDataCopy
.
outlinedButtonTheme
,
equals
(
otherTheme
.
outlinedButtonTheme
));
expect
(
themeDataCopy
.
fixTextFieldOutlineLabel
,
equals
(
otherTheme
.
fixTextFieldOutlineLabel
));
expect
(
themeDataCopy
.
fixTextFieldOutlineLabel
,
equals
(
otherTheme
.
fixTextFieldOutlineLabel
));
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment