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
|
- 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
|
- 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
|
- 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
|
## [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
|
## Messages
|
||||||
|
|
||||||
### Pressed
|
### ::: textual.widgets.Button.Pressed
|
||||||
|
|
||||||
The `Button.Pressed` message is sent when the button is pressed.
|
|
||||||
|
|
||||||
- [x] Bubbles
|
|
||||||
|
|
||||||
#### Attributes
|
|
||||||
|
|
||||||
_No other attributes_
|
|
||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
|
|
||||||
|
|||||||
@@ -32,25 +32,31 @@ The example below shows checkboxes in various states.
|
|||||||
| ------- | ------ | ------- | ---------------------------------- |
|
| ------- | ------ | ------- | ---------------------------------- |
|
||||||
| `value` | `bool` | `False` | The default value of the checkbox. |
|
| `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
|
## Messages
|
||||||
|
|
||||||
### Pressed
|
### ::: textual.widgets.Checkbox.Changed
|
||||||
|
|
||||||
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. |
|
|
||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
|
|
||||||
- To remove the spacing around a checkbox, set `border: none;` and `padding: 0;`.
|
- 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
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,24 @@ The example below populates a table with CSV data.
|
|||||||
|
|
||||||
### ::: textual.widgets.DataTable.ColumnSelected
|
### ::: 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
|
## See Also
|
||||||
|
|
||||||
* [DataTable][textual.widgets.DataTable] code reference
|
* [DataTable][textual.widgets.DataTable] code reference
|
||||||
|
|||||||
@@ -16,17 +16,7 @@ The example below creates a simple tree to navigate the current working director
|
|||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
### FileSelected
|
### ::: textual.widgets.DirectoryTree.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. |
|
|
||||||
|
|
||||||
## Reactive Attributes
|
## 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. |
|
| `show_guides` | `bool` | `True` | Show guide lines between levels. |
|
||||||
| `guide_depth` | `int` | `4` | Amount of indentation between parent and child. |
|
| `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
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ widget. Notice how the `Footer` automatically displays the keybinding.
|
|||||||
|
|
||||||
This widget sends no messages.
|
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
|
## Additional Notes
|
||||||
|
|
||||||
* You can prevent keybindings from appearing in the footer by setting the `show` argument of the `Binding` to `False`.
|
* 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
|
## 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 |
|
::: textual.widgets.Input.BINDINGS
|
||||||
| --------- | ----- | -------------------------------- |
|
options:
|
||||||
| `value` | `str` | The new value in the text input. |
|
show_root_heading: false
|
||||||
|
show_root_toc_entry: false
|
||||||
|
|
||||||
|
## Component Classes
|
||||||
|
|
||||||
### Submitted
|
The input widget provides the following component classes:
|
||||||
|
|
||||||
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. |
|
|
||||||
|
|
||||||
|
::: textual.widgets.Input.COMPONENT_CLASSES
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
show_root_toc_entry: false
|
||||||
|
|
||||||
## Additional Notes
|
## 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 |
|
| `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
|
#### Attributes
|
||||||
|
|
||||||
|
|||||||
@@ -35,34 +35,18 @@ The example below shows an app with a simple `ListView`.
|
|||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
### Highlighted
|
### ::: textual.widgets.ListView.Highlighted
|
||||||
|
|
||||||
The `ListView.Highlighted` message is emitted when the highlight changes.
|
### ::: textual.widgets.ListView.Selected
|
||||||
This happens when you use the arrow keys on your keyboard and when you
|
|
||||||
click on a list item.
|
|
||||||
|
|
||||||
- [x] Bubbles
|
## Bindings
|
||||||
|
|
||||||
#### Attributes
|
The list view widget defines directly the following bindings:
|
||||||
|
|
||||||
| 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. |
|
|
||||||
|
|
||||||
|
::: textual.widgets.ListView.BINDINGS
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
show_root_toc_entry: false
|
||||||
|
|
||||||
## See Also
|
## 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. |
|
| `show_guides` | `bool` | `True` | Show guide lines between levels. |
|
||||||
| `guide_depth` | `int` | `4` | Amount of indentation between parent and child. |
|
| `guide_depth` | `int` | `4` | Amount of indentation between parent and child. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Messages
|
## 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 |
|
## Bindings
|
||||||
| --------- | ----------------------------------------- | -------------- |
|
|
||||||
| `node` | [TreeNode][textual.widgets.tree.TreeNode] | Selected node. |
|
|
||||||
|
|
||||||
|
The tree widget defines directly the following bindings:
|
||||||
|
|
||||||
### NodeExpanded
|
::: textual.widgets.Tree.BINDINGS
|
||||||
|
options:
|
||||||
The `Tree.NodeExpanded` message is sent when the user expands a node in the tree.
|
show_root_heading: false
|
||||||
|
show_root_toc_entry: false
|
||||||
#### 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. |
|
|
||||||
|
|
||||||
|
## 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
|
## See Also
|
||||||
|
|
||||||
|
|||||||
@@ -128,17 +128,17 @@ nav:
|
|||||||
- "widgets/button.md"
|
- "widgets/button.md"
|
||||||
- "widgets/checkbox.md"
|
- "widgets/checkbox.md"
|
||||||
- "widgets/data_table.md"
|
- "widgets/data_table.md"
|
||||||
- "widgets/text_log.md"
|
|
||||||
- "widgets/directory_tree.md"
|
- "widgets/directory_tree.md"
|
||||||
- "widgets/footer.md"
|
- "widgets/footer.md"
|
||||||
- "widgets/header.md"
|
- "widgets/header.md"
|
||||||
- "widgets/index.md"
|
- "widgets/index.md"
|
||||||
- "widgets/input.md"
|
- "widgets/input.md"
|
||||||
- "widgets/label.md"
|
- "widgets/label.md"
|
||||||
- "widgets/list_view.md"
|
|
||||||
- "widgets/list_item.md"
|
- "widgets/list_item.md"
|
||||||
|
- "widgets/list_view.md"
|
||||||
- "widgets/placeholder.md"
|
- "widgets/placeholder.md"
|
||||||
- "widgets/static.md"
|
- "widgets/static.md"
|
||||||
|
- "widgets/text_log.md"
|
||||||
- "widgets/tree.md"
|
- "widgets/tree.md"
|
||||||
- API:
|
- API:
|
||||||
- "api/index.md"
|
- "api/index.md"
|
||||||
@@ -252,6 +252,7 @@ plugins:
|
|||||||
- search:
|
- search:
|
||||||
- autorefs:
|
- autorefs:
|
||||||
- mkdocstrings:
|
- mkdocstrings:
|
||||||
|
custom_templates: docs/_templates
|
||||||
default_handler: python
|
default_handler: python
|
||||||
handlers:
|
handlers:
|
||||||
python:
|
python:
|
||||||
@@ -263,9 +264,11 @@ plugins:
|
|||||||
- "!^_"
|
- "!^_"
|
||||||
- "^__init__$"
|
- "^__init__$"
|
||||||
- "!^can_replace$"
|
- "!^can_replace$"
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
- src/textual
|
- src/textual
|
||||||
|
- exclude:
|
||||||
|
glob:
|
||||||
|
- "**/_template.md"
|
||||||
|
|
||||||
|
|
||||||
extra_css:
|
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}
|
click = {version = ">=8.1.2", optional = true}
|
||||||
msgpack = { version = ">=1.0.3", optional = true }
|
msgpack = { version = ">=1.0.3", optional = true }
|
||||||
nanoid = ">=2.0.0"
|
nanoid = ">=2.0.0"
|
||||||
|
mkdocs-exclude = "^1.0.2"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
dev = ["aiohttp", "click", "msgpack"]
|
dev = ["aiohttp", "click", "msgpack"]
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ async def wait_for_idle(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Wait until the process isn't working very hard.
|
"""Wait until the process isn't working very hard.
|
||||||
|
|
||||||
This will compare wall clock time with process time, if the process time
|
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
|
is not advancing at the same rate as wall clock time it means the process is
|
||||||
sleep state or waiting for input.
|
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.
|
is predictable enough to test.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -1901,9 +1901,11 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
else:
|
else:
|
||||||
await self.screen._forward_event(event)
|
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:
|
if self.focused is not None:
|
||||||
await self.focused._forward_event(event)
|
await self.focused._forward_event(event)
|
||||||
|
else:
|
||||||
|
await self.screen._forward_event(event)
|
||||||
else:
|
else:
|
||||||
await super().on_event(event)
|
await super().on_event(event)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from rich.style import Style
|
|||||||
|
|
||||||
from ._types import MessageTarget
|
from ._types import MessageTarget
|
||||||
from .geometry import Offset, Size
|
from .geometry import Offset, Size
|
||||||
from .keys import _get_key_aliases, _get_key_display
|
from .keys import _get_key_aliases
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
|
||||||
MouseEventT = TypeVar("MouseEventT", bound="MouseEvent")
|
MouseEventT = TypeVar("MouseEventT", bound="MouseEvent")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import typing
|
|||||||
|
|
||||||
from ..case import camel_to_snake
|
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
|
# but also to the `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't
|
||||||
# be able to "see" them.
|
# be able to "see" them.
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
|
|||||||
@@ -150,9 +150,21 @@ class Button(Static, can_focus=True):
|
|||||||
ACTIVE_EFFECT_DURATION = 0.3
|
ACTIVE_EFFECT_DURATION = 0.3
|
||||||
"""When buttons are clicked they get the `-active` class for this duration (in seconds)"""
|
"""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):
|
class Pressed(Message, bubble=True):
|
||||||
"""Event sent when a `Button` is pressed.
|
"""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:
|
Attributes:
|
||||||
button: The button that was pressed.
|
button: The button that was pressed.
|
||||||
"""
|
"""
|
||||||
@@ -194,15 +206,6 @@ class Button(Static, can_focus=True):
|
|||||||
|
|
||||||
self.variant = self.validate_variant(variant)
|
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:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield from super().__rich_repr__()
|
yield from super().__rich_repr__()
|
||||||
yield "variant", self.variant, "default"
|
yield "variant", self.variant, "default"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import ClassVar
|
|||||||
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
|
|
||||||
from ..binding import Binding
|
from ..binding import Binding, BindingType
|
||||||
from ..geometry import Size
|
from ..geometry import Size
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import reactive
|
from ..reactive import reactive
|
||||||
@@ -13,12 +13,33 @@ from ..scrollbar import ScrollBarRender
|
|||||||
|
|
||||||
|
|
||||||
class Checkbox(Widget, can_focus=True):
|
class Checkbox(Widget, can_focus=True):
|
||||||
"""A checkbox widget. Represents a boolean value. Can be toggled by clicking
|
"""A checkbox widget that represents a boolean value.
|
||||||
on it or by pressing the enter key or space bar while it has focus.
|
|
||||||
|
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 = """
|
DEFAULT_CSS = """
|
||||||
|
|
||||||
Checkbox {
|
Checkbox {
|
||||||
border: tall transparent;
|
border: tall transparent;
|
||||||
background: $panel;
|
background: $panel;
|
||||||
@@ -49,13 +70,27 @@ class Checkbox(Widget, can_focus=True):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
value = reactive(False, init=False)
|
||||||
Binding("enter,space", "toggle", "toggle", show=False),
|
"""The value of the checkbox; `True` for on and `False` for off."""
|
||||||
]
|
|
||||||
|
|
||||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
slider_pos = reactive(0.0)
|
||||||
"checkbox--switch",
|
"""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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -81,12 +116,6 @@ class Checkbox(Widget, can_focus=True):
|
|||||||
self._reactive_value = value
|
self._reactive_value = value
|
||||||
self._should_animate = animate
|
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:
|
def watch_value(self, value: bool) -> None:
|
||||||
target_slider_pos = 1.0 if value else 0.0
|
target_slider_pos = 1.0 if value else 0.0
|
||||||
if self._should_animate:
|
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,
|
"""Toggle the checkbox value. As a result of the value changing,
|
||||||
a Checkbox.Changed message will be emitted."""
|
a Checkbox.Changed message will be emitted."""
|
||||||
self.value = not self.value
|
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 .._types import SegmentLines
|
||||||
from .._typing import Literal, TypeAlias
|
from .._typing import Literal, TypeAlias
|
||||||
from ..binding import Binding
|
from ..binding import Binding
|
||||||
|
from .._typing import Literal
|
||||||
|
from ..binding import Binding, BindingType
|
||||||
from ..coordinate import Coordinate
|
from ..coordinate import Coordinate
|
||||||
from ..geometry import Region, Size, Spacing, clamp
|
from ..geometry import Region, Size, Spacing, clamp
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
@@ -140,6 +142,48 @@ class Row:
|
|||||||
|
|
||||||
|
|
||||||
class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
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 = """
|
DEFAULT_CSS = """
|
||||||
App.-dark DataTable {
|
App.-dark DataTable {
|
||||||
background:;
|
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)
|
show_header = Reactive(True)
|
||||||
fixed_rows = Reactive(0)
|
fixed_rows = Reactive(0)
|
||||||
fixed_columns = 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)
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -1259,122 +1403,3 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
elif cursor_type == "column":
|
elif cursor_type == "column":
|
||||||
_, column = cursor_cell
|
_, column = cursor_cell
|
||||||
self.emit_no_wait(DataTable.ColumnSelected(self, column))
|
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]] = {
|
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--folder",
|
||||||
"directory-tree--file",
|
"directory-tree--file",
|
||||||
"directory-tree--extension",
|
"directory-tree--extension",
|
||||||
"directory-tree--hidden",
|
"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 = """
|
DEFAULT_CSS = """
|
||||||
DirectoryTree > .directory-tree--folder {
|
DirectoryTree > .directory-tree--folder {
|
||||||
@@ -64,8 +67,17 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class FileSelected(Message, bubble=True):
|
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:
|
def __init__(self, sender: MessageTarget, path: str) -> None:
|
||||||
self.path = path
|
self.path: str = path
|
||||||
super().__init__(sender)
|
super().__init__(sender)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from ..keys import _get_key_display
|
|
||||||
from ..reactive import Reactive, watch
|
from ..reactive import Reactive, watch
|
||||||
from ..widget import Widget
|
from ..widget import Widget
|
||||||
|
|
||||||
@@ -16,6 +16,21 @@ from ..widget import Widget
|
|||||||
class Footer(Widget):
|
class Footer(Widget):
|
||||||
"""A simple footer widget which docks itself to the bottom of the parent container."""
|
"""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 = """
|
DEFAULT_CSS = """
|
||||||
Footer {
|
Footer {
|
||||||
background: $accent;
|
background: $accent;
|
||||||
@@ -38,20 +53,13 @@ class Footer(Widget):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
COMPONENT_CLASSES = {
|
highlight_key: Reactive[str | None] = Reactive(None)
|
||||||
"footer--description",
|
|
||||||
"footer--key",
|
|
||||||
"footer--highlight",
|
|
||||||
"footer--highlight-key",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._key_text: Text | None = None
|
self._key_text: Text | None = None
|
||||||
self.auto_links = False
|
self.auto_links = False
|
||||||
|
|
||||||
highlight_key: Reactive[str | None] = Reactive(None)
|
|
||||||
|
|
||||||
async def watch_highlight_key(self, value) -> None:
|
async def watch_highlight_key(self, value) -> None:
|
||||||
"""If highlight key changes we need to regenerate the text."""
|
"""If highlight key changes we need to regenerate the text."""
|
||||||
self._key_text = None
|
self._key_text = None
|
||||||
|
|||||||
@@ -100,10 +100,10 @@ class Header(Widget):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tall = Reactive(False)
|
|
||||||
|
|
||||||
DEFAULT_CLASSES = ""
|
DEFAULT_CLASSES = ""
|
||||||
|
|
||||||
|
tall = Reactive(False)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
show_clock: bool = False,
|
show_clock: bool = False,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ from rich.text import Text
|
|||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from .._segment_tools import line_crop
|
from .._segment_tools import line_crop
|
||||||
from ..binding import Binding
|
from ..binding import Binding, BindingType
|
||||||
from ..geometry import Size
|
from ..geometry import Size
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import reactive
|
from ..reactive import reactive
|
||||||
@@ -55,6 +56,51 @@ class _InputRenderable:
|
|||||||
class Input(Widget, can_focus=True):
|
class Input(Widget, can_focus=True):
|
||||||
"""A text input widget."""
|
"""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 = """
|
DEFAULT_CSS = """
|
||||||
Input {
|
Input {
|
||||||
background: $boost;
|
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)
|
cursor_blink = reactive(True)
|
||||||
value = reactive("", layout=True, init=False)
|
value = reactive("", layout=True, init=False)
|
||||||
input_scroll_offset = reactive(0)
|
input_scroll_offset = reactive(0)
|
||||||
@@ -115,6 +139,38 @@ class Input(Widget, can_focus=True):
|
|||||||
password = reactive(False)
|
password = reactive(False)
|
||||||
max_size: reactive[int | None] = reactive(None)
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
value: str | None = None,
|
value: str | None = None,
|
||||||
@@ -398,29 +454,3 @@ class Input(Widget, can_focus=True):
|
|||||||
|
|
||||||
async def action_submit(self) -> None:
|
async def action_submit(self) -> None:
|
||||||
await self.emit(self.Submitted(self, self.value))
|
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;
|
height: auto;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
"""str: The default styling of a `Label`."""
|
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ class ListItem(Widget, can_focus=False):
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
highlighted = reactive(False)
|
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:
|
def on_click(self, event: events.Click) -> None:
|
||||||
self.emit_no_wait(self._ChildClicked(self))
|
self.emit_no_wait(self._ChildClicked(self))
|
||||||
|
|
||||||
def watch_highlighted(self, value: bool) -> None:
|
def watch_highlighted(self, value: bool) -> None:
|
||||||
self.set_class(value, "--highlight")
|
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 __future__ import annotations
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
from textual import events
|
from textual import events
|
||||||
from textual.await_remove import AwaitRemove
|
from textual.await_remove import AwaitRemove
|
||||||
from textual.binding import Binding
|
from textual.binding import Binding, BindingType
|
||||||
from textual.containers import Vertical
|
from textual.containers import Vertical
|
||||||
from textual.geometry import clamp
|
from textual.geometry import clamp
|
||||||
from textual.message import Message
|
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.
|
index: The index in the list that's currently highlighted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS: ClassVar[list[BindingType]] = [
|
||||||
Binding("enter", "select_cursor", "Select", show=False),
|
Binding("enter", "select_cursor", "Select", show=False),
|
||||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||||
Binding("down", "cursor_down", "Cursor Down", 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)
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*children: ListItem,
|
*children: ListItem,
|
||||||
@@ -139,25 +176,3 @@ class ListView(Vertical, can_focus=True, can_focus_children=False):
|
|||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.children)
|
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 .._types import MessageTarget
|
||||||
from .._typing import TypeAlias
|
from .._typing import TypeAlias
|
||||||
from .._immutable_sequence_view import ImmutableSequenceView
|
from .._immutable_sequence_view import ImmutableSequenceView
|
||||||
from ..binding import Binding
|
from ..binding import Binding, BindingType
|
||||||
from ..geometry import Region, Size, clamp
|
from ..geometry import Region, Size, clamp
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import reactive, var
|
from ..reactive import reactive, var
|
||||||
@@ -282,11 +282,39 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
|
|
||||||
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS: ClassVar[list[BindingType]] = [
|
||||||
Binding("enter", "select_cursor", "Select", show=False),
|
Binding("enter", "select_cursor", "Select", show=False),
|
||||||
Binding("up", "cursor_up", "Cursor Up", show=False),
|
Binding("up", "cursor_up", "Cursor Up", show=False),
|
||||||
Binding("down", "cursor_down", "Cursor Down", 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 = """
|
DEFAULT_CSS = """
|
||||||
Tree {
|
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)
|
show_root = reactive(True)
|
||||||
"""bool: Show the root of the tree."""
|
"""bool: Show the root of the tree."""
|
||||||
hover_line = var(-1)
|
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):
|
class NodeCollapsed(Generic[EventTreeDataType], Message, bubble=True):
|
||||||
"""Event sent when a node is collapsed.
|
"""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:
|
Attributes:
|
||||||
node: The node that was collapsed.
|
node: The node that was collapsed.
|
||||||
"""
|
"""
|
||||||
@@ -409,9 +404,28 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self.node: TreeNode[EventTreeDataType] = node
|
self.node: TreeNode[EventTreeDataType] = node
|
||||||
super().__init__(sender)
|
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):
|
class NodeHighlighted(Generic[EventTreeDataType], Message, bubble=True):
|
||||||
"""Event sent when a node is highlighted.
|
"""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:
|
Attributes:
|
||||||
node: The node that was highlighted.
|
node: The node that was highlighted.
|
||||||
"""
|
"""
|
||||||
@@ -422,6 +436,22 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self.node: TreeNode[EventTreeDataType] = node
|
self.node: TreeNode[EventTreeDataType] = node
|
||||||
super().__init__(sender)
|
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__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
label: TextType,
|
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"],
|
press=["down", "down", "down"],
|
||||||
terminal_size=(100, 30),
|
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)
|
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.
|
# 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)
|
styles.animate("background", "white", delay=0.05, duration=0.01)
|
||||||
await pilot.pause(0.005)
|
await pilot.pause()
|
||||||
styles.animate("background", "black", delay=0.01, duration=0.01)
|
styles.animate("background", "black", delay=0.05, duration=0.01)
|
||||||
await pilot.wait_for_scheduled_animations()
|
await pilot.wait_for_scheduled_animations()
|
||||||
assert styles.background.rgb == (0, 0, 0)
|
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