beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.29k stars 666 forks source link

Add widgets to differentiate between Switch and Checkbox #2225

Open freakboy3742 opened 10 months ago

freakboy3742 commented 10 months ago

What is the problem or limitation you are having?

Many platforms have support for both a Switch and a Checkbox widget; however, Toga doesn't currently expose this capability.

Describe the solution you'd like

Two new widgets, plus a change to an existing one:

The API for Switch, Checkbox and Toggle should be identical. The core should use the existing Switch code as a base class; with 2 subclasses providing the different factory instantiation. The backends will only need to add 1 extra implementation (where available), with the factory redirecting Toggle to the Switch or Checkbox implementation as required.

The documentation should recommend the use of "Toggle".

Describe alternatives you've considered

Retain the status quo - a single Switch widget that follows UX convention.

Rename Switch to Toggle to break the coupling between the widget and the specific implementation.

Additional context

Current platform support:

So - iOS and Windows are the only holdouts.

Examples of implementations for Switch for Windows and Checkbox on iOS exist; they are mostly "image button without extra chrome that changes image when pressed". While it is possible to fake it, Toga should adhere to platform guidelines, and omit those widget options. This will also serve to encourage the use of the "Toggle" widget.

The transition from Switch to Checkbox will require a backwards compatibility shim on Winforms to warn that toga.Switch will be removed in future.

Thanks to @Kuna42 for the suggestion (#2221)

freakboy3742 commented 10 months ago

Another idea that came out of a discussion with @mhsmith:

Toga's API has historically tried to present widgets that capture use cases, rather than specific widgets; to that end, instead of literally wrapping Checkbox and Switch and providing a design option to the user, we should find the use case that describes the difference between Checkboxes and Switches in practice.

The Android HIG provides a hint here - it provides both, saying:

This also highlights one of the big feature gaps in Toga - the lack of a RadioButton widget. However, the use case for Radio buttons is effectively the same as "a list of checkboxes".

The reading of the macOS HIG on toggles is broadly compatible with this interpretation.

So - there's effectively a need for 3 widgets in Toga:

  1. A widget for single boolean values. A switch on most platforms; but a checkbox on Windows, and arguably on macOS. This is what Switch is currently; there's an argument to be made it should be renamed "Toggle".
  2. A widget for multiple selections from (MultiSelection? MultipleOptionList?) - a list of checkboxes on all platforms except iOS (which doesn't support checkboxes). On iOS, it would be a list of switches.
  3. A widget for single selections from a fixed list (SingleSelection? SingleOptionList?) - generally a list of radio buttons, but on mobile more likely implemented as a selection pulldown.

The "Selection" name is already in use for selection of a single item from a dynamic list of items, so whatever name we pick for 2 and 3 should be conceptually consistent.

mhsmith commented 10 months ago

This is what Switch is currently; there's an argument to be made it should be renamed "Toggle".

I think Switch is clear enough; I don't remember seeing any of these elements referred to as a "toggle" except in the macOS HIG linked above.

A widget for multiple selections from (MultiSelection? MultipleOptionList?)

I like MultiSelection, because the API would be similar to Selection – maybe even identical, except that value would return a list.

A widget for single selections from a fixed list (SingleSelection? SingleOptionList?) - generally a list of radio buttons, but on mobile more likely implemented as a selection pulldown.

A list of radio buttons takes up much more space than a selection pulldown, which would make it difficult to use the same layout across all platforms. I see that Android has native radio buttons and iOS doesn't, but one alternative for iOS is a table view with a check mark.

As for the API, it would be identical to the existing Selection widget, so I wonder if we can just add it as a boolean constructor flag on that class.

In fact, we could merge both the checkbox-group and radio-group forms into the Selection API using a multiple_select flag similar to the Table.

mhsmith commented 10 months ago

Separately, we could always add a constructor argument to Switch to indicate a preference for one style over the other, without the need to create a whole separate class.

freakboy3742 commented 10 months ago

This is what Switch is currently; there's an argument to be made it should be renamed "Toggle".

I think Switch is clear enough; I don't remember seeing any of these elements referred to as a "toggle" except in the macOS HIG linked above.

Agreed that the name isn't one I've seen elsewhere, but that's kind of the point. Asking for a Switch and getting a checkbox is at least a little jarring. It's not a huge deal, and it comes at the cost of some code churn; but, we're also pre-1.0, so I'm OK with some code churn to avoid legacy issues we don't want to live with long term.

A widget for multiple selections from (MultiSelection? MultipleOptionList?)

I like MultiSelection, because the API would be similar to Selection – maybe even identical, except that value would return a list.

Agreed that most of the API should be the same, but I'd argue there's one critical difference between the current Selection and these new widgets: The data source must be static - you can't add/remove items to it over time. I guess this could be accomplished with a wrapper around ListSource that disables the list-mutating APIs.

I see that Android has native radio buttons and iOS doesn't, but one alternative for iOS is a table view with a check mark.

Good idea - I hadn't thought of that. It has the added advantage that the core of the implementation between single and multiple select will be similar.

As for the API, it would be identical to the existing Selection widget, so I wonder if we can just add it as a boolean constructor flag on that class.

In fact, we could merge both the checkbox-group and radio-group forms into the Selection API using a multiple_select flag similar to the Table. ... Separately, we could always add a constructor argument to Switch to indicate a preference for one style over the other, without the need to create a whole separate class.

Agreed - although, if possible, I'd like to find a way to HIG-driven way to express this, rather than explicitly referring to the form of the widget. "Switch/Checkbox" describes a specific form; if it's something that describes function rather than form, then it's a little less jarring when a platform doesn't satisfy the specific form requested, and it allows us to better adhere to platform HIGs.

For example, the macOS HIG suggests you should "avoid using a switch to control a single detail or a minor setting". On that basis, we could use "role=major" to display a switch, "role=minor" to display a checkbox on macOS, with the default role being minor. The Android HIG says that you shouldn't use checkboxes for standalone options, so no matter the role, we would return Switch.

Of course, the risk is that we develop an unintuitive representation for something that end users end up using as a simple "switch/checkbox" option, and we flooded with Stack Overflow answers that say "set role=major to get a checkbox" (which is sort of true, but is an answer that misses the point) and "how do I get a checkbox" questions on Android, and we end up in a new class of "but I want an on_click handler on Image" discussions.

I guess the middle ground would be to allow both: role = default | minor | major | switch | checkbox which allows us to have a "do the right thing" default and role-based hint, while also allowing a "if you insist, you can steamroll the HIG" option to avoid the interminable support queries. We can do our best to educate why you shouldn't use the literal switch/checkbox options in documentation, and we can raise warnings if you ask for a specific widget on a platform where that widget doesn't exist - maybe even raising an error, giving us an opportunity to point at docs that give a long form description of why forcing the style is a bad idea.


To summarise, this is now the desired end-state: