This commit is contained in:
Will McGugan
2022-10-07 21:05:20 +01:00
parent f2f13eacf0
commit cf14b812ed
5 changed files with 41 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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