mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' into datatable-cell-keys
This commit is contained in:
@@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Fixed double-paste into `Input` https://github.com/Textualize/textual/issues/1657
|
||||
- Added a workaround for an apparent Windows Terminal paste issue https://github.com/Textualize/textual/issues/1661
|
||||
- Fixes issue with renderable width calculation https://github.com/Textualize/textual/issues/1685
|
||||
- Fixed issue with app not processing Paste event https://github.com/Textualize/textual/issues/1666
|
||||
|
||||
## [0.10.1] - 2023-01-20
|
||||
|
||||
|
||||
67
docs/_templates/python/material/attribute.html
vendored
Normal file
67
docs/_templates/python/material/attribute.html
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
{{ log.debug("Rendering " + attribute.path) }}
|
||||
|
||||
<div class="doc doc-object doc-attribute">
|
||||
{% with html_id = attribute.path %}
|
||||
|
||||
{% if root %}
|
||||
{% set show_full_path = config.show_root_full_path %}
|
||||
{% set root_members = True %}
|
||||
{% elif root_members %}
|
||||
{% set show_full_path = config.show_root_members_full_path or config.show_object_full_path %}
|
||||
{% set root_members = False %}
|
||||
{% else %}
|
||||
{% set show_full_path = config.show_object_full_path %}
|
||||
{% endif %}
|
||||
|
||||
{% if not root or config.show_root_heading %}
|
||||
|
||||
{% filter heading(heading_level,
|
||||
role="data" if attribute.parent.kind.value == "module" else "attr",
|
||||
id=html_id,
|
||||
class="doc doc-heading",
|
||||
toc_label=attribute.name) %}
|
||||
|
||||
{% if config.separate_signature %}
|
||||
<span class="doc doc-object-name doc-attribute-name">{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}</span>
|
||||
{% else %}
|
||||
{% filter highlight(language="python", inline=True) %}
|
||||
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
|
||||
{% if attribute.annotation %}: {{ attribute.annotation }}{% endif %}
|
||||
{% endfilter %}
|
||||
{% endif %}
|
||||
|
||||
{% with labels = attribute.labels %}
|
||||
{% include "labels.html" with context %}
|
||||
{% endwith %}
|
||||
|
||||
{% endfilter %}
|
||||
|
||||
{% if config.separate_signature %}
|
||||
{% filter highlight(language="python", inline=False) %}
|
||||
{% filter format_code(config.line_length) %}
|
||||
{% if show_full_path %}{{ attribute.path }}{% else %}{{ attribute.name }}{% endif %}
|
||||
{% if attribute.annotation %}: {{ attribute.annotation|safe }}{% endif %}
|
||||
{% endfilter %}
|
||||
{% endfilter %}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
{% if config.show_root_toc_entry %}
|
||||
{% filter heading(heading_level,
|
||||
role="data" if attribute.parent.kind.value == "module" else "attr",
|
||||
id=html_id,
|
||||
toc_label=attribute.path if config.show_root_full_path else attribute.name,
|
||||
hidden=True) %}
|
||||
{% endfilter %}
|
||||
{% endif %}
|
||||
{% set heading_level = heading_level - 1 %}
|
||||
{% endif %}
|
||||
|
||||
<div class="doc doc-contents {% if root %}first{% endif %}">
|
||||
{% with docstring_sections = attribute.docstring.parsed %}
|
||||
{% include "docstring.html" with context %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
</div>
|
||||
64
docs/widgets/_template.md
Normal file
64
docs/widgets/_template.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Widget
|
||||
|
||||
Widget description.
|
||||
|
||||
- [ ] Focusable
|
||||
- [ ] Container
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Example app showing the widget:
|
||||
|
||||
=== "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
|
||||
|
||||
|
||||
## Bindings
|
||||
|
||||
The WIDGET widget defines directly the following bindings:
|
||||
|
||||
::: textual.widgets.WIDGET.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
|
||||
## Component classes
|
||||
|
||||
The WIDGET widget provides the following component classes:
|
||||
|
||||
::: textual.widget.WIDGET.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
|
||||
## Additional notes
|
||||
|
||||
- Did you know this?
|
||||
- Another pro tip.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
- [WIDGET](../api/WIDGET.md) code reference.
|
||||
- Another related API.
|
||||
- Something else useful.
|
||||
@@ -39,15 +39,7 @@ Clicking any of the non-disabled buttons in the example app below will result th
|
||||
|
||||
## Messages
|
||||
|
||||
### Pressed
|
||||
|
||||
The `Button.Pressed` message is sent when the button is pressed.
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
_No other attributes_
|
||||
### ::: textual.widgets.Button.Pressed
|
||||
|
||||
## Additional Notes
|
||||
|
||||
|
||||
@@ -32,25 +32,31 @@ The example below shows checkboxes in various states.
|
||||
| ------- | ------ | ------- | ---------------------------------- |
|
||||
| `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
|
||||
|
||||
### Pressed
|
||||
|
||||
The `Checkbox.Changed` message is sent when the checkbox is toggled.
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ------ | ------------------------------ |
|
||||
| `value` | `bool` | The new value of the checkbox. |
|
||||
### ::: textual.widgets.Checkbox.Changed
|
||||
|
||||
## Additional Notes
|
||||
|
||||
- To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`.
|
||||
- The `.checkbox--switch` component class can be used to change the color and background of the switch.
|
||||
- When focused, the ++enter++ or ++space++ keys can be used to toggle the checkbox.
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -48,6 +48,24 @@ The example below populates a table with CSV data.
|
||||
|
||||
### ::: textual.widgets.DataTable.ColumnSelected
|
||||
|
||||
## Bindings
|
||||
|
||||
The data table widget defines directly the following bindings:
|
||||
|
||||
::: textual.widgets.DataTable.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## Component Classes
|
||||
|
||||
The data table widget provides the following component classes:
|
||||
|
||||
::: textual.widgets.DataTable.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## See Also
|
||||
|
||||
* [DataTable][textual.widgets.DataTable] code reference
|
||||
|
||||
@@ -16,17 +16,7 @@ The example below creates a simple tree to navigate the current working director
|
||||
|
||||
## Messages
|
||||
|
||||
### FileSelected
|
||||
|
||||
The `DirectoryTree.FileSelected` message is sent when the user selects a file in the tree
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----- | ----------------- |
|
||||
| `path` | `str` | Path of the file. |
|
||||
### ::: textual.widgets.DirectoryTree.FileSelected
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
@@ -36,6 +26,14 @@ The `DirectoryTree.FileSelected` message is sent when the user selects a file in
|
||||
| `show_guides` | `bool` | `True` | Show guide lines between levels. |
|
||||
| `guide_depth` | `int` | `4` | Amount of indentation between parent and child. |
|
||||
|
||||
## Component Classes
|
||||
|
||||
The directory tree widget provides the following component classes:
|
||||
|
||||
::: textual.widgets.DirectoryTree.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -32,6 +32,15 @@ widget. Notice how the `Footer` automatically displays the keybinding.
|
||||
|
||||
This widget sends no messages.
|
||||
|
||||
## Component Classes
|
||||
|
||||
The footer widget provides the following component classes:
|
||||
|
||||
::: textual.widgets.Footer.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## Additional Notes
|
||||
|
||||
* You can prevent keybindings from appearing in the footer by setting the `show` argument of the `Binding` to `False`.
|
||||
|
||||
@@ -32,31 +32,27 @@ The example below shows how you might create a simple form using two `Input` wid
|
||||
|
||||
## Messages
|
||||
|
||||
### Changed
|
||||
### ::: textual.widgets.Input.Changed
|
||||
|
||||
The `Input.Changed` message is sent when the value in the text input changes.
|
||||
### ::: textual.widgets.Input.Submitted
|
||||
|
||||
- [x] Bubbles
|
||||
## Bindings
|
||||
|
||||
#### Attributes
|
||||
The input widget defines directly the following bindings:
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----- | -------------------------------- |
|
||||
| `value` | `str` | The new value in the text input. |
|
||||
::: textual.widgets.Input.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## Component Classes
|
||||
|
||||
### Submitted
|
||||
|
||||
The `Input.Submitted` message is sent when you press ++enter++ with the text field submitted.
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----- | -------------------------------- |
|
||||
| `value` | `str` | The new value in the text input. |
|
||||
The input widget provides the following component classes:
|
||||
|
||||
::: textual.widgets.Input.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## Additional Notes
|
||||
|
||||
|
||||
@@ -27,13 +27,6 @@ of multiple `ListItem`s. The arrow keys can be used to navigate the list.
|
||||
|---------------|--------|---------|--------------------------------------|
|
||||
| `highlighted` | `bool` | `False` | True if this ListItem is highlighted |
|
||||
|
||||
## Messages
|
||||
|
||||
### Selected
|
||||
|
||||
The `ListItem.Selected` message is sent when the item is selected.
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
|
||||
@@ -35,34 +35,18 @@ The example below shows an app with a simple `ListView`.
|
||||
|
||||
## Messages
|
||||
|
||||
### Highlighted
|
||||
### ::: textual.widgets.ListView.Highlighted
|
||||
|
||||
The `ListView.Highlighted` message is emitted when the highlight changes.
|
||||
This happens when you use the arrow keys on your keyboard and when you
|
||||
click on a list item.
|
||||
### ::: textual.widgets.ListView.Selected
|
||||
|
||||
- [x] Bubbles
|
||||
## Bindings
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ---------- | ------------------------------ |
|
||||
| `item` | `ListItem` | The item that was highlighted. |
|
||||
|
||||
### Selected
|
||||
|
||||
The `ListView.Selected` message is emitted when a list item is selected.
|
||||
You can select a list item by pressing ++enter++ while it is highlighted,
|
||||
or by clicking on it.
|
||||
|
||||
- [x] Bubbles
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ---------- | --------------------------- |
|
||||
| `item` | `ListItem` | The item that was selected. |
|
||||
The list view widget defines directly the following bindings:
|
||||
|
||||
::: textual.widgets.ListView.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -32,47 +32,33 @@ Tree widgets have a "root" attribute which is an instance of a [TreeNode][textua
|
||||
| `show_guides` | `bool` | `True` | Show guide lines between levels. |
|
||||
| `guide_depth` | `int` | `4` | Amount of indentation between parent and child. |
|
||||
|
||||
|
||||
|
||||
## Messages
|
||||
|
||||
### NodeSelected
|
||||
### ::: textual.widgets.Tree.NodeCollapsed
|
||||
|
||||
The `Tree.NodeSelected` message is sent when the user selects a tree node.
|
||||
### ::: textual.widgets.Tree.NodeExpanded
|
||||
|
||||
### ::: textual.widgets.Tree.NodeHighlighted
|
||||
|
||||
#### Attributes
|
||||
### ::: textual.widgets.Tree.NodeSelected
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----------------------------------------- | -------------- |
|
||||
| `node` | [TreeNode][textual.widgets.tree.TreeNode] | Selected node. |
|
||||
## Bindings
|
||||
|
||||
The tree widget defines directly the following bindings:
|
||||
|
||||
### NodeExpanded
|
||||
|
||||
The `Tree.NodeExpanded` message is sent when the user expands a node in the tree.
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----------------------------------------- | -------------- |
|
||||
| `node` | [TreeNode][textual.widgets.tree.TreeNode] | Expanded node. |
|
||||
|
||||
|
||||
### NodeCollapsed
|
||||
|
||||
|
||||
The `Tree.NodeCollapsed` message is sent when the user expands a node in the tree.
|
||||
|
||||
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
| --------- | ----------------------------------------- | --------------- |
|
||||
| `node` | [TreeNode][textual.widgets.tree.TreeNode] | Collapsed node. |
|
||||
::: textual.widgets.Tree.BINDINGS
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## Component Classes
|
||||
|
||||
The tree widget provides the following component classes:
|
||||
|
||||
::: textual.widgets.Tree.COMPONENT_CLASSES
|
||||
options:
|
||||
show_root_heading: false
|
||||
show_root_toc_entry: false
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -128,17 +128,17 @@ nav:
|
||||
- "widgets/button.md"
|
||||
- "widgets/checkbox.md"
|
||||
- "widgets/data_table.md"
|
||||
- "widgets/text_log.md"
|
||||
- "widgets/directory_tree.md"
|
||||
- "widgets/footer.md"
|
||||
- "widgets/header.md"
|
||||
- "widgets/index.md"
|
||||
- "widgets/input.md"
|
||||
- "widgets/label.md"
|
||||
- "widgets/list_view.md"
|
||||
- "widgets/list_item.md"
|
||||
- "widgets/list_view.md"
|
||||
- "widgets/placeholder.md"
|
||||
- "widgets/static.md"
|
||||
- "widgets/text_log.md"
|
||||
- "widgets/tree.md"
|
||||
- API:
|
||||
- "api/index.md"
|
||||
@@ -252,6 +252,7 @@ plugins:
|
||||
- search:
|
||||
- autorefs:
|
||||
- mkdocstrings:
|
||||
custom_templates: docs/_templates
|
||||
default_handler: python
|
||||
handlers:
|
||||
python:
|
||||
@@ -263,9 +264,11 @@ plugins:
|
||||
- "!^_"
|
||||
- "^__init__$"
|
||||
- "!^can_replace$"
|
||||
|
||||
watch:
|
||||
- src/textual
|
||||
- exclude:
|
||||
glob:
|
||||
- "**/_template.md"
|
||||
|
||||
|
||||
extra_css:
|
||||
|
||||
2232
poetry.lock
generated
2232
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@ aiohttp = { version = ">=3.8.1", optional = true }
|
||||
click = {version = ">=8.1.2", optional = true}
|
||||
msgpack = { version = ">=1.0.3", optional = true }
|
||||
nanoid = ">=2.0.0"
|
||||
mkdocs-exclude = "^1.0.2"
|
||||
|
||||
[tool.poetry.extras]
|
||||
dev = ["aiohttp", "click", "msgpack"]
|
||||
|
||||
@@ -10,11 +10,11 @@ async def wait_for_idle(
|
||||
) -> None:
|
||||
"""Wait until the process isn't working very hard.
|
||||
|
||||
This will compare wall clock time with process time, if the process time
|
||||
is not advancing the same as wall clock time it means the process is in a
|
||||
sleep state or waiting for input.
|
||||
This will compare wall clock time with process time. If the process time
|
||||
is not advancing at the same rate as wall clock time it means the process is
|
||||
idle (i.e. sleeping or waiting for input).
|
||||
|
||||
When the process is idle it suggests that input has been processes and the state
|
||||
When the process is idle it suggests that input has been processed and the state
|
||||
is predictable enough to test.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1901,9 +1901,11 @@ class App(Generic[ReturnType], DOMNode):
|
||||
else:
|
||||
await self.screen._forward_event(event)
|
||||
|
||||
elif isinstance(event, events.Paste):
|
||||
elif isinstance(event, events.Paste) and not event.is_forwarded:
|
||||
if self.focused is not None:
|
||||
await self.focused._forward_event(event)
|
||||
else:
|
||||
await self.screen._forward_event(event)
|
||||
else:
|
||||
await super().on_event(event)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from rich.style import Style
|
||||
|
||||
from ._types import MessageTarget
|
||||
from .geometry import Offset, Size
|
||||
from .keys import _get_key_aliases, _get_key_display
|
||||
from .keys import _get_key_aliases
|
||||
from .message import Message
|
||||
|
||||
MouseEventT = TypeVar("MouseEventT", bound="MouseEvent")
|
||||
|
||||
@@ -4,7 +4,7 @@ import typing
|
||||
|
||||
from ..case import camel_to_snake
|
||||
|
||||
# ⚠️For any new built-in Widget we create, not only do we have to import them here and add them to `__all__`,
|
||||
# For any new built-in Widget we create, not only do we have to import them here and add them to `__all__`,
|
||||
# but also to the `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't
|
||||
# be able to "see" them.
|
||||
if typing.TYPE_CHECKING:
|
||||
|
||||
@@ -150,9 +150,21 @@ 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("")
|
||||
"""The text label that appears within the button."""
|
||||
|
||||
variant = Reactive.init("default")
|
||||
"""The variant name for the button."""
|
||||
|
||||
disabled = Reactive(False)
|
||||
"""The disabled state of the button; `True` if disabled, `False` if not."""
|
||||
|
||||
class Pressed(Message, bubble=True):
|
||||
"""Event sent when a `Button` is pressed.
|
||||
|
||||
Can be handled using `on_button_pressed` in a subclass of `Button` or
|
||||
in a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
button: The button that was pressed.
|
||||
"""
|
||||
@@ -194,15 +206,6 @@ class Button(Static, can_focus=True):
|
||||
|
||||
self.variant = self.validate_variant(variant)
|
||||
|
||||
label: Reactive[RenderableType] = Reactive("")
|
||||
"""The text label that appears within the button."""
|
||||
|
||||
variant = Reactive.init("default")
|
||||
"""The variant name for the button."""
|
||||
|
||||
disabled = Reactive(False)
|
||||
"""The disabled state of the button; `True` if disabled, `False` if not."""
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield from super().__rich_repr__()
|
||||
yield "variant", self.variant, "default"
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import ClassVar
|
||||
|
||||
from rich.console import RenderableType
|
||||
|
||||
from ..binding import Binding
|
||||
from ..binding import Binding, BindingType
|
||||
from ..geometry import Size
|
||||
from ..message import Message
|
||||
from ..reactive import reactive
|
||||
@@ -13,12 +13,33 @@ from ..scrollbar import ScrollBarRender
|
||||
|
||||
|
||||
class Checkbox(Widget, can_focus=True):
|
||||
"""A checkbox widget. Represents a boolean value. Can be toggled by clicking
|
||||
on it or by pressing the enter key or space bar while it has focus.
|
||||
"""A checkbox widget that represents a boolean value.
|
||||
|
||||
Can be toggled by clicking on it or through its [bindings][textual.widgets.Checkbox.BINDINGS].
|
||||
|
||||
The checkbox widget also contains [component classes][textual.widgets.Checkbox.COMPONENT_CLASSES]
|
||||
that enable more customization.
|
||||
"""
|
||||
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter,space", "toggle", "Toggle", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| enter,space | Toggle the checkbox status. |
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"checkbox--switch",
|
||||
}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `checkbox--switch` | Targets the switch of the checkbox. |
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
|
||||
Checkbox {
|
||||
border: tall transparent;
|
||||
background: $panel;
|
||||
@@ -49,13 +70,27 @@ class Checkbox(Widget, can_focus=True):
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("enter,space", "toggle", "toggle", show=False),
|
||||
]
|
||||
value = reactive(False, init=False)
|
||||
"""The value of the checkbox; `True` for on and `False` for off."""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"checkbox--switch",
|
||||
}
|
||||
slider_pos = reactive(0.0)
|
||||
"""The position of the slider."""
|
||||
|
||||
class Changed(Message, bubble=True):
|
||||
"""Emitted when the status of the checkbox changes.
|
||||
|
||||
Can be handled using `on_checkbox_changed` in a subclass of `Checkbox`
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Checkbox, value: bool) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: bool = value
|
||||
self.input: Checkbox = sender
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -81,12 +116,6 @@ class Checkbox(Widget, can_focus=True):
|
||||
self._reactive_value = value
|
||||
self._should_animate = animate
|
||||
|
||||
value = reactive(False, init=False)
|
||||
"""The value of the checkbox; `True` for on and `False` for off."""
|
||||
|
||||
slider_pos = reactive(0.0)
|
||||
"""The position of the slider."""
|
||||
|
||||
def watch_value(self, value: bool) -> None:
|
||||
target_slider_pos = 1.0 if value else 0.0
|
||||
if self._should_animate:
|
||||
@@ -124,16 +153,3 @@ class Checkbox(Widget, can_focus=True):
|
||||
"""Toggle the checkbox value. As a result of the value changing,
|
||||
a Checkbox.Changed message will be emitted."""
|
||||
self.value = not self.value
|
||||
|
||||
class Changed(Message, bubble=True):
|
||||
"""Checkbox was toggled.
|
||||
|
||||
Attributes:
|
||||
value: The value that the checkbox was changed to.
|
||||
input: The `Checkbox` widget that was changed.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Checkbox, value: bool) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: bool = value
|
||||
self.input: Checkbox = sender
|
||||
|
||||
@@ -32,6 +32,8 @@ from .._two_way_dict import TwoWayDict
|
||||
from .._types import SegmentLines
|
||||
from .._typing import Literal, TypeAlias
|
||||
from ..binding import Binding
|
||||
from .._typing import Literal
|
||||
from ..binding import Binding, BindingType
|
||||
from ..coordinate import Coordinate
|
||||
from ..geometry import Region, Size, Spacing, clamp
|
||||
from ..message import Message
|
||||
@@ -140,6 +142,48 @@ class Row:
|
||||
|
||||
|
||||
class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
"""A tabular widget that contains data."""
|
||||
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter", "select_cursor", "Select", show=False),
|
||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
Binding("right", "cursor_right", "Cursor Right", show=False),
|
||||
Binding("left", "cursor_left", "Cursor Left", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| enter | Select cells under the cursor. |
|
||||
| up | Move the cursor up. |
|
||||
| down | Move the cursor down. |
|
||||
| right | Move the cursor right. |
|
||||
| left | Move the cursor left. |
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"datatable--header",
|
||||
"datatable--cursor-fixed",
|
||||
"datatable--highlight-fixed",
|
||||
"datatable--fixed",
|
||||
"datatable--odd-row",
|
||||
"datatable--even-row",
|
||||
"datatable--highlight",
|
||||
"datatable--cursor",
|
||||
}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `datatable--cursor` | Target the cursor. |
|
||||
| `datatable--cursor-fixed` | Target fixed columns or header under the cursor. |
|
||||
| `datatable--even-row` | Target even rows (row indices start at 0). |
|
||||
| `datatable--fixed` | Target fixed columns or header. |
|
||||
| `datatable--header` | Target the header of the data table. |
|
||||
| `datatable--highlight` | Target the highlighted cell(s). |
|
||||
| `datatable--highlight-fixed` | Target highlighted and fixed columns or header. |
|
||||
| `datatable--odd-row` | Target odd rows (row indices start at 0). |
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
App.-dark DataTable {
|
||||
background:;
|
||||
@@ -190,25 +234,6 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
}
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"datatable--header",
|
||||
"datatable--cursor-fixed",
|
||||
"datatable--highlight-fixed",
|
||||
"datatable--fixed",
|
||||
"datatable--odd-row",
|
||||
"datatable--even-row",
|
||||
"datatable--highlight",
|
||||
"datatable--cursor",
|
||||
}
|
||||
|
||||
BINDINGS = [
|
||||
Binding("enter", "select_cursor", "Select", show=False),
|
||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
Binding("right", "cursor_right", "Cursor Right", show=False),
|
||||
Binding("left", "cursor_left", "Cursor Left", show=False),
|
||||
]
|
||||
|
||||
show_header = Reactive(True)
|
||||
fixed_rows = Reactive(0)
|
||||
fixed_columns = Reactive(0)
|
||||
@@ -222,6 +247,125 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
)
|
||||
hover_cell: Reactive[Coordinate] = Reactive(Coordinate(0, 0), repaint=False)
|
||||
|
||||
class CellHighlighted(Message, bubble=True):
|
||||
"""Emitted when the cursor moves to highlight a new cell.
|
||||
It's only relevant when the `cursor_type` is `"cell"`.
|
||||
It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`),
|
||||
and when the cursor type is changed to `"cell"`. Can be handled using
|
||||
`on_data_table_cell_highlighted` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value in the highlighted cell.
|
||||
coordinate: The coordinate of the highlighted cell.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: DataTable, value: CellType, coordinate: Coordinate
|
||||
) -> None:
|
||||
self.value: CellType = value
|
||||
self.coordinate: Coordinate = coordinate
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "value", self.value
|
||||
yield "coordinate", self.coordinate
|
||||
|
||||
class CellSelected(Message, bubble=True):
|
||||
"""Emitted by the `DataTable` widget when a cell is selected.
|
||||
It's only relevant when the `cursor_type` is `"cell"`. Can be handled using
|
||||
`on_data_table_cell_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value in the cell that was selected.
|
||||
coordinate: The coordinate of the cell that was selected.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: DataTable, value: CellType, coordinate: Coordinate
|
||||
) -> None:
|
||||
self.value: CellType = value
|
||||
self.coordinate: Coordinate = coordinate
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "value", self.value
|
||||
yield "coordinate", self.coordinate
|
||||
|
||||
class RowHighlighted(Message, bubble=True):
|
||||
"""Emitted when a row is highlighted. This message is only emitted when the
|
||||
`cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_highlighted`
|
||||
in a subclass of `DataTable` or in a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_row: The y-coordinate of the cursor that highlighted the row.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_row: int) -> None:
|
||||
self.cursor_row: int = cursor_row
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_row", self.cursor_row
|
||||
|
||||
class RowSelected(Message, bubble=True):
|
||||
"""Emitted when a row is selected. This message is only emitted when the
|
||||
`cursor_type` is set to `"row"`. Can be handled using
|
||||
`on_data_table_row_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_row: The y-coordinate of the cursor that made the selection.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_row: int) -> None:
|
||||
self.cursor_row: int = cursor_row
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_row", self.cursor_row
|
||||
|
||||
class ColumnHighlighted(Message, bubble=True):
|
||||
"""Emitted when a column is highlighted. This message is only emitted when the
|
||||
`cursor_type` is set to `"column"`. Can be handled using
|
||||
`on_data_table_column_highlighted` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_column: The x-coordinate of the column that was highlighted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_column: int) -> None:
|
||||
self.cursor_column: int = cursor_column
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_column", self.cursor_column
|
||||
|
||||
class ColumnSelected(Message, bubble=True):
|
||||
"""Emitted when a column is selected. This message is only emitted when the
|
||||
`cursor_type` is set to `"column"`. Can be handled using
|
||||
`on_data_table_column_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_column: The x-coordinate of the column that was selected.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_column: int) -> None:
|
||||
self.cursor_column: int = cursor_column
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_column", self.cursor_column
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
@@ -1259,122 +1403,3 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
||||
elif cursor_type == "column":
|
||||
_, column = cursor_cell
|
||||
self.emit_no_wait(DataTable.ColumnSelected(self, column))
|
||||
|
||||
class CellHighlighted(Message, bubble=True):
|
||||
"""Emitted when the cursor moves to highlight a new cell.
|
||||
It's only relevant when the `cursor_type` is `"cell"`.
|
||||
It's also emitted when the cell cursor is re-enabled (by setting `show_cursor=True`),
|
||||
and when the cursor type is changed to `"cell"`. Can be handled using
|
||||
`on_data_table_cell_highlighted` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value in the highlighted cell.
|
||||
coordinate: The coordinate of the highlighted cell.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: DataTable, value: CellType, coordinate: Coordinate
|
||||
) -> None:
|
||||
self.value: CellType = value
|
||||
self.coordinate: Coordinate = coordinate
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "value", self.value
|
||||
yield "coordinate", self.coordinate
|
||||
|
||||
class CellSelected(Message, bubble=True):
|
||||
"""Emitted by the `DataTable` widget when a cell is selected.
|
||||
It's only relevant when the `cursor_type` is `"cell"`. Can be handled using
|
||||
`on_data_table_cell_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value in the cell that was selected.
|
||||
coordinate: The coordinate of the cell that was selected.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: DataTable, value: CellType, coordinate: Coordinate
|
||||
) -> None:
|
||||
self.value: CellType = value
|
||||
self.coordinate: Coordinate = coordinate
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "value", self.value
|
||||
yield "coordinate", self.coordinate
|
||||
|
||||
class RowHighlighted(Message, bubble=True):
|
||||
"""Emitted when a row is highlighted. This message is only emitted when the
|
||||
`cursor_type` is set to `"row"`. Can be handled using `on_data_table_row_highlighted`
|
||||
in a subclass of `DataTable` or in a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_row: The y-coordinate of the cursor that highlighted the row.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_row: int) -> None:
|
||||
self.cursor_row: int = cursor_row
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_row", self.cursor_row
|
||||
|
||||
class RowSelected(Message, bubble=True):
|
||||
"""Emitted when a row is selected. This message is only emitted when the
|
||||
`cursor_type` is set to `"row"`. Can be handled using
|
||||
`on_data_table_row_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_row: The y-coordinate of the cursor that made the selection.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_row: int) -> None:
|
||||
self.cursor_row: int = cursor_row
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_row", self.cursor_row
|
||||
|
||||
class ColumnHighlighted(Message, bubble=True):
|
||||
"""Emitted when a column is highlighted. This message is only emitted when the
|
||||
`cursor_type` is set to `"column"`. Can be handled using
|
||||
`on_data_table_column_highlighted` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_column: The x-coordinate of the column that was highlighted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_column: int) -> None:
|
||||
self.cursor_column: int = cursor_column
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_column", self.cursor_column
|
||||
|
||||
class ColumnSelected(Message, bubble=True):
|
||||
"""Emitted when a column is selected. This message is only emitted when the
|
||||
`cursor_type` is set to `"column"`. Can be handled using
|
||||
`on_data_table_column_selected` in a subclass of `DataTable` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
cursor_column: The x-coordinate of the column that was selected.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: DataTable, cursor_column: int) -> None:
|
||||
self.cursor_column: int = cursor_column
|
||||
super().__init__(sender)
|
||||
|
||||
def __rich_repr__(self) -> rich.repr.Result:
|
||||
yield "sender", self.sender
|
||||
yield "cursor_column", self.cursor_column
|
||||
|
||||
@@ -32,18 +32,21 @@ class DirectoryTree(Tree[DirEntry]):
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"tree--label",
|
||||
"tree--guides",
|
||||
"tree--guides-hover",
|
||||
"tree--guides-selected",
|
||||
"tree--cursor",
|
||||
"tree--highlight",
|
||||
"tree--highlight-line",
|
||||
"directory-tree--folder",
|
||||
"directory-tree--file",
|
||||
"directory-tree--extension",
|
||||
"directory-tree--hidden",
|
||||
}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `directory-tree--extension` | Target the extension of a file name. |
|
||||
| `directory-tree--file` | Target files in the directory structure. |
|
||||
| `directory-tree--folder` | Target folders in the directory structure. |
|
||||
| `directory-tree--hidden` | Target hidden items in the directory structure. |
|
||||
|
||||
See also the [component classes for `Tree`][textual.widgets.Tree.COMPONENT_CLASSES].
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
DirectoryTree > .directory-tree--folder {
|
||||
@@ -64,8 +67,17 @@ class DirectoryTree(Tree[DirEntry]):
|
||||
"""
|
||||
|
||||
class FileSelected(Message, bubble=True):
|
||||
"""Emitted when a file is selected.
|
||||
|
||||
Can be handled using `on_directory_tree_file_selected` in a subclass of
|
||||
`DirectoryTree` or in a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
path: The path of the file that was selected.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: MessageTarget, path: str) -> None:
|
||||
self.path = path
|
||||
self.path: str = path
|
||||
super().__init__(sender)
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import ClassVar
|
||||
|
||||
import rich.repr
|
||||
from rich.console import RenderableType
|
||||
from rich.text import Text
|
||||
|
||||
from .. import events
|
||||
from ..keys import _get_key_display
|
||||
from ..reactive import Reactive, watch
|
||||
from ..widget import Widget
|
||||
|
||||
@@ -16,6 +16,21 @@ from ..widget import Widget
|
||||
class Footer(Widget):
|
||||
"""A simple footer widget which docks itself to the bottom of the parent container."""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"footer--description",
|
||||
"footer--key",
|
||||
"footer--highlight",
|
||||
"footer--highlight-key",
|
||||
}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `footer--description` | Targets the descriptions of the key bindings. |
|
||||
| `footer--highlight` | Targets the highlighted key binding. |
|
||||
| `footer--highlight-key` | Targets the key portion of the highlighted key binding. |
|
||||
| `footer--key` | Targets the key portions of the key bindings. |
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
Footer {
|
||||
background: $accent;
|
||||
@@ -38,20 +53,13 @@ class Footer(Widget):
|
||||
}
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES = {
|
||||
"footer--description",
|
||||
"footer--key",
|
||||
"footer--highlight",
|
||||
"footer--highlight-key",
|
||||
}
|
||||
highlight_key: Reactive[str | None] = Reactive(None)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._key_text: Text | None = None
|
||||
self.auto_links = False
|
||||
|
||||
highlight_key: Reactive[str | None] = Reactive(None)
|
||||
|
||||
async def watch_highlight_key(self, value) -> None:
|
||||
"""If highlight key changes we need to regenerate the text."""
|
||||
self._key_text = None
|
||||
|
||||
@@ -100,10 +100,10 @@ class Header(Widget):
|
||||
}
|
||||
"""
|
||||
|
||||
tall = Reactive(False)
|
||||
|
||||
DEFAULT_CLASSES = ""
|
||||
|
||||
tall = Reactive(False)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
show_clock: bool = False,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import ClassVar
|
||||
|
||||
import re
|
||||
|
||||
@@ -10,7 +11,7 @@ from rich.text import Text
|
||||
|
||||
from .. import events
|
||||
from .._segment_tools import line_crop
|
||||
from ..binding import Binding
|
||||
from ..binding import Binding, BindingType
|
||||
from ..geometry import Size
|
||||
from ..message import Message
|
||||
from ..reactive import reactive
|
||||
@@ -55,6 +56,51 @@ class _InputRenderable:
|
||||
class Input(Widget, can_focus=True):
|
||||
"""A text input widget."""
|
||||
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("left", "cursor_left", "cursor left", show=False),
|
||||
Binding("ctrl+left", "cursor_left_word", "cursor left word", show=False),
|
||||
Binding("right", "cursor_right", "cursor right", show=False),
|
||||
Binding("ctrl+right", "cursor_right_word", "cursor right word", show=False),
|
||||
Binding("backspace", "delete_left", "delete left", show=False),
|
||||
Binding("home,ctrl+a", "home", "home", show=False),
|
||||
Binding("end,ctrl+e", "end", "end", show=False),
|
||||
Binding("delete,ctrl+d", "delete_right", "delete right", show=False),
|
||||
Binding("enter", "submit", "submit", show=False),
|
||||
Binding(
|
||||
"ctrl+w", "delete_left_word", "delete left to start of word", show=False
|
||||
),
|
||||
Binding("ctrl+u", "delete_left_all", "delete all to the left", show=False),
|
||||
Binding(
|
||||
"ctrl+f", "delete_right_word", "delete right to start of word", show=False
|
||||
),
|
||||
Binding("ctrl+k", "delete_right_all", "delete all to the right", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| left | Move the cursor left. |
|
||||
| ctrl+left | Move the cursor one word to the left. |
|
||||
| right | Move the cursor right. |
|
||||
| ctrl+right | Move the cursor one word to the right. |
|
||||
| backspace | Delete the character to the left of the cursor. |
|
||||
| home,ctrl+a | Go to the beginning of the input. |
|
||||
| end,ctrl+e | Go to the end of the input. |
|
||||
| delete,ctrl+d | Delete the character to the right of the cursor. |
|
||||
| enter | Submit the current value of the input. |
|
||||
| ctrl+w | Delete the word to the left of the cursor. |
|
||||
| ctrl+u | Delete everything to the left of the cursor. |
|
||||
| ctrl+f | Delete the word to the right of the cursor. |
|
||||
| ctrl+k | Delete everything to the right of the cursor. |
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {"input--cursor", "input--placeholder"}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `input--cursor` | Target the cursor. |
|
||||
| `input--placeholder` | Target the placeholder text (when it exists). |
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
Input {
|
||||
background: $boost;
|
||||
@@ -81,28 +127,6 @@ class Input(Widget, can_focus=True):
|
||||
}
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
Binding("left", "cursor_left", "cursor left", show=False),
|
||||
Binding("ctrl+left", "cursor_left_word", "cursor left word", show=False),
|
||||
Binding("right", "cursor_right", "cursor right", show=False),
|
||||
Binding("ctrl+right", "cursor_right_word", "cursor right word", show=False),
|
||||
Binding("home,ctrl+a", "home", "home", show=False),
|
||||
Binding("end,ctrl+e", "end", "end", show=False),
|
||||
Binding("enter", "submit", "submit", show=False),
|
||||
Binding("backspace", "delete_left", "delete left", show=False),
|
||||
Binding(
|
||||
"ctrl+w", "delete_left_word", "delete left to start of word", show=False
|
||||
),
|
||||
Binding("ctrl+u", "delete_left_all", "delete all to the left", show=False),
|
||||
Binding("delete,ctrl+d", "delete_right", "delete right", show=False),
|
||||
Binding(
|
||||
"ctrl+f", "delete_right_word", "delete right to start of word", show=False
|
||||
),
|
||||
Binding("ctrl+k", "delete_right_all", "delete all to the right", show=False),
|
||||
]
|
||||
|
||||
COMPONENT_CLASSES = {"input--cursor", "input--placeholder"}
|
||||
|
||||
cursor_blink = reactive(True)
|
||||
value = reactive("", layout=True, init=False)
|
||||
input_scroll_offset = reactive(0)
|
||||
@@ -115,6 +139,38 @@ class Input(Widget, can_focus=True):
|
||||
password = reactive(False)
|
||||
max_size: reactive[int | None] = reactive(None)
|
||||
|
||||
class Changed(Message, bubble=True):
|
||||
"""Emitted when the value changes.
|
||||
|
||||
Can be handled using `on_input_changed` in a subclass of `Input` or in a parent
|
||||
widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value that the input was changed to.
|
||||
input: The `Input` widget that was changed.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Input, value: str) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: str = value
|
||||
self.input: Input = sender
|
||||
|
||||
class Submitted(Message, bubble=True):
|
||||
"""Emitted when the enter key is pressed within an `Input`.
|
||||
|
||||
Can be handled using `on_input_submitted` in a subclass of `Input` or in a
|
||||
parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
value: The value of the `Input` being submitted.
|
||||
input: The `Input` widget that is being submitted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Input, value: str) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: str = value
|
||||
self.input: Input = sender
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: str | None = None,
|
||||
@@ -398,29 +454,3 @@ class Input(Widget, can_focus=True):
|
||||
|
||||
async def action_submit(self) -> None:
|
||||
await self.emit(self.Submitted(self, self.value))
|
||||
|
||||
class Changed(Message, bubble=True):
|
||||
"""Value was changed.
|
||||
|
||||
Attributes:
|
||||
value: The value that the input was changed to.
|
||||
input: The `Input` widget that was changed.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Input, value: str) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: str = value
|
||||
self.input: Input = sender
|
||||
|
||||
class Submitted(Message, bubble=True):
|
||||
"""Sent when the enter key is pressed within an `Input`.
|
||||
|
||||
Attributes:
|
||||
value: The value of the `Input` being submitted..
|
||||
input: The `Input` widget that is being submitted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: Input, value: str) -> None:
|
||||
super().__init__(sender)
|
||||
self.value: str = value
|
||||
self.input: Input = sender
|
||||
|
||||
@@ -12,4 +12,3 @@ class Label(Static):
|
||||
height: auto;
|
||||
}
|
||||
"""
|
||||
"""str: The default styling of a `Label`."""
|
||||
|
||||
@@ -25,15 +25,16 @@ class ListItem(Widget, can_focus=False):
|
||||
height: auto;
|
||||
}
|
||||
"""
|
||||
|
||||
highlighted = reactive(False)
|
||||
|
||||
class _ChildClicked(Message):
|
||||
"""For informing with the parent ListView that we were clicked"""
|
||||
|
||||
pass
|
||||
|
||||
def on_click(self, event: events.Click) -> None:
|
||||
self.emit_no_wait(self._ChildClicked(self))
|
||||
|
||||
def watch_highlighted(self, value: bool) -> None:
|
||||
self.set_class(value, "--highlight")
|
||||
|
||||
class _ChildClicked(Message):
|
||||
"""For informing with the parent ListView that we were clicked"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
from typing import ClassVar
|
||||
|
||||
from textual import events
|
||||
from textual.await_remove import AwaitRemove
|
||||
from textual.binding import Binding
|
||||
from textual.binding import Binding, BindingType
|
||||
from textual.containers import Vertical
|
||||
from textual.geometry import clamp
|
||||
from textual.message import Message
|
||||
@@ -19,14 +20,50 @@ class ListView(Vertical, can_focus=True, can_focus_children=False):
|
||||
index: The index in the list that's currently highlighted.
|
||||
"""
|
||||
|
||||
BINDINGS = [
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter", "select_cursor", "Select", show=False),
|
||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| enter | Select the current item. |
|
||||
| up | Move the cursor up. |
|
||||
| down | Move the cursor down. |
|
||||
"""
|
||||
|
||||
index = reactive(0, always_update=True)
|
||||
|
||||
class Highlighted(Message, bubble=True):
|
||||
"""Emitted when the highlighted item changes.
|
||||
|
||||
Highlighted item is controlled using up/down keys.
|
||||
Can be handled using `on_list_view_highlighted` in a subclass of `ListView`
|
||||
or in a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
item: The highlighted item, if there is one highlighted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: ListView, item: ListItem | None) -> None:
|
||||
super().__init__(sender)
|
||||
self.item: ListItem | None = item
|
||||
|
||||
class Selected(Message, bubble=True):
|
||||
"""Emitted when a list item is selected, e.g. when you press the enter key on it.
|
||||
|
||||
Can be handled using `on_list_view_selected` in a subclass of `ListView` or in
|
||||
a parent widget in the DOM.
|
||||
|
||||
Attributes:
|
||||
item: The selected item.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: ListView, item: ListItem) -> None:
|
||||
super().__init__(sender)
|
||||
self.item: ListItem = item
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*children: ListItem,
|
||||
@@ -139,25 +176,3 @@ class ListView(Vertical, can_focus=True, can_focus_children=False):
|
||||
|
||||
def __len__(self):
|
||||
return len(self.children)
|
||||
|
||||
class Highlighted(Message, bubble=True):
|
||||
"""Emitted when the highlighted item changes. Highlighted item is controlled using up/down keys.
|
||||
|
||||
Attributes:
|
||||
item: The highlighted item, if there is one highlighted.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: ListView, item: ListItem | None) -> None:
|
||||
super().__init__(sender)
|
||||
self.item: ListItem | None = item
|
||||
|
||||
class Selected(Message, bubble=True):
|
||||
"""Emitted when a list item is selected, e.g. when you press the enter key on it
|
||||
|
||||
Attributes:
|
||||
item: The selected item.
|
||||
"""
|
||||
|
||||
def __init__(self, sender: ListView, item: ListItem) -> None:
|
||||
super().__init__(sender)
|
||||
self.item: ListItem = item
|
||||
|
||||
@@ -14,7 +14,7 @@ from .._segment_tools import line_pad
|
||||
from .._types import MessageTarget
|
||||
from .._typing import TypeAlias
|
||||
from .._immutable_sequence_view import ImmutableSequenceView
|
||||
from ..binding import Binding
|
||||
from ..binding import Binding, BindingType
|
||||
from ..geometry import Region, Size, clamp
|
||||
from ..message import Message
|
||||
from ..reactive import reactive, var
|
||||
@@ -282,11 +282,39 @@ class TreeNode(Generic[TreeDataType]):
|
||||
|
||||
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
|
||||
BINDINGS = [
|
||||
BINDINGS: ClassVar[list[BindingType]] = [
|
||||
Binding("enter", "select_cursor", "Select", show=False),
|
||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||
Binding("down", "cursor_down", "Cursor Down", show=False),
|
||||
]
|
||||
"""
|
||||
| Key(s) | Description |
|
||||
| :- | :- |
|
||||
| enter | Select the current item. |
|
||||
| up | Move the cursor up. |
|
||||
| down | Move the cursor down. |
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"tree--label",
|
||||
"tree--guides",
|
||||
"tree--guides-hover",
|
||||
"tree--guides-selected",
|
||||
"tree--cursor",
|
||||
"tree--highlight",
|
||||
"tree--highlight-line",
|
||||
}
|
||||
"""
|
||||
| Class | Description |
|
||||
| :- | :- |
|
||||
| `tree--cursor` | Targets the cursor. |
|
||||
| `tree--guides` | Targets the indentation guides. |
|
||||
| `tree--guides-hover` | Targets the indentation guides under the cursor. |
|
||||
| `tree--guides-selected` | Targets the indentation guides that are selected. |
|
||||
| `tree--highlight` | Targets the highlighted items. |
|
||||
| `tree--highlight-line` | Targets the lines under the cursor. |
|
||||
| `tree--label` | Targets the (text) labels of the items. |
|
||||
"""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
Tree {
|
||||
@@ -326,16 +354,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"tree--label",
|
||||
"tree--guides",
|
||||
"tree--guides-hover",
|
||||
"tree--guides-selected",
|
||||
"tree--cursor",
|
||||
"tree--highlight",
|
||||
"tree--highlight-line",
|
||||
}
|
||||
|
||||
show_root = reactive(True)
|
||||
"""bool: Show the root of the tree."""
|
||||
hover_line = var(-1)
|
||||
@@ -370,35 +388,12 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
),
|
||||
}
|
||||
|
||||
class NodeSelected(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is selected.
|
||||
|
||||
Attributes:
|
||||
node: The node that was selected.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||
) -> None:
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is expanded.
|
||||
|
||||
Attributes:
|
||||
node: The node that was expanded.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||
) -> None:
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
class NodeCollapsed(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is collapsed.
|
||||
|
||||
Can be handled using `on_tree_node_collapsed` in a subclass of `Tree` or in a
|
||||
parent node in the DOM.
|
||||
|
||||
Attributes:
|
||||
node: The node that was collapsed.
|
||||
"""
|
||||
@@ -409,9 +404,28 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is expanded.
|
||||
|
||||
Can be handled using `on_tree_node_expanded` in a subclass of `Tree` or in a
|
||||
parent node in the DOM.
|
||||
|
||||
Attributes:
|
||||
node: The node that was expanded.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||
) -> None:
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
class NodeHighlighted(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is highlighted.
|
||||
|
||||
Can be handled using `on_tree_node_highlighted` in a subclass of `Tree` or in a
|
||||
parent node in the DOM.
|
||||
|
||||
Attributes:
|
||||
node: The node that was highlighted.
|
||||
"""
|
||||
@@ -422,6 +436,22 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
class NodeSelected(Generic[EventTreeDataType], Message, bubble=True):
|
||||
"""Event sent when a node is selected.
|
||||
|
||||
Can be handled using `on_tree_node_selected` in a subclass of `Tree` or in a
|
||||
parent node in the DOM.
|
||||
|
||||
Attributes:
|
||||
node: The node that was selected.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||
) -> None:
|
||||
self.node: TreeNode[EventTreeDataType] = node
|
||||
super().__init__(sender)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
label: TextType,
|
||||
|
||||
File diff suppressed because one or more lines are too long
45
tests/snapshot_tests/snapshot_apps/label_widths.py
Normal file
45
tests/snapshot_tests/snapshot_apps/label_widths.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label, Static
|
||||
from rich.panel import Panel
|
||||
|
||||
|
||||
class LabelWrap(App):
|
||||
CSS = """Screen {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#l_data {
|
||||
border: blank;
|
||||
background: lightgray;
|
||||
}
|
||||
|
||||
#s_data {
|
||||
border: blank;
|
||||
background: lightgreen;
|
||||
}
|
||||
|
||||
#p_data {
|
||||
border: blank;
|
||||
background: lightgray;
|
||||
}"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.data = (
|
||||
"Apple Banana Cherry Mango Fig Guava Pineapple:"
|
||||
"Dragon Unicorn Centaur Phoenix Chimera Castle"
|
||||
)
|
||||
|
||||
def compose(self):
|
||||
yield Label(self.data, id="l_data")
|
||||
yield Static(self.data, id="s_data")
|
||||
yield Label(Panel(self.data), id="p_data")
|
||||
|
||||
def on_mount(self):
|
||||
self.dark = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = LabelWrap()
|
||||
app.run()
|
||||
@@ -193,3 +193,8 @@ def test_demo(snap_compare):
|
||||
press=["down", "down", "down"],
|
||||
terminal_size=(100, 30),
|
||||
)
|
||||
|
||||
|
||||
def test_label_widths(snap_compare):
|
||||
"""Test renderable widths are calculate correctly."""
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "label_widths.py")
|
||||
|
||||
@@ -153,8 +153,8 @@ async def test_schedule_reverse_animations() -> None:
|
||||
assert styles.background.rgb == (0, 0, 0)
|
||||
|
||||
# Now, the actual test is to make sure we go back to black if scheduling both at once.
|
||||
styles.animate("background", "white", delay=0.01, duration=0.01)
|
||||
await pilot.pause(0.005)
|
||||
styles.animate("background", "black", delay=0.01, duration=0.01)
|
||||
styles.animate("background", "white", delay=0.05, duration=0.01)
|
||||
await pilot.pause()
|
||||
styles.animate("background", "black", delay=0.05, duration=0.01)
|
||||
await pilot.wait_for_scheduled_animations()
|
||||
assert styles.background.rgb == (0, 0, 0)
|
||||
|
||||
18
tests/test_paste.py
Normal file
18
tests/test_paste.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from textual.app import App
|
||||
from textual import events
|
||||
|
||||
|
||||
async def test_paste_app():
|
||||
paste_events = []
|
||||
|
||||
class PasteApp(App):
|
||||
def on_paste(self, event):
|
||||
paste_events.append(event)
|
||||
|
||||
app = PasteApp()
|
||||
async with app.run_test() as pilot:
|
||||
await app.post_message(events.Paste(sender=app, text="Hello"))
|
||||
await pilot.pause(0)
|
||||
|
||||
assert len(paste_events) == 1
|
||||
assert paste_events[0].text == "Hello"
|
||||
79
tools/widget_documentation.py
Normal file
79
tools/widget_documentation.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Helper script to help document all widgets.
|
||||
|
||||
This goes through the widgets listed in textual.widgets and prints the scaffolding
|
||||
for the tables that are used to document the classvars BINDINGS and COMPONENT_CLASSES.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import textual.widgets
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from textual.binding import Binding
|
||||
|
||||
|
||||
def print_bindings(widget: str, bindings: list[Binding]) -> None:
|
||||
"""Print a table summarising the bindings.
|
||||
|
||||
The table contains columns for the key(s) that trigger the binding,
|
||||
the method that it calls (and tries to link it to the widget itself),
|
||||
and the description of the binding.
|
||||
"""
|
||||
if bindings:
|
||||
print("BINDINGS")
|
||||
print('"""')
|
||||
print("| Key(s) | Description |")
|
||||
print("| :- | :- |")
|
||||
|
||||
for binding in bindings:
|
||||
print(f"| {binding.key} | {binding.description} |")
|
||||
|
||||
if bindings:
|
||||
print('"""')
|
||||
|
||||
|
||||
def print_component_classes(classes: set[str]) -> None:
|
||||
"""Print a table to document these component classes.
|
||||
|
||||
The table contains two columns, one with the component class name and another
|
||||
for the description of what the component class is for.
|
||||
The second column is always empty.
|
||||
"""
|
||||
if classes:
|
||||
print("COMPONENT_CLASSES")
|
||||
print('"""')
|
||||
print("| Class | Description |")
|
||||
print("| :- | :- |")
|
||||
|
||||
for cls in sorted(classes):
|
||||
print(f"| `{cls}` | XXX |")
|
||||
|
||||
if classes:
|
||||
print('"""')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entrypoint.
|
||||
|
||||
Iterates over all widgets and prints docs tables.
|
||||
"""
|
||||
|
||||
widgets: list[str] = textual.widgets.__all__
|
||||
|
||||
for widget in widgets:
|
||||
w = getattr(textual.widgets, widget)
|
||||
bindings: list[Binding] = w.__dict__.get("BINDINGS", [])
|
||||
component_classes: set[str] = getattr(w, "COMPONENT_CLASSES", set())
|
||||
|
||||
if bindings or component_classes:
|
||||
print(widget)
|
||||
print()
|
||||
print_bindings(widget, bindings)
|
||||
print_component_classes(component_classes)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user