Merge pull request #1673 from Textualize/doc-improvements

Documentation improvements
This commit is contained in:
Rodrigo Girão Serrão
2023-01-30 15:52:45 +00:00
committed by GitHub
27 changed files with 1885 additions and 1538 deletions

View 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
View 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ from .._cache import LRUCache
from .._segment_tools import line_crop
from .._types import SegmentLines
from .._typing import Literal
from ..binding import Binding
from ..binding import Binding, BindingType
from ..coordinate import Coordinate
from ..geometry import Region, Size, Spacing, clamp
from ..message import Message
@@ -84,6 +84,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:;
@@ -134,25 +176,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)
@@ -166,6 +189,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,
*,
@@ -966,122 +1108,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

View File

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

View File

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

View File

@@ -100,10 +100,10 @@ class Header(Widget):
}
"""
tall = Reactive(False)
DEFAULT_CLASSES = ""
tall = Reactive(False)
def __init__(
self,
show_clock: bool = False,

View File

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

View File

@@ -12,4 +12,3 @@ class Label(Static):
height: auto;
}
"""
"""str: The default styling of a `Label`."""

View File

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

View File

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

View File

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

View 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()