| // 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. |
| |
| /// @docImport 'package:flutter/material.dart'; |
| /// |
| /// @docImport 'slider.dart'; |
| /// @docImport 'switch.dart'; |
| library; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'colors.dart'; |
| import 'constants.dart'; |
| import 'theme.dart'; |
| |
| // Examples can assume: |
| // bool _throwShotAway = false; |
| // late StateSetter setState; |
| |
| // Eyeballed from a checkbox on a physical Macbook Pro running macOS version 14.5. |
| const Color _kDisabledCheckColor = CupertinoDynamicColor.withBrightness( |
| color: Color.fromARGB(64, 0, 0, 0), |
| darkColor: Color.fromARGB(64, 255, 255, 255), |
| ); |
| const Color _kDisabledBorderColor = CupertinoDynamicColor.withBrightness( |
| color: Color.fromARGB(13, 0, 0, 0), |
| darkColor: Color.fromARGB(13, 0, 0, 0), |
| ); |
| const CupertinoDynamicColor _kDefaultBorderColor = CupertinoDynamicColor.withBrightness( |
| color: Color.fromARGB(255, 209, 209, 214), |
| darkColor: Color.fromARGB(50, 128, 128, 128), |
| ); |
| const CupertinoDynamicColor _kDefaultFillColor = CupertinoDynamicColor.withBrightness( |
| color: CupertinoColors.activeBlue, |
| darkColor: Color.fromARGB(255, 50, 100, 215), |
| ); |
| const Color _kDefaultCheckColor = CupertinoDynamicColor.withBrightness( |
| color: CupertinoColors.white, |
| darkColor: Color.fromARGB(255, 222, 232, 248), |
| ); |
| const double _kPressedOverlayOpacity = 0.15; |
| // In dark mode, the fill color of a checkbox is an opacity gradient of the |
| // background color. |
| const List<double> _kDarkGradientOpacities = <double>[0.14, 0.29]; |
| const List<double> _kDisabledDarkGradientOpacities = <double>[0.08, 0.14]; |
| |
| /// A macOS style checkbox. |
| /// |
| /// {@youtube 560 315 https://www.youtube.com/watch?v=ua54JU7k1Us} |
| /// |
| /// The checkbox itself does not maintain any state. Instead, when the state of |
| /// the checkbox changes, the widget calls the [onChanged] callback. Most |
| /// widgets that use a checkbox will listen for the [onChanged] callback and |
| /// rebuild the checkbox with a new [value] to update the visual appearance of |
| /// the checkbox. |
| /// |
| /// The checkbox can optionally display three values - true, false, and null - |
| /// if [tristate] is true. When [value] is null a dash is displayed. By default |
| /// [tristate] is false and the checkbox's [value] must be true or false. |
| /// |
| /// In the Apple Human Interface Guidelines (HIG), checkboxes are encouraged for |
| /// use on macOS, but is silent about their use on iOS. If a multi-selection |
| /// component is needed on iOS, the HIG encourages the developer to use switches |
| /// ([CupertinoSwitch] in Flutter) instead, or to find a creative custom |
| /// solution. |
| /// |
| /// Visually, the checkbox is a square of [CupertinoCheckbox.width] pixels. |
| /// However, the widget's tap target and layout size depend on the platform: |
| /// * On desktop devices, the tap target matches the visual size. |
| /// * On mobile devices, the tap target expands to a square of |
| /// [kMinInteractiveDimensionCupertino] pixels to meet accessibility |
| /// guidelines. |
| /// |
| /// {@tool dartpad} |
| /// This example shows a toggleable [CupertinoCheckbox]. |
| /// |
| /// ** See code in examples/api/lib/cupertino/checkbox/cupertino_checkbox.0.dart ** |
| /// {@end-tool} |
| /// |
| /// See also: |
| /// |
| /// * [Checkbox], the Material Design equivalent. |
| /// * [CupertinoSwitch], a widget with semantics similar to [CupertinoCheckbox]. |
| /// * [CupertinoSlider], for selecting a value in a range. |
| /// * <https://developer.apple.com/design/human-interface-guidelines/components/selection-and-input/toggles/> |
| class CupertinoCheckbox extends StatefulWidget { |
| /// Creates a macOS-styled checkbox. |
| /// |
| /// The checkbox itself does not maintain any state. Instead, when the state of |
| /// the checkbox changes, the widget calls the [onChanged] callback. Most |
| /// widgets that use a checkbox will listen for the [onChanged] callback and |
| /// rebuild the checkbox with a new [value] to update the visual appearance of |
| /// the checkbox. |
| /// |
| /// The following arguments are required: |
| /// |
| /// * [value], which determines whether the checkbox is checked. The [value] |
| /// can only be null if [tristate] is true. |
| /// * [onChanged], which is called when the value of the checkbox should |
| /// change. It can be set to null to disable the checkbox. |
| const CupertinoCheckbox({ |
| super.key, |
| required this.value, |
| this.tristate = false, |
| required this.onChanged, |
| this.mouseCursor, |
| this.activeColor, |
| @Deprecated( |
| 'Use fillColor instead. ' |
| 'fillColor now manages the background color in all states. ' |
| 'This feature was deprecated after v3.24.0-0.2.pre.', |
| ) |
| this.inactiveColor, |
| this.fillColor, |
| this.checkColor, |
| this.focusColor, |
| this.focusNode, |
| this.autofocus = false, |
| this.side, |
| this.shape, |
| this.tapTargetSize, |
| this.semanticLabel, |
| }) : assert(tristate || value != null); |
| |
| /// Whether this checkbox is checked. |
| /// |
| /// When [tristate] is true, a value of null corresponds to the mixed state. |
| /// When [tristate] is false, this value must not be null. This is asserted in |
| /// debug mode. |
| final bool? value; |
| |
| /// Called when the value of the checkbox should change. |
| /// |
| /// The checkbox passes the new value to the callback but does not actually |
| /// change state until the parent widget rebuilds the checkbox with the new |
| /// value. |
| /// |
| /// If this callback is null, the checkbox will be displayed as disabled |
| /// and will not respond to input gestures. |
| /// |
| /// When the checkbox is tapped, if [tristate] is false (the default) then |
| /// the [onChanged] callback will be applied to `!value`. If [tristate] is |
| /// true this callback cycle from false to true to null and back to false |
| /// again. |
| /// |
| /// The callback provided to [onChanged] should update the state of the parent |
| /// [StatefulWidget] using the [State.setState] method, so that the parent |
| /// gets rebuilt; for example: |
| /// |
| /// ```dart |
| /// CupertinoCheckbox( |
| /// value: _throwShotAway, |
| /// onChanged: (bool? newValue) { |
| /// setState(() { |
| /// _throwShotAway = newValue!; |
| /// }); |
| /// }, |
| /// ) |
| /// ``` |
| final ValueChanged<bool?>? onChanged; |
| |
| /// The cursor for a mouse pointer when it enters or is hovering over the |
| /// widget. |
| /// |
| /// If [mouseCursor] is a [WidgetStateMouseCursor], |
| /// [WidgetStateProperty.resolve] is used for the following [WidgetState]s: |
| /// |
| /// * [WidgetState.selected]. |
| /// * [WidgetState.focused]. |
| /// * [WidgetState.disabled]. |
| /// |
| /// When [value] is null and [tristate] is true, [WidgetState.selected] is |
| /// included as a state. |
| /// |
| /// If null, then [SystemMouseCursors.basic] is used when this checkbox is |
| /// disabled. When the checkbox is enabled, [SystemMouseCursors.click] is used |
| /// on Web, and [SystemMouseCursors.basic] is used on other platforms. |
| /// |
| /// See also: |
| /// |
| /// * [WidgetStateMouseCursor], a [MouseCursor] that implements |
| /// [WidgetStateProperty] which is used in APIs that need to accept |
| /// either a [MouseCursor] or a [WidgetStateProperty]. |
| final MouseCursor? mouseCursor; |
| |
| /// The color to use when this checkbox is checked. |
| /// |
| /// If [fillColor] returns a non-null color in the [WidgetState.selected] |
| /// state, [fillColor] will be used instead of [activeColor]. |
| /// |
| /// Defaults to [CupertinoColors.activeBlue]. |
| final Color? activeColor; |
| |
| /// {@template flutter.cupertino.CupertinoCheckbox.fillColor} |
| /// The color used to fill this checkbox. |
| /// |
| /// Resolves in the following states: |
| /// * [WidgetState.selected]. |
| /// * [WidgetState.hovered]. |
| /// * [WidgetState.focused]. |
| /// * [WidgetState.disabled]. |
| /// |
| /// {@tool snippet} |
| /// This example resolves the [fillColor] based on the current [WidgetState] |
| /// of the [CupertinoCheckbox], providing a different [Color] when it is |
| /// [WidgetState.disabled]. |
| /// |
| /// ```dart |
| /// CupertinoCheckbox( |
| /// value: true, |
| /// onChanged: (_){}, |
| /// fillColor: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) { |
| /// if (states.contains(WidgetState.disabled)) { |
| /// return Colors.orange.withValues(alpha: .32); |
| /// } |
| /// return Colors.orange; |
| /// }) |
| /// ) |
| /// ``` |
| /// {@end-tool} |
| /// {@endtemplate} |
| /// |
| /// If [fillColor] resolves to null for the requested state, then the fill color |
| /// falls back to [activeColor] if the state includes [WidgetState.selected], |
| /// [CupertinoColors.white] at 50% opacity if checkbox is disabled, |
| /// and [CupertinoColors.white] otherwise. |
| final WidgetStateProperty<Color?>? fillColor; |
| |
| /// The color used if the checkbox is inactive. |
| /// |
| /// Currently [inactiveColor] is not used. Instead, [fillColor] controls the |
| /// color of the background in all states, including when unselected. |
| @Deprecated( |
| 'Use fillColor instead. ' |
| 'fillColor now manages the background color in all states. ' |
| 'This feature was deprecated after v3.24.0-0.2.pre.', |
| ) |
| final Color? inactiveColor; |
| |
| /// The color to use for the check icon when this checkbox is checked. |
| /// |
| /// If null, then the value of [CupertinoColors.white] is used if the checkbox |
| /// is enabled. If the checkbox is disabled, a grey-black color is used. |
| final Color? checkColor; |
| |
| /// If true, the checkbox's [value] can be true, false, or null. |
| /// |
| /// [CupertinoCheckbox] displays a dash when its value is null. |
| /// |
| /// When a tri-state checkbox ([tristate] is true) is tapped, its [onChanged] |
| /// callback will be applied to true if the current value is false, to null if |
| /// value is true, and to false if value is null (i.e. it cycles through false |
| /// => true => null => false when tapped). |
| /// |
| /// If tristate is false (the default), [value] must not be null, and |
| /// [onChanged] will only toggle between true and false. |
| final bool tristate; |
| |
| /// The color for the checkbox's border shadow when it has the input focus. |
| /// |
| /// If null, then a paler form of the [activeColor] will be used. |
| final Color? focusColor; |
| |
| /// {@macro flutter.widgets.Focus.focusNode} |
| final FocusNode? focusNode; |
| |
| /// {@macro flutter.widgets.Focus.autofocus} |
| final bool autofocus; |
| |
| /// The color and width of the checkbox's border. |
| /// |
| /// This property can be a [WidgetStateBorderSide] that can |
| /// specify different border color and widths depending on the |
| /// checkbox's state. |
| /// |
| /// Resolves in the following states: |
| /// * [WidgetState.pressed]. |
| /// * [WidgetState.selected]. |
| /// * [WidgetState.hovered]. |
| /// * [WidgetState.focused]. |
| /// * [WidgetState.disabled]. |
| /// * [WidgetState.error]. |
| /// |
| /// If this property is not a [WidgetStateBorderSide] 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. |
| /// |
| /// If this property is null and the checkbox's value is false, then the side |
| /// defaults to a one pixel wide grey-black border. |
| final BorderSide? side; |
| |
| /// The shape of the checkbox. |
| /// |
| /// If this property is null then the shape defaults to a |
| /// [RoundedRectangleBorder] with a circular corner radius of 4.0. |
| final OutlinedBorder? shape; |
| |
| /// The tap target and layout size of the checkbox. |
| /// |
| /// If this property is null, the tap target size defaults to a square of |
| /// [CupertinoCheckbox.width] pixels on desktop devices and |
| /// [kMinInteractiveDimensionCupertino] pixels on mobile devices. |
| final Size? tapTargetSize; |
| |
| /// The semantic label for the checkbox that will be announced by screen readers. |
| /// |
| /// This is announced by assistive technologies (e.g TalkBack/VoiceOver). |
| /// |
| /// This label does not show in the UI. |
| final String? semanticLabel; |
| |
| /// The width of a checkbox widget. |
| static const double width = 14.0; |
| |
| @override |
| State<CupertinoCheckbox> createState() => _CupertinoCheckboxState(); |
| } |
| |
| class _CupertinoCheckboxState extends State<CupertinoCheckbox> |
| with TickerProviderStateMixin, ToggleableStateMixin { |
| final _CheckboxPainter _painter = _CheckboxPainter(); |
| bool? _previousValue; |
| |
| bool focused = false; |
| |
| @override |
| void initState() { |
| super.initState(); |
| _previousValue = widget.value; |
| } |
| |
| @override |
| void didUpdateWidget(CupertinoCheckbox oldWidget) { |
| super.didUpdateWidget(oldWidget); |
| if (oldWidget.value != widget.value) { |
| _previousValue = oldWidget.value; |
| } |
| } |
| |
| @override |
| void dispose() { |
| _painter.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| ValueChanged<bool?>? get onChanged => widget.onChanged; |
| |
| @override |
| bool get tristate => widget.tristate; |
| |
| @override |
| bool? get value => widget.value; |
| |
| WidgetStateProperty<Color> get _defaultFillColor { |
| return WidgetStateProperty.resolveWith((Set<WidgetState> states) { |
| if (states.contains(WidgetState.disabled)) { |
| return CupertinoColors.white.withOpacity(0.5); |
| } |
| if (states.contains(WidgetState.selected)) { |
| return widget.activeColor ?? CupertinoDynamicColor.resolve(_kDefaultFillColor, context); |
| } |
| return CupertinoColors.white; |
| }); |
| } |
| |
| WidgetStateProperty<Color> get _defaultCheckColor { |
| return WidgetStateProperty.resolveWith((Set<WidgetState> states) { |
| if (states.contains(WidgetState.disabled) && states.contains(WidgetState.selected)) { |
| return widget.checkColor ?? CupertinoDynamicColor.resolve(_kDisabledCheckColor, context); |
| } |
| if (states.contains(WidgetState.selected)) { |
| return widget.checkColor ?? CupertinoDynamicColor.resolve(_kDefaultCheckColor, context); |
| } |
| return CupertinoColors.white; |
| }); |
| } |
| |
| WidgetStateProperty<BorderSide> get _defaultSide { |
| return WidgetStateProperty.resolveWith((Set<WidgetState> states) { |
| if ((states.contains(WidgetState.selected) || states.contains(WidgetState.focused)) && |
| !states.contains(WidgetState.disabled)) { |
| return const BorderSide(width: 0.0, color: CupertinoColors.transparent); |
| } |
| if (states.contains(WidgetState.disabled)) { |
| return BorderSide(color: CupertinoDynamicColor.resolve(_kDisabledBorderColor, context)); |
| } |
| return BorderSide(color: CupertinoDynamicColor.resolve(_kDefaultBorderColor, context)); |
| }); |
| } |
| |
| BorderSide? _resolveSide(BorderSide? side, Set<WidgetState> states) { |
| if (side is WidgetStateBorderSide) { |
| return WidgetStateProperty.resolveAs<BorderSide?>(side, states); |
| } |
| if (!states.contains(WidgetState.selected)) { |
| return side; |
| } |
| return null; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| // Colors need to be resolved in selected and non selected states separately. |
| // The `states` getter constructs a new set every time, making it safe to edit in place. |
| final Set<WidgetState> activeStates = states..add(WidgetState.selected); |
| final Set<WidgetState> inactiveStates = states..remove(WidgetState.selected); |
| |
| // Since the states getter always makes a new set, make a copy to use |
| // throughout the lifecycle of this build method. |
| final Set<WidgetState> currentStates = states; |
| |
| final Color effectiveActiveColor = |
| widget.fillColor?.resolve(activeStates) ?? _defaultFillColor.resolve(activeStates); |
| |
| final Color effectiveInactiveColor = |
| widget.fillColor?.resolve(inactiveStates) ?? _defaultFillColor.resolve(inactiveStates); |
| |
| final BorderSide effectiveBorderSide = |
| _resolveSide(widget.side, currentStates) ?? _defaultSide.resolve(currentStates); |
| |
| final Color effectiveFocusOverlayColor = |
| widget.focusColor ?? |
| HSLColor.fromColor(effectiveActiveColor.withOpacity(kCupertinoFocusColorOpacity)) |
| .withLightness(kCupertinoFocusColorBrightness) |
| .withSaturation(kCupertinoFocusColorSaturation) |
| .toColor(); |
| |
| final WidgetStateProperty<MouseCursor> effectiveMouseCursor = |
| WidgetStateProperty.resolveWith<MouseCursor>((Set<WidgetState> states) { |
| return WidgetStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states) ?? |
| (kIsWeb && !states.contains(WidgetState.disabled) |
| ? SystemMouseCursors.click |
| : SystemMouseCursors.basic); |
| }); |
| |
| final Size effectiveSize = |
| widget.tapTargetSize ?? |
| switch (defaultTargetPlatform) { |
| TargetPlatform.iOS || |
| TargetPlatform.android || |
| TargetPlatform.fuchsia => const Size.square(kMinInteractiveDimensionCupertino), |
| TargetPlatform.macOS || |
| TargetPlatform.linux || |
| TargetPlatform.windows => const Size.square(CupertinoCheckbox.width), |
| }; |
| |
| return Semantics( |
| label: widget.semanticLabel, |
| checked: widget.value ?? false, |
| mixed: widget.tristate ? widget.value == null : null, |
| child: buildToggleable( |
| mouseCursor: effectiveMouseCursor, |
| focusNode: widget.focusNode, |
| autofocus: widget.autofocus, |
| size: effectiveSize, |
| painter: _painter |
| ..position = position |
| ..reaction = reaction |
| ..focusColor = effectiveFocusOverlayColor |
| ..downPosition = downPosition |
| ..isFocused = currentStates.contains(WidgetState.focused) |
| ..isHovered = currentStates.contains(WidgetState.hovered) |
| ..activeColor = effectiveActiveColor |
| ..inactiveColor = effectiveInactiveColor |
| ..checkColor = _defaultCheckColor.resolve(currentStates) |
| ..value = value |
| ..previousValue = _previousValue |
| ..isActive = widget.onChanged != null |
| ..shape = widget.shape ?? RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)) |
| ..side = effectiveBorderSide |
| ..brightness = CupertinoTheme.of(context).brightness, |
| ), |
| ); |
| } |
| } |
| |
| class _CheckboxPainter extends ToggleablePainter { |
| Color get checkColor => _checkColor!; |
| Color? _checkColor; |
| set checkColor(Color value) { |
| if (_checkColor == value) { |
| return; |
| } |
| _checkColor = value; |
| notifyListeners(); |
| } |
| |
| bool? get value => _value; |
| bool? _value; |
| set value(bool? value) { |
| if (_value == value) { |
| return; |
| } |
| _value = value; |
| notifyListeners(); |
| } |
| |
| bool? get previousValue => _previousValue; |
| bool? _previousValue; |
| set previousValue(bool? value) { |
| if (_previousValue == value) { |
| return; |
| } |
| _previousValue = value; |
| notifyListeners(); |
| } |
| |
| OutlinedBorder get shape => _shape!; |
| OutlinedBorder? _shape; |
| set shape(OutlinedBorder value) { |
| if (_shape == value) { |
| return; |
| } |
| _shape = value; |
| notifyListeners(); |
| } |
| |
| BorderSide get side => _side!; |
| BorderSide? _side; |
| set side(BorderSide value) { |
| if (_side == value) { |
| return; |
| } |
| _side = value; |
| notifyListeners(); |
| } |
| |
| Brightness? get brightness => _brightness; |
| Brightness? _brightness; |
| set brightness(Brightness? value) { |
| if (_brightness == value) { |
| return; |
| } |
| _brightness = value; |
| notifyListeners(); |
| } |
| |
| Rect _outerRectAt(Offset origin) { |
| const double size = CupertinoCheckbox.width; |
| final rect = Rect.fromLTWH(origin.dx, origin.dy, size, size); |
| return rect; |
| } |
| |
| // The checkbox's border color if value == false, or its fill color when |
| // value == true or null. |
| Color _colorAt(bool value) { |
| return value && isActive ? activeColor : inactiveColor; |
| } |
| |
| // White stroke used to paint the check and dash. |
| Paint _createStrokePaint() { |
| return Paint() |
| ..color = checkColor |
| ..style = PaintingStyle.stroke |
| ..strokeWidth = 2.0 |
| ..strokeCap = StrokeCap.round; |
| } |
| |
| // Draw a gradient from the top to the bottom of the checkbox. |
| void _drawFillGradient(Canvas canvas, Rect outer, Color topColor, Color bottomColor) { |
| final fillGradient = LinearGradient( |
| begin: Alignment.topCenter, |
| end: Alignment.bottomCenter, |
| // Eyeballed from a checkbox on a physical Macbook Pro running macOS version 14.5. |
| colors: <Color>[topColor, bottomColor], |
| ); |
| final gradientPaint = Paint()..shader = fillGradient.createShader(outer); |
| canvas.drawPath(shape.getOuterPath(outer), gradientPaint); |
| } |
| |
| void _drawBox(Canvas canvas, Rect outer, Paint paint, BorderSide? side, bool value) { |
| // Draw a gradient in dark mode except when the checkbox is enabled and checked. |
| if (brightness == Brightness.dark && !(isActive && value)) { |
| _drawFillGradient( |
| canvas, |
| outer, |
| paint.color.withOpacity( |
| isActive ? _kDarkGradientOpacities[0] : _kDisabledDarkGradientOpacities[0], |
| ), |
| paint.color.withOpacity( |
| isActive ? _kDarkGradientOpacities[1] : _kDisabledDarkGradientOpacities[1], |
| ), |
| ); |
| } else { |
| canvas.drawPath(shape.getOuterPath(outer), paint); |
| } |
| if (side != null) { |
| shape.copyWith(side: side).paint(canvas, outer); |
| } |
| } |
| |
| void _drawCheck(Canvas canvas, Offset origin, Paint paint) { |
| final path = Path(); |
| // The ratios for the offsets below were found from looking at the checkbox |
| // examples on in the HIG docs. The distance from the needed point to the |
| // edge was measured, then divided by the total width. |
| const start = Offset(CupertinoCheckbox.width * 0.22, CupertinoCheckbox.width * 0.54); |
| const mid = Offset(CupertinoCheckbox.width * 0.40, CupertinoCheckbox.width * 0.75); |
| const end = Offset(CupertinoCheckbox.width * 0.78, CupertinoCheckbox.width * 0.25); |
| path.moveTo(origin.dx + start.dx, origin.dy + start.dy); |
| path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy); |
| path.moveTo(origin.dx + mid.dx, origin.dy + mid.dy); |
| path.lineTo(origin.dx + end.dx, origin.dy + end.dy); |
| canvas.drawPath(path, paint); |
| } |
| |
| void _drawDash(Canvas canvas, Offset origin, Paint paint) { |
| // From measuring the checkbox example in the HIG docs, the dash was found |
| // to be half the total width, centered in the middle. |
| const start = Offset(CupertinoCheckbox.width * 0.25, CupertinoCheckbox.width * 0.5); |
| const end = Offset(CupertinoCheckbox.width * 0.75, CupertinoCheckbox.width * 0.5); |
| canvas.drawLine(origin + start, origin + end, paint); |
| } |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| final Paint strokePaint = _createStrokePaint(); |
| final origin = size / 2.0 - const Size.square(CupertinoCheckbox.width) / 2.0 as Offset; |
| final Rect outer = _outerRectAt(origin); |
| final paint = Paint()..color = _colorAt(value ?? true); |
| |
| switch (value) { |
| case false: |
| _drawBox(canvas, outer, paint, side, value ?? true); |
| case true: |
| _drawBox(canvas, outer, paint, side, value ?? true); |
| _drawCheck(canvas, origin, strokePaint); |
| case null: |
| _drawBox(canvas, outer, paint, side, value ?? true); |
| _drawDash(canvas, origin, strokePaint); |
| } |
| // The checkbox's opacity changes when pressed. |
| if (downPosition != null) { |
| final pressedPaint = Paint() |
| ..color = brightness == Brightness.light |
| ? CupertinoColors.black.withOpacity(_kPressedOverlayOpacity) |
| : CupertinoColors.white.withOpacity(_kPressedOverlayOpacity); |
| canvas.drawPath(shape.getOuterPath(outer), pressedPaint); |
| } |
| if (isFocused) { |
| final Rect focusOuter = outer.inflate(1); |
| final borderPaint = Paint() |
| ..color = focusColor |
| ..style = PaintingStyle.stroke |
| ..strokeWidth = 3.5; |
| _drawBox(canvas, focusOuter, borderPaint, side, value ?? true); |
| } |
| } |
| } |