From decc1e2f3cd3cec6cfa8fd60878081413eabde3e Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Thu, 9 Feb 2023 11:10:30 +0000 Subject: [PATCH] Rename Checkbox to Switch A new form of Checkbox will be arriving in Textual soon, working in conjunction with a RadioButton. What was called Checkbox is perhaps a wee bit heavyweight in terms of visual design, but is a style of widget that should remain. With this in mind we're renaming the current Checkbox to Switch. In all other respects its workings remains the same, only the name has changed. Things for people to watch out for: - Imports will need to be updated. - Queries will need to be updated; special attention will need to be paid to any queries that are string-based. - CSS will need to be changed if any Checkbox styling is happening, or if any Checkbox component styles are being used. See #1725 as the initial motivation and #1746 as the issue for this particular change. --- CHANGELOG.md | 1 + docs/api/checkbox.md | 1 - docs/api/switch.md | 1 + .../widgets/{checkbox.css => switch.css} | 4 +- .../widgets/{checkbox.py => switch.py} | 20 +- docs/roadmap.md | 3 +- docs/widgets/checkbox.md | 63 ---- docs/widgets/switch.md | 63 ++++ mkdocs.yml | 4 +- src/textual/demo.css | 2 +- src/textual/demo.py | 10 +- src/textual/widgets/__init__.py | 4 +- src/textual/widgets/__init__.pyi | 2 +- .../widgets/{_checkbox.py => _switch.py} | 58 ++-- .../__snapshots__/test_snapshots.ambr | 320 +++++++++--------- tests/snapshot_tests/test_snapshots.py | 6 +- tests/test_focus.py | 12 +- 17 files changed, 288 insertions(+), 286 deletions(-) delete mode 100644 docs/api/checkbox.md create mode 100644 docs/api/switch.md rename docs/examples/widgets/{checkbox.css => switch.css} (86%) rename docs/examples/widgets/{checkbox.py => switch.py} (55%) delete mode 100644 docs/widgets/checkbox.md create mode 100644 docs/widgets/switch.md rename src/textual/widgets/{_checkbox.py => _switch.py} (65%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87edabf69..27c3fa5bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Breaking change: `TreeNode` can no longer be imported from `textual.widgets`; it is now available via `from textual.widgets.tree import TreeNode`. https://github.com/Textualize/textual/pull/1637 - `Tree` now shows a (subdued) cursor for a highlighted node when focus has moved elsewhere https://github.com/Textualize/textual/issues/1471 +- Breaking change: renamed `Checkbox` to `Switch`. ### Fixed diff --git a/docs/api/checkbox.md b/docs/api/checkbox.md deleted file mode 100644 index 6c9c434f2..000000000 --- a/docs/api/checkbox.md +++ /dev/null @@ -1 +0,0 @@ -::: textual.widgets.Checkbox diff --git a/docs/api/switch.md b/docs/api/switch.md new file mode 100644 index 000000000..711e817a0 --- /dev/null +++ b/docs/api/switch.md @@ -0,0 +1 @@ +::: textual.widgets.Switch diff --git a/docs/examples/widgets/checkbox.css b/docs/examples/widgets/switch.css similarity index 86% rename from docs/examples/widgets/checkbox.css rename to docs/examples/widgets/switch.css index 77c9fb368..fb6a0d220 100644 --- a/docs/examples/widgets/checkbox.css +++ b/docs/examples/widgets/switch.css @@ -7,7 +7,7 @@ Screen { width: auto; } -Checkbox { +Switch { height: auto; width: auto; } @@ -22,7 +22,7 @@ Checkbox { background: darkslategrey; } -#custom-design > .checkbox--switch { +#custom-design > .switch--switch { color: dodgerblue; background: darkslateblue; } diff --git a/docs/examples/widgets/checkbox.py b/docs/examples/widgets/switch.py similarity index 55% rename from docs/examples/widgets/checkbox.py rename to docs/examples/widgets/switch.py index 400f2ae25..54a59ad63 100644 --- a/docs/examples/widgets/checkbox.py +++ b/docs/examples/widgets/switch.py @@ -1,35 +1,35 @@ from textual.app import App, ComposeResult from textual.containers import Horizontal -from textual.widgets import Checkbox, Static +from textual.widgets import Switch, Static -class CheckboxApp(App): +class SwitchApp(App): def compose(self) -> ComposeResult: - yield Static("[b]Example checkboxes\n", classes="label") + yield Static("[b]Example switches\n", classes="label") yield Horizontal( Static("off: ", classes="label"), - Checkbox(animate=False), + Switch(animate=False), classes="container", ) yield Horizontal( Static("on: ", classes="label"), - Checkbox(value=True), + Switch(value=True), classes="container", ) - focused_checkbox = Checkbox() - focused_checkbox.focus() + focused_switch = Switch() + focused_switch.focus() yield Horizontal( - Static("focused: ", classes="label"), focused_checkbox, classes="container" + Static("focused: ", classes="label"), focused_switch, classes="container" ) yield Horizontal( Static("custom: ", classes="label"), - Checkbox(id="custom-design"), + Switch(id="custom-design"), classes="container", ) -app = CheckboxApp(css_path="checkbox.css") +app = SwitchApp(css_path="switch.css") if __name__ == "__main__": app.run() diff --git a/docs/roadmap.md b/docs/roadmap.md index 7589d5c33..f486260b8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -40,7 +40,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c - [x] Buttons * [x] Error / warning variants - [ ] Color picker -- [x] Checkbox +- [ ] Checkbox - [ ] Content switcher - [x] DataTable * [x] Cell select @@ -70,6 +70,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c * [ ] Style variants (solid, thin etc) - [ ] Radio boxes - [ ] Spark-lines +- [X] Switch - [ ] Tabs - [ ] TextArea (multi-line input) * [ ] Basic controls diff --git a/docs/widgets/checkbox.md b/docs/widgets/checkbox.md deleted file mode 100644 index a5a247ef9..000000000 --- a/docs/widgets/checkbox.md +++ /dev/null @@ -1,63 +0,0 @@ -# Checkbox - -A simple checkbox widget which stores a boolean value. - -- [x] Focusable -- [ ] Container - -## Example - -The example below shows checkboxes in various states. - -=== "Output" - - ```{.textual path="docs/examples/widgets/checkbox.py"} - ``` - -=== "checkbox.py" - - ```python - --8<-- "docs/examples/widgets/checkbox.py" - ``` - -=== "checkbox.css" - - ```sass - --8<-- "docs/examples/widgets/checkbox.css" - ``` - -## Reactive Attributes - -| Name | Type | Default | Description | -| ------- | ------ | ------- | ---------------------------------- | -| `value` | `bool` | `False` | The default value of the checkbox. | - -## Bindings - -The checkbox widget defines directly the following bindings: - -::: textual.widgets.Checkbox.BINDINGS - options: - show_root_heading: false - show_root_toc_entry: false - -## Component Classes - -The checkbox widget provides the following component classes: - -::: textual.widgets.Checkbox.COMPONENT_CLASSES - options: - show_root_heading: false - show_root_toc_entry: false - -## Messages - -### ::: textual.widgets.Checkbox.Changed - -## Additional Notes - -- To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`. - -## See Also - -- [Checkbox](../api/checkbox.md) code reference diff --git a/docs/widgets/switch.md b/docs/widgets/switch.md new file mode 100644 index 000000000..1cb77be6e --- /dev/null +++ b/docs/widgets/switch.md @@ -0,0 +1,63 @@ +# Switch + +A simple switch widget which stores a boolean value. + +- [x] Focusable +- [ ] Container + +## Example + +The example below shows switches in various states. + +=== "Output" + + ```{.textual path="docs/examples/widgets/switch.py"} + ``` + +=== "switch.py" + + ```python + --8<-- "docs/examples/widgets/switch.py" + ``` + +=== "switch.css" + + ```sass + --8<-- "docs/examples/widgets/switch.css" + ``` + +## Reactive Attributes + +| Name | Type | Default | Description | +|---------|--------|---------|----------------------------------| +| `value` | `bool` | `False` | The default value of the switch. | + +## Bindings + +The switch widget defines directly the following bindings: + +::: textual.widgets.Switch.BINDINGS + options: + show_root_heading: false + show_root_toc_entry: false + +## Component Classes + +The switch widget provides the following component classes: + +::: textual.widgets.Switch.COMPONENT_CLASSES + options: + show_root_heading: false + show_root_toc_entry: false + +## Messages + +### ::: textual.widgets.Switch.Changed + +## Additional Notes + +- To remove the spacing around a `Switch`, set `border: none;` and `padding: 0;`. + +## See Also + +- [Switch](../api/switch.md) code reference diff --git a/mkdocs.yml b/mkdocs.yml index ad2d90d47..863f1288d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -126,7 +126,6 @@ nav: - "styles/width.md" - Widgets: - "widgets/button.md" - - "widgets/checkbox.md" - "widgets/data_table.md" - "widgets/directory_tree.md" - "widgets/footer.md" @@ -138,6 +137,7 @@ nav: - "widgets/list_view.md" - "widgets/placeholder.md" - "widgets/static.md" + - "widgets/switch.md" - "widgets/text_log.md" - "widgets/tree.md" - API: @@ -145,7 +145,6 @@ nav: - "api/app.md" - "api/binding.md" - "api/button.md" - - "api/checkbox.md" - "api/color.md" - "api/containers.md" - "api/coordinate.md" @@ -170,6 +169,7 @@ nav: - "api/scroll_view.md" - "api/static.md" - "api/strip.md" + - "api/switch.md" - "api/text_log.md" - "api/timer.md" - "api/tree.md" diff --git a/src/textual/demo.css b/src/textual/demo.css index c93224e9f..3fb8c7d71 100644 --- a/src/textual/demo.css +++ b/src/textual/demo.css @@ -119,7 +119,7 @@ DarkSwitch .label { color: $text-muted; } -DarkSwitch Checkbox { +DarkSwitch Switch { background: $boost; dock: left; } diff --git a/src/textual/demo.py b/src/textual/demo.py index 907d93307..9f523ab4c 100644 --- a/src/textual/demo.py +++ b/src/textual/demo.py @@ -18,7 +18,7 @@ from textual.containers import Container, Horizontal from textual.reactive import reactive, watch from textual.widgets import ( Button, - Checkbox, + Switch, DataTable, Footer, Header, @@ -138,7 +138,7 @@ Build your own or use the builtin widgets. - **Input** Text / Password input. - **Button** Clickable button with a number of styles. -- **Checkbox** A checkbox to toggle between states. +- **Switch** A switch to toggle between states. - **DataTable** A spreadsheet-like widget for navigating data. Cells may contain text or Rich renderables. - **Tree** An generic tree with expandable nodes. - **DirectoryTree** A tree of file and folders. @@ -199,16 +199,16 @@ class Title(Static): class DarkSwitch(Horizontal): def compose(self) -> ComposeResult: - yield Checkbox(value=self.app.dark) + yield Switch(value=self.app.dark) yield Static("Dark mode toggle", classes="label") def on_mount(self) -> None: watch(self.app, "dark", self.on_dark_change, init=False) def on_dark_change(self, dark: bool) -> None: - self.query_one(Checkbox).value = self.app.dark + self.query_one(Switch).value = self.app.dark - def on_checkbox_changed(self, event: Checkbox.Changed) -> None: + def on_switch_changed(self, event: Switch.Changed) -> None: self.app.dark = event.value diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index 98dd81f18..e26881468 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -9,7 +9,7 @@ from ..case import camel_to_snake # be able to "see" them. if typing.TYPE_CHECKING: from ._button import Button - from ._checkbox import Checkbox + from ._switch import Switch from ._data_table import DataTable from ._directory_tree import DirectoryTree from ._footer import Footer @@ -29,7 +29,7 @@ if typing.TYPE_CHECKING: __all__ = [ "Button", - "Checkbox", + "Switch", "DataTable", "DirectoryTree", "Footer", diff --git a/src/textual/widgets/__init__.pyi b/src/textual/widgets/__init__.pyi index 82d25cd90..ad3b73fbb 100644 --- a/src/textual/widgets/__init__.pyi +++ b/src/textual/widgets/__init__.pyi @@ -1,7 +1,7 @@ # This stub file must re-export every classes exposed in the __init__.py's `__all__` list: from ._button import Button as Button from ._data_table import DataTable as DataTable -from ._checkbox import Checkbox as Checkbox +from ._switch import Switch as Switch from ._directory_tree import DirectoryTree as DirectoryTree from ._footer import Footer as Footer from ._header import Header as Header diff --git a/src/textual/widgets/_checkbox.py b/src/textual/widgets/_switch.py similarity index 65% rename from src/textual/widgets/_checkbox.py rename to src/textual/widgets/_switch.py index 9b5b1b454..cdc9f21a6 100644 --- a/src/textual/widgets/_checkbox.py +++ b/src/textual/widgets/_switch.py @@ -12,12 +12,12 @@ from ..widget import Widget from ..scrollbar import ScrollBarRender -class Checkbox(Widget, can_focus=True): - """A checkbox widget that represents a boolean value. +class Switch(Widget, can_focus=True): + """A switch widget that represents a boolean value. - Can be toggled by clicking on it or through its [bindings][textual.widgets.Checkbox.BINDINGS]. + Can be toggled by clicking on it or through its [bindings][textual.widgets.Switch.BINDINGS]. - The checkbox widget also contains [component classes][textual.widgets.Checkbox.COMPONENT_CLASSES] + The switch widget also contains [component classes][textual.widgets.Switch.COMPONENT_CLASSES] that enable more customization. """ @@ -27,20 +27,20 @@ class Checkbox(Widget, can_focus=True): """ | Key(s) | Description | | :- | :- | - | enter,space | Toggle the checkbox status. | + | enter,space | Toggle the switch state. | """ COMPONENT_CLASSES: ClassVar[set[str]] = { - "checkbox--switch", + "switch--switch", } """ | Class | Description | | :- | :- | - | `checkbox--switch` | Targets the switch of the checkbox. | + | `switch--switch` | Targets the switch of the switch. | """ DEFAULT_CSS = """ - Checkbox { + Switch { border: tall transparent; background: $panel; height: auto; @@ -48,49 +48,49 @@ class Checkbox(Widget, can_focus=True): padding: 0 2; } - Checkbox > .checkbox--switch { + Switch > .switch--switch { background: $panel-darken-2; color: $panel-lighten-2; } - Checkbox:hover { + Switch:hover { border: tall $background; } - Checkbox:focus { + Switch:focus { border: tall $accent; } - Checkbox.-on { + Switch.-on { } - Checkbox.-on > .checkbox--switch { + Switch.-on > .switch--switch { color: $success; } """ value = reactive(False, init=False) - """The value of the checkbox; `True` for on and `False` for off.""" + """The value of the switch; `True` for on and `False` for off.""" slider_pos = reactive(0.0) """The position of the slider.""" class Changed(Message, bubble=True): - """Posted when the status of the checkbox changes. + """Posted when the status of the switch changes. - Can be handled using `on_checkbox_changed` in a subclass of `Checkbox` + Can be handled using `on_switch_changed` in a subclass of `Switch` or in a parent widget in the DOM. Attributes: - value: The value that the checkbox was changed to. - input: The `Checkbox` widget that was changed. + value: The value that the switch was changed to. + input: The `Switch` widget that was changed. """ - def __init__(self, sender: Checkbox, value: bool) -> None: + def __init__(self, sender: Switch, value: bool) -> None: super().__init__(sender) self.value: bool = value - self.input: Checkbox = sender + self.input: Switch = sender def __init__( self, @@ -101,14 +101,14 @@ class Checkbox(Widget, can_focus=True): id: str | None = None, classes: str | None = None, ): - """Initialise the checkbox. + """Initialise the switch. Args: - value: The initial value of the checkbox. Defaults to False. - animate: True if the checkbox should animate when toggled. Defaults to True. - name: The name of the checkbox. - id: The ID of the checkbox in the DOM. - classes: The CSS classes of the checkbox. + value: The initial value of the switch. Defaults to False. + animate: True if the switch should animate when toggled. Defaults to True. + name: The name of the switch. + id: The ID of the switch in the DOM. + classes: The CSS classes of the switch. """ super().__init__(name=name, id=id, classes=classes) if value: @@ -128,7 +128,7 @@ class Checkbox(Widget, can_focus=True): self.set_class(slider_pos == 1, "-on") def render(self) -> RenderableType: - style = self.get_component_rich_style("checkbox--switch") + style = self.get_component_rich_style("switch--switch") return ScrollBarRender( virtual_size=100, window_size=50, @@ -150,6 +150,6 @@ class Checkbox(Widget, can_focus=True): self.toggle() def toggle(self) -> None: - """Toggle the checkbox value. As a result of the value changing, - a Checkbox.Changed message will be posted.""" + """Toggle the switch value. As a result of the value changing, + a Switch.Changed message will be posted.""" self.value = not self.value diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 5a8c647bf..0064c4c32 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -344,166 +344,6 @@ ''' # --- -# name: test_checkboxes - ''' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CheckboxApp - - - - - - - - - - - - - - Example checkboxes - - - ▔▔▔▔▔▔▔▔ - off:      - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - on:       - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - focused:  - ▁▁▁▁▁▁▁▁ - ▔▔▔▔▔▔▔▔ - custom:   - ▁▁▁▁▁▁▁▁ - - - - - - - - - - ''' -# --- # name: test_columns_height ''' @@ -14124,6 +13964,166 @@ ''' # --- +# name: test_switches + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SwitchApp + + + + + + + + + + + + + + Example switches + + + ▔▔▔▔▔▔▔▔ + off:      + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + on:       + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + focused:  + ▁▁▁▁▁▁▁▁ + ▔▔▔▔▔▔▔▔ + custom:   + ▁▁▁▁▁▁▁▁ + + + + + + + + + + ''' +# --- # name: test_textlog_max_lines ''' diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 9ce48d171..cdc2d0552 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -52,8 +52,8 @@ def test_dock_layout_sidebar(snap_compare): # from these examples which test rendering and simple interactions with it. -def test_checkboxes(snap_compare): - """Tests checkboxes but also acts a regression test for using +def test_switches(snap_compare): + """Tests switches but also acts a regression test for using width: auto in a Horizontal layout context.""" press = [ "shift+tab", @@ -63,7 +63,7 @@ def test_checkboxes(snap_compare): "enter", # toggle on "wait:20", ] - assert snap_compare(WIDGET_EXAMPLES_DIR / "checkbox.py", press=press) + assert snap_compare(WIDGET_EXAMPLES_DIR / "switch.py", press=press) def test_input_and_focus(snap_compare): diff --git a/tests/test_focus.py b/tests/test_focus.py index e11b14a8d..cee50c6da 100644 --- a/tests/test_focus.py +++ b/tests/test_focus.py @@ -152,21 +152,21 @@ def test_focus_next_and_previous_with_type_selector_without_self(): screen = app.screen from textual.containers import Horizontal, Vertical - from textual.widgets import Button, Checkbox, Input + from textual.widgets import Button, Switch, Input screen._add_children( Vertical( Horizontal( Input(id="w3"), - Checkbox(id="w4"), + Switch(id="w4"), Input(id="w5"), Button(id="w6"), - Checkbox(id="w7"), + Switch(id="w7"), id="w2", ), Horizontal( Button(id="w9"), - Checkbox(id="w10"), + Switch(id="w10"), Button(id="w11"), Input(id="w12"), Input(id="w13"), @@ -180,11 +180,11 @@ def test_focus_next_and_previous_with_type_selector_without_self(): assert screen.focused.id == "w3" assert screen.focus_next(Button).id == "w6" - assert screen.focus_next(Checkbox).id == "w7" + assert screen.focus_next(Switch).id == "w7" assert screen.focus_next(Input).id == "w12" assert screen.focus_previous(Button).id == "w11" - assert screen.focus_previous(Checkbox).id == "w10" + assert screen.focus_previous(Switch).id == "w10" assert screen.focus_previous(Button).id == "w9" assert screen.focus_previous(Input).id == "w5"