diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87edabf69..c26553d34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,11 +19,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added Shift+scroll wheel and ctrl+scroll wheel to scroll horizontally
- Added `Tree.action_toggle_node` to toggle a node without selecting, and bound it to Space https://github.com/Textualize/textual/issues/1433
- Added `Tree.reset` to fully reset a `Tree` https://github.com/Textualize/textual/issues/1437
+- Added DOMNode.watch and DOMNode.is_attached methods https://github.com/Textualize/textual/pull/1750
### Changed
- 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
@@ -42,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Removed
- Methods `MessagePump.emit` and `MessagePump.emit_no_wait` https://github.com/Textualize/textual/pull/1738
+- Removed `reactive.watch` in favor of DOMNode.watch.
## [0.10.1] - 2023-01-20
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/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md
index fab89158c..076cb96b5 100644
--- a/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md
+++ b/docs/blog/posts/on-dog-food-the-original-metaverse-and-not-being-bored.md
@@ -288,7 +288,7 @@ So, thanks to this bit of code in my `Activity` widget...
parent.move_child(
self, before=parent.children.index( self ) - 1
)
- self.post_message_no_wait( self.Moved( self ) )
+ self.emit_no_wait( self.Moved( self ) )
self.scroll_visible( top=True )
```
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-nav.yml b/mkdocs-nav.yml
index e453e9276..6b5c4508b 100644
--- a/mkdocs-nav.yml
+++ b/mkdocs-nav.yml
@@ -121,7 +121,6 @@ nav:
- "styles/width.md"
- Widgets:
- "widgets/button.md"
- - "widgets/checkbox.md"
- "widgets/data_table.md"
- "widgets/directory_tree.md"
- "widgets/footer.md"
@@ -133,6 +132,7 @@ nav:
- "widgets/list_view.md"
- "widgets/placeholder.md"
- "widgets/static.md"
+ - "widgets/switch.md"
- "widgets/text_log.md"
- "widgets/tree.md"
- API:
@@ -140,7 +140,6 @@ nav:
- "api/app.md"
- "api/binding.md"
- "api/button.md"
- - "api/checkbox.md"
- "api/color.md"
- "api/containers.md"
- "api/coordinate.md"
@@ -165,6 +164,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/cli/previews/easing.py b/src/textual/cli/previews/easing.py
index b81290302..53b7c4475 100644
--- a/src/textual/cli/previews/easing.py
+++ b/src/textual/cli/previews/easing.py
@@ -5,7 +5,7 @@ from textual._easing import EASING
from textual.app import App, ComposeResult
from textual.cli.previews.borders import TEXT
from textual.containers import Container, Horizontal, Vertical
-from textual.reactive import Reactive
+from textual.reactive import reactive, var
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
from textual.widgets import Button, Footer, Label, Input
@@ -23,8 +23,8 @@ class EasingButtons(Widget):
class Bar(Widget):
- position = Reactive.init(START_POSITION)
- animation_running = Reactive(False)
+ position = reactive(START_POSITION)
+ animation_running = reactive(False)
DEFAULT_CSS = """
@@ -53,8 +53,8 @@ class Bar(Widget):
class EasingApp(App):
- position = Reactive.init(START_POSITION)
- duration = Reactive.var(1.0)
+ position = reactive(START_POSITION)
+ duration = var(1.0)
def on_load(self):
self.bind(
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..a470cf72b 100644
--- a/src/textual/demo.py
+++ b/src/textual/demo.py
@@ -15,15 +15,15 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import Container, Horizontal
-from textual.reactive import reactive, watch
+from textual.reactive import reactive
from textual.widgets import (
Button,
- Checkbox,
DataTable,
Footer,
Header,
Input,
Static,
+ Switch,
TextLog,
)
@@ -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)
+ self.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/dom.py b/src/textual/dom.py
index b6129daed..07f994958 100644
--- a/src/textual/dom.py
+++ b/src/textual/dom.py
@@ -22,6 +22,7 @@ from rich.tree import Tree
from ._context import NoActiveAppError
from ._node_list import NodeList
+from ._types import CallbackType
from .binding import Bindings, BindingType
from .color import BLACK, WHITE, Color
from .css._error_tools import friendly_list
@@ -31,7 +32,7 @@ from .css.parse import parse_declarations
from .css.styles import RenderStyles, Styles
from .css.tokenize import IDENTIFIER
from .message_pump import MessagePump
-from .reactive import Reactive
+from .reactive import Reactive, _watch
from .timer import Timer
from .walk import walk_breadth_first, walk_depth_first
@@ -210,6 +211,10 @@ class DOMNode(MessagePump):
styles = self._component_styles[name]
return styles
+ def _post_mount(self):
+ """Called after the object has been mounted."""
+ Reactive._initialize_object(self)
+
@property
def _node_bases(self) -> Iterator[Type[DOMNode]]:
"""Iterator[Type[DOMNode]]: The DOMNode bases classes (including self.__class__)"""
@@ -643,6 +648,23 @@ class DOMNode(MessagePump):
"""
return [child for child in self.children if child.display]
+ def watch(
+ self,
+ obj: DOMNode,
+ attribute_name: str,
+ callback: CallbackType,
+ init: bool = True,
+ ) -> None:
+ """Watches for modifications to reactive attributes on another object.
+
+ Args:
+ obj: Object containing attribute to watch.
+ attribute_name: Attribute to watch.
+ callback: A callback to run when attribute changes.
+ init: Check watchers on first call.
+ """
+ _watch(self, obj, attribute_name, callback, init=init)
+
def get_pseudo_classes(self) -> Iterable[str]:
"""Get any pseudo classes applicable to this Node, e.g. hover, focus.
diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py
index 4058139ce..d17bd8249 100644
--- a/src/textual/message_pump.py
+++ b/src/textual/message_pump.py
@@ -123,6 +123,19 @@ class MessagePump(metaclass=MessagePumpMeta):
"""
return self.app._logger
+ @property
+ def is_attached(self) -> bool:
+ """Is the node is attached to the app via the DOM."""
+ from .app import App
+
+ node = self
+
+ while not isinstance(node, App):
+ if node._parent is None:
+ return False
+ node = node._parent
+ return True
+
def _attach(self, parent: MessagePump) -> None:
"""Set the parent, and therefore attach this node to the tree.
@@ -358,7 +371,10 @@ class MessagePump(metaclass=MessagePumpMeta):
finally:
# This is critical, mount may be waiting
self._mounted_event.set()
- Reactive._initialize_object(self)
+ self._post_mount()
+
+ def _post_mount(self):
+ """Called after the object has been mounted."""
async def _process_messages_loop(self) -> None:
"""Process messages until the queue is closed."""
diff --git a/src/textual/pilot.py b/src/textual/pilot.py
index 2051c3098..b68f9409c 100644
--- a/src/textual/pilot.py
+++ b/src/textual/pilot.py
@@ -53,6 +53,7 @@ class Pilot(Generic[ReturnType]):
async def wait_for_scheduled_animations(self) -> None:
"""Wait for any current and scheduled animations to complete."""
await self._app.animator.wait_until_complete()
+ await wait_for_idle(0)
async def exit(self, result: ReturnType) -> None:
"""Exit the app with the given result.
diff --git a/src/textual/reactive.py b/src/textual/reactive.py
index 93139501d..58f9a0837 100644
--- a/src/textual/reactive.py
+++ b/src/textual/reactive.py
@@ -7,36 +7,26 @@ from typing import (
Any,
Awaitable,
Callable,
+ ClassVar,
Generic,
Type,
TypeVar,
- Union,
)
import rich.repr
from . import events
-from ._callback import count_parameters, invoke
-from ._types import MessageTarget
+from ._callback import count_parameters
+from ._types import MessageTarget, CallbackType
if TYPE_CHECKING:
- from .app import App
- from .widget import Widget
+ from .dom import DOMNode
- Reactable = Union[Widget, App]
+ Reactable = DOMNode
ReactiveType = TypeVar("ReactiveType")
-class _NotSet:
- pass
-
-
-_NOT_SET = _NotSet()
-
-T = TypeVar("T")
-
-
@rich.repr.auto
class Reactive(Generic[ReactiveType]):
"""Reactive descriptor.
@@ -50,7 +40,7 @@ class Reactive(Generic[ReactiveType]):
compute: Run compute methods when attribute is changed. Defaults to True.
"""
- _reactives: TypeVar[dict[str, object]] = {}
+ _reactives: ClassVar[dict[str, object]] = {}
def __init__(
self,
@@ -77,37 +67,6 @@ class Reactive(Generic[ReactiveType]):
yield "always_update", self._always_update
yield "compute", self._run_compute
- @classmethod
- def init(
- cls,
- default: ReactiveType | Callable[[], ReactiveType],
- *,
- layout: bool = False,
- repaint: bool = True,
- always_update: bool = False,
- compute: bool = True,
- ) -> Reactive:
- """A reactive variable that calls watchers and compute on initialize (post mount).
-
- Args:
- default: A default value or callable that returns a default.
- layout: Perform a layout on change. Defaults to False.
- repaint: Perform a repaint on change. Defaults to True.
- always_update: Call watchers even when the new value equals the old value. Defaults to False.
- compute: Run compute methods when attribute is changed. Defaults to True.
-
- Returns:
- A Reactive instance which calls watchers or initialize.
- """
- return cls(
- default,
- layout=layout,
- repaint=repaint,
- init=True,
- always_update=always_update,
- compute=compute,
- )
-
@classmethod
def var(
cls,
@@ -254,7 +213,7 @@ class Reactive(Generic[ReactiveType]):
def invoke_watcher(
watch_function: Callable, old_value: object, value: object
- ) -> bool:
+ ) -> None:
"""Invoke a watch function.
Args:
@@ -262,8 +221,6 @@ class Reactive(Generic[ReactiveType]):
old_value: The old value of the attribute.
value: The new value of the attribute.
- Returns:
- True if the watcher was run, or False if it was posted.
"""
_rich_traceback_omit = True
param_count = count_parameters(watch_function)
@@ -280,17 +237,23 @@ class Reactive(Generic[ReactiveType]):
sender=obj, callback=partial(await_watcher, watch_result)
)
)
- return False
- else:
- return True
watch_function = getattr(obj, f"watch_{name}", None)
if callable(watch_function):
invoke_watcher(watch_function, old_value, value)
- watchers: list[Callable] = getattr(obj, "__watchers", {}).get(name, [])
- for watcher in watchers:
- invoke_watcher(watcher, old_value, value)
+ # Process "global" watchers
+ watchers: list[tuple[Reactable, Callable]]
+ watchers = getattr(obj, "__watchers", {}).get(name, [])
+ # Remove any watchers for reactables that have since closed
+ if watchers:
+ watchers[:] = [
+ (reactable, callback)
+ for reactable, callback in watchers
+ if reactable.is_attached and not reactable._closing
+ ]
+ for _, callback in watchers:
+ invoke_watcher(callback, old_value, value)
@classmethod
def _compute(cls, obj: Reactable) -> None:
@@ -362,10 +325,12 @@ class var(Reactive[ReactiveType]):
)
-def watch(
+def _watch(
+ node: DOMNode,
obj: Reactable,
attribute_name: str,
- callback: Callable[[Any], object],
+ callback: CallbackType,
+ *,
init: bool = True,
) -> None:
"""Watch a reactive variable on an object.
@@ -379,11 +344,11 @@ def watch(
if not hasattr(obj, "__watchers"):
setattr(obj, "__watchers", {})
- watchers: dict[str, list[Callable]] = getattr(obj, "__watchers")
+ watchers: dict[str, list[tuple[Reactable, Callable]]] = getattr(obj, "__watchers")
watcher_list = watchers.setdefault(attribute_name, [])
if callback in watcher_list:
return
- watcher_list.append(callback)
+ watcher_list.append((node, callback))
if init:
current_value = getattr(obj, attribute_name, None)
Reactive._check_watchers(obj, attribute_name, current_value)
diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py
index 98dd81f18..355641bee 100644
--- a/src/textual/widgets/__init__.py
+++ b/src/textual/widgets/__init__.py
@@ -9,7 +9,6 @@ from ..case import camel_to_snake
# be able to "see" them.
if typing.TYPE_CHECKING:
from ._button import Button
- from ._checkbox import Checkbox
from ._data_table import DataTable
from ._directory_tree import DirectoryTree
from ._footer import Footer
@@ -21,6 +20,7 @@ if typing.TYPE_CHECKING:
from ._placeholder import Placeholder
from ._pretty import Pretty
from ._static import Static
+ from ._switch import Switch
from ._text_log import TextLog
from ._tree import Tree
from ._welcome import Welcome
@@ -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..7681c32f0 100644
--- a/src/textual/widgets/__init__.pyi
+++ b/src/textual/widgets/__init__.pyi
@@ -1,18 +1,17 @@
# 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 ._directory_tree import DirectoryTree as DirectoryTree
from ._footer import Footer as Footer
from ._header import Header as Header
+from ._input import Input as Input
from ._label import Label as Label
from ._list_view import ListView as ListView
from ._list_item import ListItem as ListItem
from ._placeholder import Placeholder as Placeholder
from ._pretty import Pretty as Pretty
from ._static import Static as Static
-from ._input import Input as Input
+from ._switch import Switch as Switch
from ._text_log import TextLog as TextLog
from ._tree import Tree as Tree
-from ._tree_node import TreeNode as TreeNode
from ._welcome import Welcome as Welcome
diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py
index ade5a1fd8..eaa0b874b 100644
--- a/src/textual/widgets/_button.py
+++ b/src/textual/widgets/_button.py
@@ -11,7 +11,7 @@ from rich.text import Text, TextType
from .. import events
from ..css._error_tools import friendly_list
from ..message import Message
-from ..reactive import Reactive
+from ..reactive import reactive
from ..widgets import Static
@@ -151,13 +151,13 @@ class Button(Static, can_focus=True):
ACTIVE_EFFECT_DURATION = 0.3
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
- label: Reactive[RenderableType] = Reactive("")
+ label: reactive[RenderableType] = reactive("")
"""The text label that appears within the button."""
- variant = Reactive.init("default")
+ variant = reactive("default")
"""The variant name for the button."""
- disabled = Reactive(False)
+ disabled = reactive(False)
"""The disabled state of the button; `True` if disabled, `False` if not."""
class Pressed(Message, bubble=True):
diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py
index 79b8e4485..0a641d870 100644
--- a/src/textual/widgets/_data_table.py
+++ b/src/textual/widgets/_data_table.py
@@ -1042,7 +1042,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
self._set_hover_cursor(True)
if self.show_cursor and self.cursor_type != "none":
# Only post selection events if there is a visible row/col/cell cursor.
- self._post_message_selected_message()
+ self._post_selected_message()
meta = self.get_style_at(event.x, event.y).meta
if meta:
self.cursor_cell = Coordinate(meta["row"], meta["column"])
diff --git a/src/textual/widgets/_footer.py b/src/textual/widgets/_footer.py
index 0b1c9a808..00ccf9e1f 100644
--- a/src/textual/widgets/_footer.py
+++ b/src/textual/widgets/_footer.py
@@ -8,7 +8,7 @@ from rich.console import RenderableType
from rich.text import Text
from .. import events
-from ..reactive import Reactive, watch
+from ..reactive import Reactive
from ..widget import Widget
@@ -66,7 +66,7 @@ class Footer(Widget):
self.refresh()
def on_mount(self) -> None:
- watch(self.screen, "focused", self._focus_changed)
+ self.watch(self.screen, "focused", self._focus_changed)
def _focus_changed(self, focused: Widget | None) -> None:
self._key_text = None
diff --git a/src/textual/widgets/_header.py b/src/textual/widgets/_header.py
index b2df426cc..d09c4dad8 100644
--- a/src/textual/widgets/_header.py
+++ b/src/textual/widgets/_header.py
@@ -5,7 +5,7 @@ from datetime import datetime
from rich.text import Text
from ..widget import Widget
-from ..reactive import Reactive, watch
+from ..reactive import Reactive
class HeaderIcon(Widget):
@@ -133,5 +133,5 @@ class Header(Widget):
def set_sub_title(sub_title: str) -> None:
self.query_one(HeaderTitle).sub_text = sub_title
- watch(self.app, "title", set_title)
- watch(self.app, "sub_title", set_sub_title)
+ self.watch(self.app, "title", set_title)
+ self.watch(self.app, "sub_title", set_sub_title)
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..a9f31e2aa 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -344,166 +344,6 @@
'''
# ---
-# name: test_checkboxes
- '''
-
-
- '''
-# ---
# name: test_columns_height
'''