mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
words
This commit is contained in:
@@ -8,11 +8,11 @@ Action methods are methods on your app or widgets prefixed with `action_`. Aside
|
|||||||
|
|
||||||
!!! information
|
!!! information
|
||||||
|
|
||||||
Action methods may be coroutines (methods with the `async` keyword).
|
Action methods may be coroutines (defined with the `async` keyword).
|
||||||
|
|
||||||
Let's write an app with a simple action.
|
Let's write an app with a simple action.
|
||||||
|
|
||||||
```python title="actions01.py" hl_lines="6-8"
|
```python title="actions01.py" hl_lines="6-8 12"
|
||||||
--8<-- "docs/examples/guide/actions/actions01.py"
|
--8<-- "docs/examples/guide/actions/actions01.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ Action strings have a simple syntax, which for the most part replicates Python's
|
|||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
|
|
||||||
As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings.
|
As much as they *look* like Python code, Textual does **not** call Python's `eval` function or similar to compile action strings.
|
||||||
|
|
||||||
Action strings have the following format:
|
Action strings have the following format:
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ When you click any of the links, Textual runs the `"set_background"` action to c
|
|||||||
|
|
||||||
## Bindings
|
## Bindings
|
||||||
|
|
||||||
Textual will also run actions that are bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action.
|
Textual will also run actions bound to keys. The following example adds key [bindings](./input.md#bindings) for the ++r++, ++g++, and ++b++ keys which call the `"set_background"` action.
|
||||||
|
|
||||||
=== "actions04.py"
|
=== "actions04.py"
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ The following example defines a custom widget with its own `set_background` acti
|
|||||||
--8<-- "docs/examples/guide/actions/actions05.py"
|
--8<-- "docs/examples/guide/actions/actions05.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "actions05.py"
|
=== "actions05.sass"
|
||||||
|
|
||||||
```sass title="actions05.css"
|
```sass title="actions05.css"
|
||||||
--8<-- "docs/examples/guide/actions/actions05.css"
|
--8<-- "docs/examples/guide/actions/actions05.css"
|
||||||
@@ -158,6 +158,3 @@ Textual supports the following builtin actions which are defined on the app.
|
|||||||
### Quit
|
### Quit
|
||||||
|
|
||||||
::: textual.app.App.action_quit
|
::: textual.app.App.action_quit
|
||||||
|
|
||||||
|
|
||||||
*TODO:* document more actions
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class MyWidget(Widget):
|
|||||||
|
|
||||||
### Layout
|
### Layout
|
||||||
|
|
||||||
The smart refresh feature will update the content area of a widget, but will not change its size. If modifying an attribute should change the size of the widget, you should set `layout=True` on the reactive attribute. This ensures that your CSS layout will update accordingly.
|
The smart refresh feature will update the content area of a widget, but will not change its size. If modifying an attribute should change the size of the widget, you can set `layout=True` on the reactive attribute. This ensures that your CSS layout will update accordingly.
|
||||||
|
|
||||||
The following example modifies "refresh01.py" so that the greeting has an automatic width.
|
The following example modifies "refresh01.py" so that the greeting has an automatic width.
|
||||||
|
|
||||||
@@ -138,9 +138,11 @@ If you type in to the input now, the greeting will expand to fit the content. If
|
|||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
The next superpower we will look at is _validation_. If you add a method that begins with `validate_` followed by the name of your attribute, it will be called when you assign a value to that attribute. This method should accept the incoming value as a positional argument, and return the value to set (which may be the same or a different value).
|
The next superpower we will look at is _validation_, which can check and potentially modify a value you assign to a reactive attribute.
|
||||||
|
|
||||||
A common use for this is to restrict numbers to a given range. The following example keeps a count. There is a button to increase the count, and a button to decrease it.
|
If you add a method that begins with `validate_` followed by the name of your attribute, it will be called when you assign a value to that attribute. This method should accept the incoming value as a positional argument, and return the value to set (which may be the same or a different value).
|
||||||
|
|
||||||
|
A common use for this is to restrict numbers to a given range. The following example keeps a count. There is a button to increase the count, and a button to decrease it. The validation ensures that the count will never go above 10 or below zero.
|
||||||
|
|
||||||
=== "validate01.py"
|
=== "validate01.py"
|
||||||
|
|
||||||
@@ -159,7 +161,7 @@ A common use for this is to restrict numbers to a given range. The following exa
|
|||||||
```{.textual path="docs/examples/guide/reactivity/validate01.py"}
|
```{.textual path="docs/examples/guide/reactivity/validate01.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you click the buttons in the above example it will show the current count. When `self.count` is modified in the button handler, Textual runs `validate_count` which limits self.count` to a maximum of 10, and stops it going below zero.
|
If you click the buttons in the above example it will show the current count. When `self.count` is modified in the button handler, Textual runs `validate_count` which performs the validation to limit the value of count.
|
||||||
|
|
||||||
## Watch methods
|
## Watch methods
|
||||||
|
|
||||||
@@ -218,8 +220,10 @@ The following example uses a computed attribute. It displays three inputs for th
|
|||||||
```{.textual path="docs/examples/guide/reactivity/computed01.py"}
|
```{.textual path="docs/examples/guide/reactivity/computed01.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the `compute_color` method which combines the color components into a [Color][textual.color.Color] object. When the _result_ of this method changes, Textual calls `watch_color` which uses the new color as a background.
|
Note the `compute_color` method which combines the color components into a [Color][textual.color.Color] object. It will be recalculated when any of the `red` , `green`, or `blue` attributes are modified.
|
||||||
|
|
||||||
|
When the result of `compute_color` changes, Textual will also call `watch_color` since `color` still has the [watch method](#watch-methods) superpower.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
You should avoid doing anything slow or cpu-intensive in a compute method. Textual calls compute methods on an object when _any_ reactive attribute changes, so it can known when it changes.
|
It is best to avoid doing anything slow or cpu-intensive in a compute method. Textual calls compute methods on an object when _any_ reactive attribute changes.
|
||||||
|
|||||||
@@ -1552,12 +1552,23 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self.set_focus(node)
|
self.set_focus(node)
|
||||||
|
|
||||||
async def action_switch_screen(self, screen: str) -> None:
|
async def action_switch_screen(self, screen: str) -> None:
|
||||||
|
"""Switches to another screen.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
screen (str): Name of the screen.
|
||||||
|
"""
|
||||||
self.switch_screen(screen)
|
self.switch_screen(screen)
|
||||||
|
|
||||||
async def action_push_screen(self, screen: str) -> None:
|
async def action_push_screen(self, screen: str) -> None:
|
||||||
|
"""Pushes a screen on to the screen stack and makes it active.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
screen (str): Name of the screen.
|
||||||
|
"""
|
||||||
self.push_screen(screen)
|
self.push_screen(screen)
|
||||||
|
|
||||||
async def action_pop_screen(self) -> None:
|
async def action_pop_screen(self) -> None:
|
||||||
|
"""Removes the topmost screen and makes the new topmost screen active."""
|
||||||
self.pop_screen()
|
self.pop_screen()
|
||||||
|
|
||||||
async def action_back(self) -> None:
|
async def action_back(self) -> None:
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class Widget(DOMNode):
|
|||||||
"""Widget may receive focus."""
|
"""Widget may receive focus."""
|
||||||
can_focus_children: bool = True
|
can_focus_children: bool = True
|
||||||
"""Widget's children may receive focus."""
|
"""Widget's children may receive focus."""
|
||||||
expand = Reactive(True)
|
expand = Reactive(False)
|
||||||
"""Rich renderable may expand."""
|
"""Rich renderable may expand."""
|
||||||
shrink = Reactive(True)
|
shrink = Reactive(True)
|
||||||
"""Rich renderable may shrink."""
|
"""Rich renderable may shrink."""
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ class Input(Widget, can_focus=True):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
Binding("left", "cursor_left", "cursor left"),
|
Binding("left", "cursor_left", "cursor left", show=False),
|
||||||
Binding("right", "cursor_right", "cursor right"),
|
Binding("right", "cursor_right", "cursor right", show=False),
|
||||||
Binding("backspace", "delete_left", "delete left"),
|
Binding("backspace", "delete_left", "delete left", show=False),
|
||||||
Binding("home", "home", "home"),
|
Binding("home", "home", "home", show=False),
|
||||||
Binding("end", "end", "end"),
|
Binding("end", "end", "end", show=False),
|
||||||
Binding("ctrl+d", "delete_right", "delete right"),
|
Binding("ctrl+d", "delete_right", "delete right", show=False),
|
||||||
Binding("enter", "submit", "Submit"),
|
Binding("enter", "submit", "Submit", show=False),
|
||||||
]
|
]
|
||||||
|
|
||||||
COMPONENT_CLASSES = {"input--cursor", "input--placeholder"}
|
COMPONENT_CLASSES = {"input--cursor", "input--placeholder"}
|
||||||
@@ -237,14 +237,14 @@ class Input(Widget, can_focus=True):
|
|||||||
|
|
||||||
# Do key bindings first
|
# Do key bindings first
|
||||||
if await self.handle_key(event):
|
if await self.handle_key(event):
|
||||||
|
event.prevent_default()
|
||||||
event.stop()
|
event.stop()
|
||||||
elif event.key in ("tab", "shift+tab"):
|
|
||||||
return
|
return
|
||||||
elif event.is_printable:
|
elif event.is_printable:
|
||||||
event.stop()
|
event.stop()
|
||||||
assert event.char is not None
|
assert event.char is not None
|
||||||
self.insert_text_at_cursor(event.char)
|
self.insert_text_at_cursor(event.char)
|
||||||
event.prevent_default()
|
event.prevent_default()
|
||||||
|
|
||||||
def on_paste(self, event: events.Paste) -> None:
|
def on_paste(self, event: events.Paste) -> None:
|
||||||
line = event.text.splitlines()[0]
|
line = event.text.splitlines()[0]
|
||||||
@@ -267,6 +267,11 @@ class Input(Widget, can_focus=True):
|
|||||||
self.cursor_position = len(self.value)
|
self.cursor_position = len(self.value)
|
||||||
|
|
||||||
def insert_text_at_cursor(self, text: str) -> None:
|
def insert_text_at_cursor(self, text: str) -> None:
|
||||||
|
"""Insert new text at the cursor, move the cursor to the end of the new text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): new text to insert.
|
||||||
|
"""
|
||||||
if self.cursor_position > len(self.value):
|
if self.cursor_position > len(self.value):
|
||||||
self.value += text
|
self.value += text
|
||||||
self.cursor_position = len(self.value)
|
self.cursor_position = len(self.value)
|
||||||
|
|||||||
Reference in New Issue
Block a user