Initial commit
This commit is contained in:
182
skills/textual-builder/references/basics.md
Normal file
182
skills/textual-builder/references/basics.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Textual Basics
|
||||
|
||||
## App Structure
|
||||
|
||||
Every Textual app follows this pattern:
|
||||
|
||||
```python
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Widget
|
||||
|
||||
class MyApp(App):
|
||||
"""Docstring describing the app."""
|
||||
|
||||
# Optional: Link to external CSS file
|
||||
CSS_PATH = "app.tcss"
|
||||
|
||||
# Optional: Inline CSS
|
||||
CSS = """
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Create child widgets."""
|
||||
yield Widget()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Called when app is mounted and ready."""
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = MyApp()
|
||||
app.run()
|
||||
```
|
||||
|
||||
## Compose Method
|
||||
|
||||
The `compose()` method yields widgets to add to the app. It's called once during initialization:
|
||||
|
||||
```python
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Header()
|
||||
yield ContentWidget()
|
||||
yield Footer()
|
||||
```
|
||||
|
||||
## Mounting
|
||||
|
||||
- `on_mount()`: Called when the app/widget is fully mounted and ready
|
||||
- `mount()`: Dynamically add widgets after app starts (returns a coroutine)
|
||||
|
||||
```python
|
||||
async def on_key(self) -> None:
|
||||
# Must await when modifying mounted widgets
|
||||
await self.mount(NewWidget())
|
||||
self.query_one(Button).label = "Modified!"
|
||||
```
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
Reactive attributes automatically update the UI when changed:
|
||||
|
||||
```python
|
||||
from textual.reactive import reactive
|
||||
|
||||
class Counter(Widget):
|
||||
count = reactive(0) # Initial value
|
||||
|
||||
def watch_count(self, new_value: int) -> None:
|
||||
"""Called automatically when count changes."""
|
||||
self.query_one(Label).update(f"Count: {new_value}")
|
||||
|
||||
def increment(self) -> None:
|
||||
self.count += 1 # Triggers watch_count
|
||||
```
|
||||
|
||||
### Reactive with Bindings
|
||||
|
||||
Set `bindings=True` to auto-refresh footer bindings when reactive changes:
|
||||
|
||||
```python
|
||||
class MyApp(App):
|
||||
page = reactive(0, bindings=True)
|
||||
|
||||
def check_action(self, action: str, parameters) -> bool | None:
|
||||
"""Return None to disable action."""
|
||||
if action == "next" and self.page == MAX_PAGES:
|
||||
return None # Dims the key in footer
|
||||
return True
|
||||
```
|
||||
|
||||
## Querying Widgets
|
||||
|
||||
Find widgets in the DOM:
|
||||
|
||||
```python
|
||||
# Get one widget (raises if not found)
|
||||
button = self.query_one(Button)
|
||||
button = self.query_one("#my-id")
|
||||
|
||||
# Get multiple widgets
|
||||
all_buttons = self.query(Button)
|
||||
for button in all_buttons:
|
||||
pass
|
||||
|
||||
# Get with CSS selector
|
||||
widget = self.query_one("#container .special-class")
|
||||
```
|
||||
|
||||
## Messages and Events
|
||||
|
||||
### Built-in Events
|
||||
|
||||
Handle with `on_<event>` methods:
|
||||
|
||||
```python
|
||||
def on_mount(self) -> None:
|
||||
"""When mounted."""
|
||||
pass
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
"""Key pressed."""
|
||||
if event.key == "escape":
|
||||
self.exit()
|
||||
```
|
||||
|
||||
### Widget Messages
|
||||
|
||||
Handle messages from child widgets:
|
||||
|
||||
```python
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Button was clicked."""
|
||||
self.notify(f"Button {event.button.id} clicked!")
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Input text changed."""
|
||||
self.value = event.value
|
||||
```
|
||||
|
||||
### Custom Messages
|
||||
|
||||
Define custom messages in your widgets:
|
||||
|
||||
```python
|
||||
from textual.message import Message
|
||||
|
||||
class MyWidget(Widget):
|
||||
class ValueChanged(Message):
|
||||
"""Posted when value changes."""
|
||||
def __init__(self, value: int) -> None:
|
||||
super().__init__()
|
||||
self.value = value
|
||||
|
||||
def update_value(self, new_value: int) -> None:
|
||||
self.value = new_value
|
||||
self.post_message(self.ValueChanged(new_value))
|
||||
|
||||
# Handle in parent
|
||||
def on_my_widget_value_changed(self, event: MyWidget.ValueChanged) -> None:
|
||||
self.notify(f"New value: {event.value}")
|
||||
```
|
||||
|
||||
## Preventing Message Propagation
|
||||
|
||||
Stop messages from bubbling to parent:
|
||||
|
||||
```python
|
||||
def on_switch_changed(self, event: Switch.Changed) -> None:
|
||||
event.stop() # Don't propagate to parent
|
||||
# Handle here
|
||||
```
|
||||
|
||||
## Preventing Reactive Watchers
|
||||
|
||||
Temporarily prevent reactive watchers from firing:
|
||||
|
||||
```python
|
||||
with self.prevent(MyWidget.ValueChanged):
|
||||
self.value = new_value # Won't trigger watch_value or post message
|
||||
```
|
||||
Reference in New Issue
Block a user