From db6d3d7c3adb73e9811bfb16950f43b9946b6bad Mon Sep 17 00:00:00 2001 From: Paul Kepinski Date: Fri, 11 Nov 2022 23:57:34 +0100 Subject: [PATCH] Add comments on new controls widgets (#382) Fixes #374 --- lib/src/controls/yaru_checkbox.dart | 100 ++++++++++++++++++++++-- lib/src/controls/yaru_radio.dart | 109 +++++++++++++++++++++++++-- lib/src/controls/yaru_switch.dart | 75 ++++++++++++++++-- lib/src/controls/yaru_togglable.dart | 62 ++++++++++----- 4 files changed, 308 insertions(+), 38 deletions(-) diff --git a/lib/src/controls/yaru_checkbox.dart b/lib/src/controls/yaru_checkbox.dart index 0c1554940..aedf2cad5 100644 --- a/lib/src/controls/yaru_checkbox.dart +++ b/lib/src/controls/yaru_checkbox.dart @@ -1,25 +1,113 @@ import 'package:flutter/material.dart'; import '../constants.dart'; +import 'yaru_check_button.dart'; +import 'yaru_radio.dart'; +import 'yaru_switch.dart'; import 'yaru_togglable.dart'; const _kCheckboxBorderRadius = Radius.circular(4); const _kCheckboxDashStroke = 2.0; const _kDashSizeFactor = 0.52; -class YaruCheckbox extends YaruTogglable { +/// A Yaru 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 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. +/// +/// See also: +/// +/// * [YaruCheckButton], a desktop style check button with an interactive label. +/// * [YaruSwitch], a widget with semantics similar to [Checkbox]. +/// * [YaruRadio], for selecting among a set of explicit values. +/// * [Slider], for selecting a value in a range. +class YaruCheckbox extends StatefulWidget implements YaruTogglable { + /// A Yaru 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. const YaruCheckbox({ super.key, - required super.value, - super.tristate, - required super.onChanged, - super.focusNode, - super.autofocus, + required this.value, + this.tristate = false, + required this.onChanged, + this.focusNode, + this.autofocus = false, }) : assert(tristate || value != null); + /// Whether this checkbox is checked. + /// + /// This property must not be null. + @override + final bool? value; + + /// If true the checkbox's [value] can be true, false, or null. + /// + /// Checkbox 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. + @override + final bool tristate; + @override bool? get checked => 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. + /// + /// 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 + /// YaruCheckbox( + /// value: _throwShotAway, + /// onChanged: (bool? newValue) { + /// setState(() { + /// _throwShotAway = newValue!; + /// }); + /// }, + /// ) + /// ``` + @override + final ValueChanged? onChanged; + + @override + bool get interactive => onChanged != null; + + /// {@macro flutter.widgets.Focus.focusNode} + @override + final FocusNode? focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + @override + final bool autofocus; + @override YaruTogglableState createState() { return _YaruCheckboxState(); diff --git a/lib/src/controls/yaru_radio.dart b/lib/src/controls/yaru_radio.dart index 97ee7a015..11f691c50 100644 --- a/lib/src/controls/yaru_radio.dart +++ b/lib/src/controls/yaru_radio.dart @@ -1,27 +1,122 @@ import 'package:flutter/material.dart'; import '../constants.dart'; +import 'yaru_checkbox.dart'; +import 'yaru_radio_button.dart'; +import 'yaru_switch.dart'; import 'yaru_togglable.dart'; const _kDotSizeFactor = 0.4; -class YaruRadio extends YaruTogglable { +/// A Yaru radio. +/// +/// Used to select between a number of mutually exclusive values. When one radio +/// button in a group is selected, the other radio buttons in the group cease to +/// be selected. The values are of type [T], the type parameter of the [YaruRadio] +/// class. Enums are commonly used for this purpose. +/// +/// The radio button itself does not maintain any state. Instead, selecting the +/// radio invokes the [onChanged] callback, passing [value] as a parameter. If +/// [groupValue] and [value] match, this radio will be selected. Most widgets +/// will respond to [onChanged] by calling [State.setState] to update the +/// radio button's [groupValue]. +/// +/// See also: +/// +/// * [YaruRadioButton], a desktop style radio button with an interactive label. +/// * [Slider], for selecting a value in a range. +/// * [YaruCheckbox] and [YaruSwitch], for toggling a particular value on or off. +class YaruRadio extends StatefulWidget implements YaruTogglable { + /// Create a Yaru radio. + /// + /// The radio button itself does not maintain any state. Instead, selecting the + /// radio invokes the [onChanged] callback, passing [value] as a parameter. If + /// [groupValue] and [value] match, this radio will be selected. Most widgets + /// will respond to [onChanged] by calling [State.setState] to update the + /// radio button's [groupValue]. const YaruRadio({ super.key, - required super.value, + required this.value, required this.groupValue, this.toggleable = false, - required super.onChanged, - super.focusNode, - super.autofocus, + required this.onChanged, + this.focusNode, + this.autofocus = false, }) : assert(toggleable || value != null); - final bool toggleable; + /// The value represented by this radio button. + @override + final T value; + + /// The currently selected value for a group of radio buttons. + /// + /// This radio button is considered selected if its [value] matches the + /// [groupValue]. + final T? groupValue; @override bool get checked => value == groupValue; - final T? groupValue; + /// Set to true if this radio button is allowed to be returned to an + /// indeterminate state by selecting it again when selected. + /// + /// To indicate returning to an indeterminate state, [onChanged] will be + /// called with null. + /// + /// If true, [onChanged] can be called with [value] when selected while + /// [groupValue] != [value], or with null when selected again while + /// [groupValue] == [value]. + /// + /// If false, [onChanged] will be called with [value] when it is selected + /// while [groupValue] != [value], and only by selecting another radio button + /// in the group (i.e. changing the value of [groupValue]) can this radio + /// button be unselected. + /// + /// The default is false. + final bool toggleable; + + @override + bool get tristate => toggleable; + + /// Called when the user selects this radio button. + /// + /// The radio button passes [value] as a parameter to this callback. The radio + /// button does not actually change state until the parent widget rebuilds the + /// radio button with the new [groupValue]. + /// + /// If null, the radio button will be displayed as disabled. + /// + /// The provided callback will not be invoked if this radio button is already + /// selected. + /// + /// 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 + /// YaruRadio( + /// value: SingingCharacter.lafayette, + /// groupValue: _character, + /// onChanged: (SingingCharacter newValue) { + /// setState(() { + /// _character = newValue; + /// }); + /// }, + /// ) + /// ``` + @override + final ValueChanged? onChanged; + + @override + bool get interactive => onChanged != null; + + /// {@macro flutter.widgets.Focus.focusNode} + @override + final FocusNode? focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + @override + final bool autofocus; @override YaruTogglableState> createState() { diff --git a/lib/src/controls/yaru_switch.dart b/lib/src/controls/yaru_switch.dart index ec584bf4c..2efd781ec 100644 --- a/lib/src/controls/yaru_switch.dart +++ b/lib/src/controls/yaru_switch.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'yaru_checkbox.dart'; +import 'yaru_radio.dart'; +import 'yaru_switch_button.dart'; import 'yaru_togglable.dart'; const _kSwitchActivableAreaPadding = @@ -7,21 +10,83 @@ const _kSwitchActivableAreaPadding = const _kSwitchSize = Size(55, 30); const _kSwitchDotSizeFactor = 0.8; -class YaruSwitch extends YaruTogglable { +/// A Yaru switch. +/// +/// Used to toggle the on/off state of a single setting. +/// +/// The switch itself does not maintain any state. Instead, when the state of +/// the switch changes, the widget calls the [onChanged] callback. Most widgets +/// that use a switch will listen for the [onChanged] callback and rebuild the +/// switch with a new [value] to update the visual appearance of the switch. +/// +/// If the [onChanged] callback is null, then the switch will be disabled (it +/// will not respond to input). A disabled switch's thumb and track are rendered +/// in shades of grey by default. The default appearance of a disabled switch +/// can be overridden with [inactiveThumbColor] and [inactiveTrackColor]. +/// +/// See also: +/// +/// * [YaruSwitchButton], a desktop style switch button with an interactive label. +/// * [YaruCheckbox], another widget with similar semantics. +/// * [YaruRadio], for selecting among a set of explicit values. +/// * [Slider], for selecting a value in a range. +class YaruSwitch extends StatefulWidget implements YaruTogglable { const YaruSwitch({ super.key, - required super.value, - required super.onChanged, - super.focusNode, - super.autofocus, + required this.value, + required this.onChanged, + this.focusNode, + this.autofocus = false, }); + /// Whether this switch is on or off. + /// + /// This property must not be null. + @override + final bool value; + @override bool get checked => value; @override bool get tristate => false; + /// Called when the user toggles the switch on or off. + /// + /// The switch passes the new value to the callback but does not actually + /// change state until the parent widget rebuilds the switch with the new + /// value. + /// + /// If null, the switch will be displayed as disabled. + /// + /// 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 + /// YaruSwitch( + /// value: _giveVerse, + /// onChanged: (bool newValue) { + /// setState(() { + /// _giveVerse = newValue; + /// }); + /// }, + /// ) + /// ``` + @override + final ValueChanged? onChanged; + + @override + bool get interactive => onChanged != null; + + /// {@macro flutter.widgets.Focus.focusNode} + @override + final FocusNode? focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + @override + final bool autofocus; + @override YaruTogglableState createState() { return _YaruSwitchState(); diff --git a/lib/src/controls/yaru_togglable.dart b/lib/src/controls/yaru_togglable.dart index 9fed1ffcf..59604cea0 100644 --- a/lib/src/controls/yaru_togglable.dart +++ b/lib/src/controls/yaru_togglable.dart @@ -1,12 +1,21 @@ import 'package:flutter/material.dart'; import '../constants.dart'; +import 'yaru_checkbox.dart'; +import 'yaru_radio.dart'; +import 'yaru_switch.dart'; const _kTogglableAnimationDuration = Duration(milliseconds: 150); const _kTogglableSizeAnimationDuration = Duration(milliseconds: 100); const _kIndicatorAnimationDuration = Duration(milliseconds: 200); const _kIndicatorRadius = 20.0; +/// A generic class to create a togglable widget +/// +/// See also: +/// +/// * [YaruCheckbox] and [YaruSwitch], for toggling a particular value on or off. +/// * [YaruRadio], for selecting among a set of explicit values. abstract class YaruTogglable extends StatefulWidget { const YaruTogglable({ super.key, @@ -17,27 +26,40 @@ abstract class YaruTogglable extends StatefulWidget { this.autofocus = false, }); + /// Value of this [YaruTogglable]. final T value; + /// By default, a [YaruTogglable] widget can only handle two state. + /// If true, it will be able to display tree values. final bool tristate; + /// Getter used to link [T] to a [bool] value. + /// If true, the [YaruTogglable] will be considered as checked. bool? get checked; + /// The [YaruTogglable] itself does not maintain any state. Instead, when the state of + /// the [YaruTogglable] changes, the widget calls the [onChanged] callback. + /// The callback provided to [onChanged] should update the state of the parent + /// [StatefulWidget] using the [State.setState] method, so that the parent + /// gets rebuilt. final ValueChanged? onChanged; - bool get interactive => onChanged != null; + /// Determine if this [YaruTogglable] can handle events. + bool get interactive; + /// {@macro flutter.widgets.Focus.focusNode} final FocusNode? focusNode; + /// {@macro flutter.widgets.Focus.autofocus} final bool autofocus; } abstract class YaruTogglableState extends State with TickerProviderStateMixin { - bool hover = false; - bool focus = false; - bool active = false; - bool? oldChecked; + bool _hover = false; + bool _focus = false; + bool _active = false; + bool? _oldChecked; late CurvedAnimation _position; late AnimationController _positionController; @@ -59,7 +81,7 @@ abstract class YaruTogglableState extends State void initState() { super.initState(); - oldChecked = widget.checked; + _oldChecked = widget.checked; _positionController = AnimationController( duration: _kTogglableAnimationDuration, @@ -97,7 +119,7 @@ abstract class YaruTogglableState extends State super.didUpdateWidget(oldWidget); if (oldWidget.checked != widget.checked) { - oldChecked = oldWidget.checked; + _oldChecked = oldWidget.checked; if (widget.tristate) { if (widget.checked == null) { @@ -132,13 +154,13 @@ abstract class YaruTogglableState extends State } void _handleFocusChange(bool value) { - if (focus == value) { + if (_focus == value) { return; } - setState(() => focus = value); + setState(() => _focus = value); - if (focus) { + if (_focus) { _indicatorController.forward(); } else { _indicatorController.reverse(); @@ -146,13 +168,13 @@ abstract class YaruTogglableState extends State } void _handleHoverChange(bool value) { - if (hover == value) { + if (_hover == value) { return; } - setState(() => hover = value); + setState(() => _hover = value); - if (hover) { + if (_hover) { _indicatorController.forward(); } else { _indicatorController.reverse(); @@ -160,13 +182,13 @@ abstract class YaruTogglableState extends State } void _handleActiveChange(bool value) { - if (active == value) { + if (_active == value) { return; } - setState(() => active = value); + setState(() => _active = value); - if (active) { + if (_active) { _sizeController.forward(); } else { _sizeController.reverse(); @@ -235,11 +257,11 @@ abstract class YaruTogglableState extends State size: togglableSize, painter: painter ..interactive = widget.interactive - ..hover = hover - ..focus = focus - ..active = active + ..hover = _hover + ..focus = _focus + ..active = _active ..checked = widget.checked - ..oldChecked = oldChecked + ..oldChecked = _oldChecked ..position = _position ..sizePosition = _sizePosition ..indicatorPosition = _indicatorPosition