Unverified Commit 60a072b0 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added Checkbox support for MaterialStateBorderSide (#86910)

parent 6d8ab062
...@@ -310,7 +310,23 @@ class Checkbox extends StatefulWidget { ...@@ -310,7 +310,23 @@ class Checkbox extends StatefulWidget {
final OutlinedBorder? shape; final OutlinedBorder? shape;
/// {@template flutter.material.checkbox.side} /// {@template flutter.material.checkbox.side}
/// The side of the checkbox's border. /// The color and width of the checkbox's border.
///
/// This property can be a [MaterialStateBorderSide] that can
/// specify different border color and widths depending on the
/// checkbox's state.
///
/// Resolves in the following states:
/// * [MaterialState.pressed].
/// * [MaterialState.selected].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
///
/// If this property is not a [MaterialStateBorderSide] and it is
/// non-null, then it is only rendered when the checkbox's value is
/// false. The difference in interpretation is for backwards
/// compatibility.
/// {@endtemplate} /// {@endtemplate}
/// ///
/// If this property is null then [CheckboxThemeData.side] of [ThemeData.checkboxTheme] /// If this property is null then [CheckboxThemeData.side] of [ThemeData.checkboxTheme]
...@@ -383,6 +399,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg ...@@ -383,6 +399,14 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
}); });
} }
BorderSide? _resolveSide(BorderSide? side) {
if (side is MaterialStateBorderSide)
return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
if (!states.contains(MaterialState.selected))
return side;
return null;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
...@@ -477,7 +501,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg ...@@ -477,7 +501,7 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin, Togg
..shape = widget.shape ?? themeData.checkboxTheme.shape ?? const RoundedRectangleBorder( ..shape = widget.shape ?? themeData.checkboxTheme.shape ?? const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(1.0)), borderRadius: BorderRadius.all(Radius.circular(1.0)),
) )
..side = widget.side ?? themeData.checkboxTheme.side, ..side = _resolveSide(widget.side) ?? _resolveSide(themeData.checkboxTheme.side),
), ),
); );
} }
...@@ -563,13 +587,13 @@ class _CheckboxPainter extends ToggleablePainter { ...@@ -563,13 +587,13 @@ class _CheckboxPainter extends ToggleablePainter {
..strokeWidth = _kStrokeWidth; ..strokeWidth = _kStrokeWidth;
} }
void _drawBorder(Canvas canvas, Rect outer, double t, Paint paint) { void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool fill) {
assert(t >= 0.0 && t <= 0.5); if (fill) {
OutlinedBorder resolvedShape = shape; canvas.drawPath(shape.getOuterPath(outer), paint);
if (side == null) { }
resolvedShape = resolvedShape.copyWith(side: BorderSide(width: 2, color: paint.color)); if (side != null) {
shape.copyWith(side: side).paint(canvas, outer);
} }
resolvedShape.copyWith(side: side).paint(canvas, outer);
} }
void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) { void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
...@@ -622,14 +646,13 @@ class _CheckboxPainter extends ToggleablePainter { ...@@ -622,14 +646,13 @@ class _CheckboxPainter extends ToggleablePainter {
if (previousValue == false || value == false) { if (previousValue == false || value == false) {
final double t = value == false ? 1.0 - tNormalized : tNormalized; final double t = value == false ? 1.0 - tNormalized : tNormalized;
final Rect outer = _outerRectAt(origin, t); final Rect outer = _outerRectAt(origin, t);
final Path emptyCheckboxPath = shape.copyWith(side: side).getOuterPath(outer);
final Paint paint = Paint()..color = _colorAt(t); final Paint paint = Paint()..color = _colorAt(t);
if (t <= 0.5) { if (t <= 0.5) {
_drawBorder(canvas, outer, t, paint); final BorderSide border = side ?? BorderSide(width: 2, color: paint.color);
_drawBox(canvas, outer, paint, border, false); // only paint the border
} else { } else {
canvas.drawPath(emptyCheckboxPath, paint); _drawBox(canvas, outer, paint, side, true);
final double tShrink = (t - 0.5) * 2.0; final double tShrink = (t - 0.5) * 2.0;
if (previousValue == null || value == null) if (previousValue == null || value == null)
_drawDash(canvas, origin, tShrink, strokePaint); _drawDash(canvas, origin, tShrink, strokePaint);
...@@ -639,8 +662,8 @@ class _CheckboxPainter extends ToggleablePainter { ...@@ -639,8 +662,8 @@ class _CheckboxPainter extends ToggleablePainter {
} else { // Two cases: null to true, true to null } else { // Two cases: null to true, true to null
final Rect outer = _outerRectAt(origin, 1.0); final Rect outer = _outerRectAt(origin, 1.0);
final Paint paint = Paint() ..color = _colorAt(1.0); final Paint paint = Paint() ..color = _colorAt(1.0);
canvas.drawPath(shape.copyWith(side: side).getOuterPath(outer), paint);
_drawBox(canvas, outer, paint, side, true);
if (tNormalized <= 0.5) { if (tNormalized <= 0.5) {
final double tShrink = 1.0 - tNormalized * 2.0; final double tShrink = 1.0 - tNormalized * 2.0;
if (previousValue == true) if (previousValue == true)
......
...@@ -1193,6 +1193,122 @@ void main() { ...@@ -1193,6 +1193,122 @@ void main() {
// Release pointer after widget disappeared. // Release pointer after widget disappeared.
await gesture.up(); await gesture.up();
}); });
testWidgets('Checkbox BorderSide side only applies when unselected', (WidgetTester tester) async {
const Color borderColor = Color(0xfff44336);
const Color activeColor = Color(0xff123456);
const BorderSide side = BorderSide(
width: 4,
color: borderColor,
);
Widget buildApp({ bool? value, bool enabled = true }) {
return MaterialApp(
home: Material(
child: Center(
child: Checkbox(
value: value,
tristate: value == null,
activeColor: activeColor,
onChanged: enabled ? (bool? newValue) { } : null,
side: side,
),
),
),
);
}
RenderBox getCheckboxRenderer() {
return tester.renderObject<RenderBox>(find.byType(Checkbox));
}
void expectBorder() {
expect(
getCheckboxRenderer(),
paints
..drrect(
color: borderColor,
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
inner: RRect.fromLTRBR(19, 19, 29, 29, const Radius.circular(-3)),
),
);
}
// Checkbox is unselected, so the specified BorderSide appears.
await tester.pumpWidget(buildApp(value: false));
await tester.pumpAndSettle();
expectBorder();
await tester.pumpWidget(buildApp(value: false, enabled: false));
await tester.pumpAndSettle();
expectBorder();
// Checkbox is selected/interdeterminate, so the specified BorderSide
// does not appear.
await tester.pumpWidget(buildApp(value: true));
await tester.pumpAndSettle();
expect(getCheckboxRenderer(), isNot(paints..drrect())); // no border
expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
await tester.pumpWidget(buildApp(value: null));
await tester.pumpAndSettle();
expect(getCheckboxRenderer(), isNot(paints..drrect())); // no border
expect(getCheckboxRenderer(), paints..path(color: activeColor)); // checkbox fill
});
testWidgets('Checkbox MaterialStateBorderSide applies unconditionally', (WidgetTester tester) async {
const Color borderColor = Color(0xfff44336);
const BorderSide side = BorderSide(
width: 4,
color: borderColor,
);
Widget buildApp({ bool? value, bool enabled = true }) {
return MaterialApp(
home: Material(
child: Center(
child: Checkbox(
value: value,
tristate: value == null,
onChanged: enabled ? (bool? newValue) { } : null,
side: MaterialStateBorderSide.resolveWith((Set<MaterialState> states) => side),
),
),
),
);
}
void expectBorder() {
expect(
tester.renderObject<RenderBox>(find.byType(Checkbox)),
paints
..drrect(
color: borderColor,
outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)),
inner: RRect.fromLTRBR(19, 19, 29, 29, const Radius.circular(-3)),
),
);
}
await tester.pumpWidget(buildApp(value: false));
await tester.pumpAndSettle();
expectBorder();
await tester.pumpWidget(buildApp(value: false, enabled: false));
await tester.pumpAndSettle();
expectBorder();
await tester.pumpWidget(buildApp(value: true));
await tester.pumpAndSettle();
expectBorder();
await tester.pumpWidget(buildApp(value: null));
await tester.pumpAndSettle();
expectBorder();
});
} }
class _SelectedGrabMouseCursor extends MaterialStateMouseCursor { class _SelectedGrabMouseCursor extends MaterialStateMouseCursor {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment