Merge branch 'main' of github.com:Textualize/textual into import-sorting-hook

This commit is contained in:
Darren Burns
2023-02-09 14:28:23 +00:00
17 changed files with 288 additions and 286 deletions

View File

@@ -25,6 +25,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 - 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 - `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 ### Fixed

View File

@@ -1 +0,0 @@
::: textual.widgets.Checkbox

1
docs/api/switch.md Normal file
View File

@@ -0,0 +1 @@
::: textual.widgets.Switch

View File

@@ -7,7 +7,7 @@ Screen {
width: auto; width: auto;
} }
Checkbox { Switch {
height: auto; height: auto;
width: auto; width: auto;
} }
@@ -22,7 +22,7 @@ Checkbox {
background: darkslategrey; background: darkslategrey;
} }
#custom-design > .checkbox--switch { #custom-design > .switch--switch {
color: dodgerblue; color: dodgerblue;
background: darkslateblue; background: darkslateblue;
} }

View File

@@ -1,35 +1,35 @@
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Horizontal 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: def compose(self) -> ComposeResult:
yield Static("[b]Example checkboxes\n", classes="label") yield Static("[b]Example switches\n", classes="label")
yield Horizontal( yield Horizontal(
Static("off: ", classes="label"), Static("off: ", classes="label"),
Checkbox(animate=False), Switch(animate=False),
classes="container", classes="container",
) )
yield Horizontal( yield Horizontal(
Static("on: ", classes="label"), Static("on: ", classes="label"),
Checkbox(value=True), Switch(value=True),
classes="container", classes="container",
) )
focused_checkbox = Checkbox() focused_switch = Switch()
focused_checkbox.focus() focused_switch.focus()
yield Horizontal( yield Horizontal(
Static("focused: ", classes="label"), focused_checkbox, classes="container" Static("focused: ", classes="label"), focused_switch, classes="container"
) )
yield Horizontal( yield Horizontal(
Static("custom: ", classes="label"), Static("custom: ", classes="label"),
Checkbox(id="custom-design"), Switch(id="custom-design"),
classes="container", classes="container",
) )
app = CheckboxApp(css_path="checkbox.css") app = SwitchApp(css_path="switch.css")
if __name__ == "__main__": if __name__ == "__main__":
app.run() app.run()

View File

@@ -40,7 +40,7 @@ Widgets are key to making user-friendly interfaces. The builtin widgets should c
- [x] Buttons - [x] Buttons
* [x] Error / warning variants * [x] Error / warning variants
- [ ] Color picker - [ ] Color picker
- [x] Checkbox - [ ] Checkbox
- [ ] Content switcher - [ ] Content switcher
- [x] DataTable - [x] DataTable
* [x] Cell select * [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) * [ ] Style variants (solid, thin etc)
- [ ] Radio boxes - [ ] Radio boxes
- [ ] Spark-lines - [ ] Spark-lines
- [X] Switch
- [ ] Tabs - [ ] Tabs
- [ ] TextArea (multi-line input) - [ ] TextArea (multi-line input)
* [ ] Basic controls * [ ] Basic controls

View File

@@ -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

63
docs/widgets/switch.md Normal file
View File

@@ -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

View File

@@ -126,7 +126,6 @@ nav:
- "styles/width.md" - "styles/width.md"
- Widgets: - Widgets:
- "widgets/button.md" - "widgets/button.md"
- "widgets/checkbox.md"
- "widgets/data_table.md" - "widgets/data_table.md"
- "widgets/directory_tree.md" - "widgets/directory_tree.md"
- "widgets/footer.md" - "widgets/footer.md"
@@ -138,6 +137,7 @@ nav:
- "widgets/list_view.md" - "widgets/list_view.md"
- "widgets/placeholder.md" - "widgets/placeholder.md"
- "widgets/static.md" - "widgets/static.md"
- "widgets/switch.md"
- "widgets/text_log.md" - "widgets/text_log.md"
- "widgets/tree.md" - "widgets/tree.md"
- API: - API:
@@ -145,7 +145,6 @@ nav:
- "api/app.md" - "api/app.md"
- "api/binding.md" - "api/binding.md"
- "api/button.md" - "api/button.md"
- "api/checkbox.md"
- "api/color.md" - "api/color.md"
- "api/containers.md" - "api/containers.md"
- "api/coordinate.md" - "api/coordinate.md"
@@ -170,6 +169,7 @@ nav:
- "api/scroll_view.md" - "api/scroll_view.md"
- "api/static.md" - "api/static.md"
- "api/strip.md" - "api/strip.md"
- "api/switch.md"
- "api/text_log.md" - "api/text_log.md"
- "api/timer.md" - "api/timer.md"
- "api/tree.md" - "api/tree.md"

View File

@@ -119,7 +119,7 @@ DarkSwitch .label {
color: $text-muted; color: $text-muted;
} }
DarkSwitch Checkbox { DarkSwitch Switch {
background: $boost; background: $boost;
dock: left; dock: left;
} }

View File

@@ -18,12 +18,12 @@ from textual.containers import Container, Horizontal
from textual.reactive import reactive from textual.reactive import reactive
from textual.widgets import ( from textual.widgets import (
Button, Button,
Checkbox,
DataTable, DataTable,
Footer, Footer,
Header, Header,
Input, Input,
Static, Static,
Switch,
TextLog, TextLog,
) )
@@ -138,7 +138,7 @@ Build your own or use the builtin widgets.
- **Input** Text / Password input. - **Input** Text / Password input.
- **Button** Clickable button with a number of styles. - **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. - **DataTable** A spreadsheet-like widget for navigating data. Cells may contain text or Rich renderables.
- **Tree** An generic tree with expandable nodes. - **Tree** An generic tree with expandable nodes.
- **DirectoryTree** A tree of file and folders. - **DirectoryTree** A tree of file and folders.
@@ -199,16 +199,16 @@ class Title(Static):
class DarkSwitch(Horizontal): class DarkSwitch(Horizontal):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Checkbox(value=self.app.dark) yield Switch(value=self.app.dark)
yield Static("Dark mode toggle", classes="label") yield Static("Dark mode toggle", classes="label")
def on_mount(self) -> None: def on_mount(self) -> None:
self.watch(self.app, "dark", self.on_dark_change, init=False) self.watch(self.app, "dark", self.on_dark_change, init=False)
def on_dark_change(self, dark: bool) -> None: 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 self.app.dark = event.value

View File

@@ -11,7 +11,6 @@ from ..case import camel_to_snake
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from ..widget import Widget from ..widget import Widget
from ._button import Button from ._button import Button
from ._checkbox import Checkbox
from ._data_table import DataTable from ._data_table import DataTable
from ._directory_tree import DirectoryTree from ._directory_tree import DirectoryTree
from ._footer import Footer from ._footer import Footer
@@ -23,6 +22,7 @@ if typing.TYPE_CHECKING:
from ._placeholder import Placeholder from ._placeholder import Placeholder
from ._pretty import Pretty from ._pretty import Pretty
from ._static import Static from ._static import Static
from ._switch import Switch
from ._text_log import TextLog from ._text_log import TextLog
from ._tree import Tree from ._tree import Tree
from ._welcome import Welcome from ._welcome import Welcome
@@ -30,7 +30,7 @@ if typing.TYPE_CHECKING:
__all__ = [ __all__ = [
"Button", "Button",
"Checkbox", "Switch",
"DataTable", "DataTable",
"DirectoryTree", "DirectoryTree",
"Footer", "Footer",

View File

@@ -1,6 +1,5 @@
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list: # This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
from ._button import Button as Button from ._button import Button as Button
from ._checkbox import Checkbox as Checkbox
from ._data_table import DataTable as DataTable from ._data_table import DataTable as DataTable
from ._directory_tree import DirectoryTree as DirectoryTree from ._directory_tree import DirectoryTree as DirectoryTree
from ._footer import Footer as Footer from ._footer import Footer as Footer
@@ -12,6 +11,7 @@ from ._list_view import ListView as ListView
from ._placeholder import Placeholder as Placeholder from ._placeholder import Placeholder as Placeholder
from ._pretty import Pretty as Pretty from ._pretty import Pretty as Pretty
from ._static import Static as Static from ._static import Static as Static
from ._switch import Switch as Switch
from ._text_log import TextLog as TextLog from ._text_log import TextLog as TextLog
from ._tree import Tree as Tree from ._tree import Tree as Tree
from ._welcome import Welcome as Welcome from ._welcome import Welcome as Welcome

View File

@@ -12,12 +12,12 @@ from ..scrollbar import ScrollBarRender
from ..widget import Widget from ..widget import Widget
class Checkbox(Widget, can_focus=True): class Switch(Widget, can_focus=True):
"""A checkbox widget that represents a boolean value. """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. that enable more customization.
""" """
@@ -27,20 +27,20 @@ class Checkbox(Widget, can_focus=True):
""" """
| Key(s) | Description | | Key(s) | Description |
| :- | :- | | :- | :- |
| enter,space | Toggle the checkbox status. | | enter,space | Toggle the switch state. |
""" """
COMPONENT_CLASSES: ClassVar[set[str]] = { COMPONENT_CLASSES: ClassVar[set[str]] = {
"checkbox--switch", "switch--switch",
} }
""" """
| Class | Description | | Class | Description |
| :- | :- | | :- | :- |
| `checkbox--switch` | Targets the switch of the checkbox. | | `switch--switch` | Targets the switch of the switch. |
""" """
DEFAULT_CSS = """ DEFAULT_CSS = """
Checkbox { Switch {
border: tall transparent; border: tall transparent;
background: $panel; background: $panel;
height: auto; height: auto;
@@ -48,49 +48,49 @@ class Checkbox(Widget, can_focus=True):
padding: 0 2; padding: 0 2;
} }
Checkbox > .checkbox--switch { Switch > .switch--switch {
background: $panel-darken-2; background: $panel-darken-2;
color: $panel-lighten-2; color: $panel-lighten-2;
} }
Checkbox:hover { Switch:hover {
border: tall $background; border: tall $background;
} }
Checkbox:focus { Switch:focus {
border: tall $accent; border: tall $accent;
} }
Checkbox.-on { Switch.-on {
} }
Checkbox.-on > .checkbox--switch { Switch.-on > .switch--switch {
color: $success; color: $success;
} }
""" """
value = reactive(False, init=False) 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) slider_pos = reactive(0.0)
"""The position of the slider.""" """The position of the slider."""
class Changed(Message, bubble=True): 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. or in a parent widget in the DOM.
Attributes: Attributes:
value: The value that the checkbox was changed to. value: The value that the switch was changed to.
input: The `Checkbox` widget that was changed. 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) super().__init__(sender)
self.value: bool = value self.value: bool = value
self.input: Checkbox = sender self.input: Switch = sender
def __init__( def __init__(
self, self,
@@ -101,14 +101,14 @@ class Checkbox(Widget, can_focus=True):
id: str | None = None, id: str | None = None,
classes: str | None = None, classes: str | None = None,
): ):
"""Initialise the checkbox. """Initialise the switch.
Args: Args:
value: The initial value of the checkbox. Defaults to False. value: The initial value of the switch. Defaults to False.
animate: True if the checkbox should animate when toggled. Defaults to True. animate: True if the switch should animate when toggled. Defaults to True.
name: The name of the checkbox. name: The name of the switch.
id: The ID of the checkbox in the DOM. id: The ID of the switch in the DOM.
classes: The CSS classes of the checkbox. classes: The CSS classes of the switch.
""" """
super().__init__(name=name, id=id, classes=classes) super().__init__(name=name, id=id, classes=classes)
if value: if value:
@@ -128,7 +128,7 @@ class Checkbox(Widget, can_focus=True):
self.set_class(slider_pos == 1, "-on") self.set_class(slider_pos == 1, "-on")
def render(self) -> RenderableType: def render(self) -> RenderableType:
style = self.get_component_rich_style("checkbox--switch") style = self.get_component_rich_style("switch--switch")
return ScrollBarRender( return ScrollBarRender(
virtual_size=100, virtual_size=100,
window_size=50, window_size=50,
@@ -150,6 +150,6 @@ class Checkbox(Widget, can_focus=True):
self.toggle() self.toggle()
def toggle(self) -> None: def toggle(self) -> None:
"""Toggle the checkbox value. As a result of the value changing, """Toggle the switch value. As a result of the value changing,
a Checkbox.Changed message will be posted.""" a Switch.Changed message will be posted."""
self.value = not self.value self.value = not self.value

File diff suppressed because one or more lines are too long

View File

@@ -52,8 +52,8 @@ def test_dock_layout_sidebar(snap_compare):
# from these examples which test rendering and simple interactions with it. # from these examples which test rendering and simple interactions with it.
def test_checkboxes(snap_compare): def test_switches(snap_compare):
"""Tests checkboxes but also acts a regression test for using """Tests switches but also acts a regression test for using
width: auto in a Horizontal layout context.""" width: auto in a Horizontal layout context."""
press = [ press = [
"shift+tab", "shift+tab",
@@ -63,7 +63,7 @@ def test_checkboxes(snap_compare):
"enter", # toggle on "enter", # toggle on
"wait:20", "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): def test_input_and_focus(snap_compare):

View File

@@ -152,21 +152,21 @@ def test_focus_next_and_previous_with_type_selector_without_self():
screen = app.screen screen = app.screen
from textual.containers import Horizontal, Vertical from textual.containers import Horizontal, Vertical
from textual.widgets import Button, Checkbox, Input from textual.widgets import Button, Input, Switch
screen._add_children( screen._add_children(
Vertical( Vertical(
Horizontal( Horizontal(
Input(id="w3"), Input(id="w3"),
Checkbox(id="w4"), Switch(id="w4"),
Input(id="w5"), Input(id="w5"),
Button(id="w6"), Button(id="w6"),
Checkbox(id="w7"), Switch(id="w7"),
id="w2", id="w2",
), ),
Horizontal( Horizontal(
Button(id="w9"), Button(id="w9"),
Checkbox(id="w10"), Switch(id="w10"),
Button(id="w11"), Button(id="w11"),
Input(id="w12"), Input(id="w12"),
Input(id="w13"), 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.focused.id == "w3"
assert screen.focus_next(Button).id == "w6" 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_next(Input).id == "w12"
assert screen.focus_previous(Button).id == "w11" 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(Button).id == "w9"
assert screen.focus_previous(Input).id == "w5" assert screen.focus_previous(Input).id == "w5"