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"