Skip to content

Tri-state Checkbox #14611

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 13, 2018
Merged

Conversation

HansMuller
Copy link
Contributor

Added optional support for tri-state checkboxes, i.e. a checkbox whose value can be true, false, or null.

The checkboxes on the right were created with triState: true, value: null.

checkbox

@@ -21,6 +21,10 @@ import 'toggleable.dart';
/// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[triState] and [value] on the last line

/// * [value], which determines whether the checkbox is checked, and must not
/// be null.
/// * [value], which determines whether the checkbox is checked. The value can
/// only be be null if triState is true.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tryState]

const Checkbox({
Key key,
@required this.value,
this.triState: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably spell this "tristate", since "tri" isn't a word, but a prefix.

/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[onChanged]

///
/// 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 will be applied to false if the current value is true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[value]

/// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean by "applied to" in this paragraph

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, ok. I think that phrasing would be confusing to our less mathematically-inclined users. I'd probably just say "will be called with the value" or some such.

/// Checkbox displays a dash when its value is null.
///
/// When a tri-state checkbox is tapped its [onChanged] callback will be
/// applied to true if the current value is null or false, false otherwise.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what "applied to" means here either

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inactiveColor: inactiveColor,
onChanged: onChanged,
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius),
vsync: vsync);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: missing newline after last argument

/// The [value], [activeColor], and [inactiveColor] arguments must not be
/// null.
/// The [activeColor], and [inactiveColor] arguments must not be
/// null. The [value] can only be null if triState is true.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[triState]

/// the value 0.0. When the control is active, the value is true and this
/// animation has the value 1.0. When the control is changing from inactive
/// the value 0.0. When the control is active, the value either true or triState
/// is true and the value is null. When the control is active the animation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[triState], [value]

/// False if this control is "inactive" (not checked, off, or unselected).
///
/// If value is true then the control "active" (checked, on, or selected). If
/// triState is true and value is null, then the control is considered to be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[triState], [value]

@@ -290,7 +318,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
config.isEnabled = isInteractive;
if (isInteractive)
config.onTap = _handleTap;
config.isChecked = _value;
config.isChecked = _value != false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to convey the null state to the accessibility system

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to defer that to a separate PR. I've discussed it with @goderbauer; support for tristates isn't trivial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you leave a TODO here and/or file a bug?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we should have the rule that all new widgets support accessibility, text scaling, localization, and bidi out of the box, but if there's a reason to make an exception here then ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a TODO and filed #14677

@@ -241,7 +269,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {

void _handleTap() {
if (isInteractive)
onChanged(!_value);
onChanged(value == false);
Copy link
Contributor

@Hixie Hixie Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if tristate is true, this doesn't seem to give the user a way to get back to the indeterminate state. It seems like if tristate is true, this should go in the loop false -> true -> indeterminate -> false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was intentional however I see that HTML tristates do cycle this way. So we can too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason to do it this way is that if someone doesn't want this behaviour, they can easily just set tristate to false once the value isn't null, but if they do want this behaviour, there's nothing they can do if we don't implement it by default.

await tester.tap(find.byType(Checkbox));
await tester.pumpAndSettle();
expect(checkBoxValue, true);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should verify that animations correctly handle:

true -> false
true -> indeterminate
false -> true
false -> indeterminate
indeterminate -> true
indeterminate -> false

@HansMuller
Copy link
Contributor Author

Added a TODO and filed #14674 for smooth segues for cases where the checkbox value changes in the middle its animation cycle

@Hixie
Copy link
Contributor

Hixie commented Feb 13, 2018

LGTM

@HansMuller HansMuller merged commit 2aa9bb2 into flutter:master Feb 13, 2018
DaveShuckerow pushed a commit to DaveShuckerow/flutter that referenced this pull request May 14, 2018
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants