mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
actions docs
This commit is contained in:
17
docs/examples/guide/actions/actions01.py
Normal file
17
docs/examples/guide/actions/actions01.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from textual.app import App
|
||||||
|
from textual import events
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsApp(App):
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.screen.styles.background = color
|
||||||
|
self.bell()
|
||||||
|
|
||||||
|
def on_key(self, event: events.Key) -> None:
|
||||||
|
if event.key == "r":
|
||||||
|
self.action_set_background("red")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = ActionsApp()
|
||||||
|
app.run()
|
||||||
17
docs/examples/guide/actions/actions02.py
Normal file
17
docs/examples/guide/actions/actions02.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from textual.app import App
|
||||||
|
from textual import events
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsApp(App):
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.screen.styles.background = color
|
||||||
|
self.bell()
|
||||||
|
|
||||||
|
async def on_key(self, event: events.Key) -> None:
|
||||||
|
if event.key == "r":
|
||||||
|
await self.action("set_background('red')")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = ActionsApp()
|
||||||
|
app.run()
|
||||||
23
docs/examples/guide/actions/actions03.py
Normal file
23
docs/examples/guide/actions/actions03.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
TEXT = """
|
||||||
|
[b]Set your background[/b]
|
||||||
|
[@click=set_background('red')]Red[/]
|
||||||
|
[@click=set_background('green')]Green[/]
|
||||||
|
[@click=set_background('blue')]Blue[/]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsApp(App):
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Static(TEXT)
|
||||||
|
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.screen.styles.background = color
|
||||||
|
self.bell()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = ActionsApp()
|
||||||
|
app.run()
|
||||||
29
docs/examples/guide/actions/actions04.py
Normal file
29
docs/examples/guide/actions/actions04.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
TEXT = """
|
||||||
|
[b]Set your background[/b]
|
||||||
|
[@click=set_background('red')]Red[/]
|
||||||
|
[@click=set_background('green')]Green[/]
|
||||||
|
[@click=set_background('blue')]Blue[/]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsApp(App):
|
||||||
|
BINDINGS = [
|
||||||
|
("r", "set_background('red')", "Red"),
|
||||||
|
("g", "set_background('green')", "Green"),
|
||||||
|
("b", "set_background('blue')", "Blue"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Static(TEXT)
|
||||||
|
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.screen.styles.background = color
|
||||||
|
self.bell()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = ActionsApp()
|
||||||
|
app.run()
|
||||||
11
docs/examples/guide/actions/actions05.css
Normal file
11
docs/examples/guide/actions/actions05.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Screen {
|
||||||
|
layout: grid;
|
||||||
|
grid-size: 1;
|
||||||
|
grid-gutter: 2 4;
|
||||||
|
grid-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorSwitcher {
|
||||||
|
height: 100%;
|
||||||
|
margin: 2 4;
|
||||||
|
}
|
||||||
36
docs/examples/guide/actions/actions05.py
Normal file
36
docs/examples/guide/actions/actions05.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
TEXT = """
|
||||||
|
[b]Set your background[/b]
|
||||||
|
[@click=set_background('cyan')]Cyan[/]
|
||||||
|
[@click=set_background('magenta')]Magenta[/]
|
||||||
|
[@click=set_background('yellow')]Yellow[/]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ColorSwitcher(Static):
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.styles.background = color
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsApp(App):
|
||||||
|
CSS_PATH = "actions05.css"
|
||||||
|
BINDINGS = [
|
||||||
|
("r", "set_background('red')", "Red"),
|
||||||
|
("g", "set_background('green')", "Green"),
|
||||||
|
("b", "set_background('blue')", "Blue"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield ColorSwitcher(TEXT)
|
||||||
|
yield ColorSwitcher(TEXT)
|
||||||
|
|
||||||
|
def action_set_background(self, color: str) -> None:
|
||||||
|
self.screen.styles.background = color
|
||||||
|
self.bell()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = ActionsApp()
|
||||||
|
app.run()
|
||||||
@@ -1,3 +1,107 @@
|
|||||||
# Actions
|
# Actions
|
||||||
|
|
||||||
TODO: Actions docs
|
Actions are white-listed functions with a string syntax you can embed in to links and bind to keys. In this chapter wee will discuss how to create actions and how to run them.
|
||||||
|
|
||||||
|
## Action methods
|
||||||
|
|
||||||
|
Action methods are methods on your app or widgets prefixed with `action_`. Aside from the prefix these are regular methods which you could call directly if you wished.
|
||||||
|
|
||||||
|
Let's write an app with a simple action.
|
||||||
|
|
||||||
|
```python title="actions01.py" hl_lines="6-8"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions01.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `action_set_background` method is an action which sets the background of the screen. The key handler calls this action if you press the ++r++ key, to set the background color to red.
|
||||||
|
|
||||||
|
Although it is possible (and occasionally useful) to call action methods in this way, they are intended to be parsed from an _action string_. For instance, the string "set_background('red')" is an action string that would call `self.action_set_background('red')`.
|
||||||
|
|
||||||
|
The following example replaces the immediate call with a call to [action()][textual.widgets.Widget.action] which parses an action strings and dispatches it to the appropriate action method.
|
||||||
|
|
||||||
|
```python title="actions02.py" hl_lines="10-12"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions02.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the `action()` method is a coroutine so `on_key` needs to be prefixed with the `async` key.
|
||||||
|
|
||||||
|
You will not typically need this in a real app as Textual will run actions for you from key bindings or links. Before we discuss more practical uses for action strings, let's have a look at the syntax for actions.
|
||||||
|
|
||||||
|
## Action syntax
|
||||||
|
|
||||||
|
Action strings have a simple syntax, which for the most part replicates Python's function call syntax.
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
|
||||||
|
As much as they look like Python code, Textual does **not** call Python's `eval` function or similar to execute action strings, as this would create a security risk.
|
||||||
|
|
||||||
|
Action strings have the following format:
|
||||||
|
|
||||||
|
- The name of an action on is own will call the action method with no parameters. For example, `"bell"` will call `action_bell()`.
|
||||||
|
- Actions may be followed by braces containing Python objects. For example, the action string `set_background('red')` will call `action_set_background("red")`.
|
||||||
|
- Actions may be prefixed with a _namespace_ (see below) follow by a dot.
|
||||||
|
|
||||||
|
<div class="excalidraw">
|
||||||
|
--8<-- "docs/images/actions/format.excalidraw.svg"
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### Action parameters
|
||||||
|
|
||||||
|
If the action strings contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbols.
|
||||||
|
|
||||||
|
Consequently `"set_background('blue')"` is a valid action string, but `"set_background(new_color)"` is not — because `new_color` is a variable and not a literal.
|
||||||
|
|
||||||
|
## Actions in links
|
||||||
|
|
||||||
|
Actions may be embedded in links with console markup, which you can introduce the `@click` tag.
|
||||||
|
|
||||||
|
The following example mounts simple static text with embedded action links.
|
||||||
|
|
||||||
|
=== "actions03.py"
|
||||||
|
|
||||||
|
```python title="actions03.py" hl_lines="4-9 13-14"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions03.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Output"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/guide/actions/actions03.py"}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color.
|
||||||
|
|
||||||
|
## Actions in binding
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
=== "actions04.py"
|
||||||
|
|
||||||
|
```python title="actions04.py" hl_lines="13-17"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions04.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Output"
|
||||||
|
|
||||||
|
```{.textual path="docs/examples/guide/actions/actions04.py" press="g"}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run this example, you can change the background by pressing keys in addition to clicking links.
|
||||||
|
|
||||||
|
## Action namespaces
|
||||||
|
|
||||||
|
Textual will look for action methods on the widget or app where they are used. If we were to create a [custom widget](./widgets.md#custom-widgets) with an action method, it can have its own set of actions.
|
||||||
|
|
||||||
|
The following example defines a custom widget with its own `set_background` action.
|
||||||
|
|
||||||
|
=== "actions05.py"
|
||||||
|
|
||||||
|
```python title="actions05.py" hl_lines="13-14"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions05.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "actions05.py"
|
||||||
|
|
||||||
|
```sass title="actions05.css"
|
||||||
|
--8<-- "docs/examples/guide/actions/actions05.css"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you click on the links, it will call the action method for the _widget_ where the click is handler. The ++r++, ++g++, and ++b++ keys are defined on the App so will set the background for the app.
|
||||||
|
|||||||
16
docs/images/actions/format.excalidraw.svg
Normal file
16
docs/images/actions/format.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -1366,30 +1366,43 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
await super().on_event(event)
|
await super().on_event(event)
|
||||||
|
|
||||||
async def action(
|
async def action(
|
||||||
self, action: str, default_namespace: object | None = None
|
self,
|
||||||
) -> None:
|
action: str | tuple[str, tuple[str, ...]],
|
||||||
|
default_namespace: object | None = None,
|
||||||
|
) -> bool:
|
||||||
"""Perform an action.
|
"""Perform an action.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
action (str): Action encoded in a string.
|
action (str): Action encoded in a string.
|
||||||
default_namespace (object | None): Namespace to use if not provided in the action,
|
default_namespace (object | None): Namespace to use if not provided in the action,
|
||||||
or None to use app. Defaults to None.
|
or None to use app. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the event has handled.
|
||||||
"""
|
"""
|
||||||
target, params = actions.parse(action)
|
if isinstance(action, str):
|
||||||
|
target, params = actions.parse(action)
|
||||||
|
else:
|
||||||
|
target, params = action
|
||||||
|
implicit_destination = True
|
||||||
if "." in target:
|
if "." in target:
|
||||||
destination, action_name = target.split(".", 1)
|
destination, action_name = target.split(".", 1)
|
||||||
if destination not in self._action_targets:
|
if destination not in self._action_targets:
|
||||||
raise ActionError("Action namespace {destination} is not known")
|
raise ActionError("Action namespace {destination} is not known")
|
||||||
action_target = getattr(self, destination)
|
action_target = getattr(self, destination)
|
||||||
|
implicit_destination = True
|
||||||
else:
|
else:
|
||||||
action_target = default_namespace or self
|
action_target = default_namespace or self
|
||||||
action_name = target
|
action_name = target
|
||||||
|
|
||||||
await self._dispatch_action(action_target, action_name, params)
|
handled = await self._dispatch_action(action_target, action_name, params)
|
||||||
|
if not handled and implicit_destination and not isinstance(action_target, App):
|
||||||
|
handled = await self.app._dispatch_action(self.app, action_name, params)
|
||||||
|
return handled
|
||||||
|
|
||||||
async def _dispatch_action(
|
async def _dispatch_action(
|
||||||
self, namespace: object, action_name: str, params: Any
|
self, namespace: object, action_name: str, params: Any
|
||||||
) -> None:
|
) -> bool:
|
||||||
log(
|
log(
|
||||||
"<action>",
|
"<action>",
|
||||||
namespace=namespace,
|
namespace=namespace,
|
||||||
@@ -1403,6 +1416,8 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
log(f"<action> {action_name!r} has no target")
|
log(f"<action> {action_name!r} has no target")
|
||||||
if callable(method):
|
if callable(method):
|
||||||
await invoke(method, *params)
|
await invoke(method, *params)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def _broker_event(
|
async def _broker_event(
|
||||||
self, event_name: str, event: events.Event, default_namespace: object | None
|
self, event_name: str, event: events.Event, default_namespace: object | None
|
||||||
@@ -1427,7 +1442,7 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
event.stop()
|
event.stop()
|
||||||
if isinstance(action, str):
|
if isinstance(action, (str, tuple)):
|
||||||
await self.action(action, default_namespace=default_namespace)
|
await self.action(action, default_namespace=default_namespace)
|
||||||
elif callable(action):
|
elif callable(action):
|
||||||
await action()
|
await action()
|
||||||
|
|||||||
Reference in New Issue
Block a user