Modern Text User Interface framework for building cross-platform terminal and web applications with Python
Overall
score
93%
Comprehensive event handling system for user interactions, widget lifecycle, and system events, including keyboard input, mouse interactions, focus management, and custom message passing.
Foundation classes for all events and messages in the Textual framework.
class Message:
"""Base class for all messages."""
def __init__(self) -> None:
"""Initialize a message."""
def prevent_default(self) -> None:
"""Prevent the default handler for this message."""
def stop(self) -> None:
"""Stop message propagation."""
# Properties
sender: MessageTarget | None
time: float
handler_name: str | None
class Event(Message):
"""Base class for all events."""
def __init__(self) -> None:
"""Initialize an event."""
# Properties
bubble: bool # Whether the event bubbles up the DOM
verbose: bool # Whether to include in verbose logging
class InputEvent(Event):
"""Base class for input-related events."""
passEvents for handling keyboard input and key combinations.
class Key(InputEvent):
"""Keyboard key press event."""
def __init__(self, key: str, character: str | None = None):
"""
Initialize a key event.
Parameters:
- key: The key identifier (e.g., "enter", "escape", "ctrl+c")
- character: The character representation (if printable)
"""
@property
def is_printable(self) -> bool:
"""Whether the key produces a printable character."""
# Properties
key: str # Key identifier
character: str | None # Character representation
name: str # Human-readable key name
aliases: list[str] # Alternative key namesEvents for handling mouse interactions including clicks, movement, and scrolling.
class MouseEvent(InputEvent):
"""Base class for mouse events."""
def __init__(self, x: int, y: int, button: int = 0):
"""
Initialize a mouse event.
Parameters:
- x: Mouse X coordinate
- y: Mouse Y coordinate
- button: Mouse button number
"""
# Properties
x: int # Mouse X coordinate
y: int # Mouse Y coordinate
button: int # Mouse button (0=left, 1=middle, 2=right)
screen_x: int # Screen-relative X
screen_y: int # Screen-relative Y
class MouseDown(MouseEvent):
"""Mouse button pressed down event."""
pass
class MouseUp(MouseEvent):
"""Mouse button released event."""
pass
class Click(MouseEvent):
"""Mouse click event (down + up)."""
pass
class MouseMove(MouseEvent):
"""Mouse movement event."""
pass
class MouseScrollDown(MouseEvent):
"""Mouse scroll down event."""
pass
class MouseScrollUp(MouseEvent):
"""Mouse scroll up event."""
passEvents related to widget focus state and visibility changes.
class Focus(Event):
"""Widget gained focus event."""
def __init__(self, widget: Widget):
"""
Initialize a focus event.
Parameters:
- widget: The widget that gained focus
"""
# Properties
widget: Widget
class Blur(Event):
"""Widget lost focus event."""
def __init__(self, widget: Widget):
"""
Initialize a blur event.
Parameters:
- widget: The widget that lost focus
"""
# Properties
widget: Widget
class Show(Event):
"""Widget became visible event."""
def __init__(self, widget: Widget):
"""
Initialize a show event.
Parameters:
- widget: The widget that became visible
"""
class Hide(Event):
"""Widget became hidden event."""
def __init__(self, widget: Widget):
"""
Initialize a hide event.
Parameters:
- widget: The widget that became hidden
"""Events that occur during widget creation, mounting, and destruction.
class Mount(Event):
"""Widget was mounted to the DOM event."""
def __init__(self, widget: Widget):
"""
Initialize a mount event.
Parameters:
- widget: The widget that was mounted
"""
class Unmount(Event):
"""Widget was unmounted from the DOM event."""
def __init__(self, widget: Widget):
"""
Initialize an unmount event.
Parameters:
- widget: The widget that was unmounted
"""
class Compose(Event):
"""Widget composition completed event."""
pass
class Ready(Event):
"""Application is ready to receive events."""
pass
class Load(Event):
"""Application/widget loading completed event."""
passEvents related to system state and layout changes.
class Resize(Event):
"""Screen or widget resize event."""
def __init__(self, size: Size, virtual_size: Size):
"""
Initialize a resize event.
Parameters:
- size: New size of the widget/screen
- virtual_size: Virtual size including scrollable area
"""
# Properties
size: Size
virtual_size: Size
class Idle(Event):
"""Application idle event (no recent input)."""
pass
class ExitApp(Event):
"""Application exit requested event."""
def __init__(self, result: Any = None):
"""
Initialize an exit event.
Parameters:
- result: Exit result value
"""
# Properties
result: Any
class Callback(Event):
"""Callback function invocation event."""
def __init__(self, callback: Callable, *args, **kwargs):
"""
Initialize a callback event.
Parameters:
- callback: Function to call
- *args: Positional arguments
- **kwargs: Keyword arguments
"""
class Timer(Event):
"""Timer callback event."""
def __init__(self, timer: Timer, time: float):
"""
Initialize a timer event.
Parameters:
- timer: The timer instance
- time: Current time
"""Common event patterns used by built-in widgets.
class Changed(Event):
"""Generic value changed event."""
def __init__(self, widget: Widget, value: Any):
"""
Initialize a changed event.
Parameters:
- widget: Widget that changed
- value: New value
"""
# Properties
widget: Widget
value: Any
class Pressed(Event):
"""Generic button/item pressed event."""
def __init__(self, widget: Widget):
"""
Initialize a pressed event.
Parameters:
- widget: Widget that was pressed
"""
class Selected(Event):
"""Generic selection event."""
def __init__(self, widget: Widget, item: Any):
"""
Initialize a selected event.
Parameters:
- widget: Widget with selection
- item: Selected item
"""
class Toggled(Event):
"""Generic toggle state changed event."""
def __init__(self, widget: Widget, toggled: bool):
"""
Initialize a toggled event.
Parameters:
- widget: Widget that was toggled
- toggled: New toggle state
"""from textual.app import App
from textual.widgets import Button, Input
from textual.events import Key
class EventApp(App):
def compose(self):
yield Input(placeholder="Type here...", id="input")
yield Button("Click me!", id="button")
def on_key(self, event: Key):
"""Handle any key press."""
self.log(f"Key pressed: {event.key}")
if event.key == "ctrl+q":
self.exit()
def on_button_pressed(self, event: Button.Pressed):
"""Handle button presses."""
self.log(f"Button {event.button.id} was pressed!")
def on_input_changed(self, event: Input.Changed):
"""Handle input value changes."""
self.log(f"Input value: {event.value}")
def on_input_submitted(self, event: Input.Submitted):
"""Handle input submission (Enter key)."""
self.log(f"Input submitted: {event.value}")from textual.app import App
from textual.widgets import Button, Input
from textual import on
class DecoratorApp(App):
def compose(self):
yield Input(id="name-input")
yield Button("Submit", id="submit-btn")
yield Button("Clear", id="clear-btn")
@on(Button.Pressed, "#submit-btn")
def handle_submit(self, event: Button.Pressed):
"""Handle submit button with CSS selector."""
input_widget = self.query_one("#name-input", Input)
self.log(f"Submitting: {input_widget.value}")
@on(Button.Pressed, "#clear-btn")
def handle_clear(self, event: Button.Pressed):
"""Handle clear button."""
input_widget = self.query_one("#name-input", Input)
input_widget.value = ""
@on(Input.Changed, "#name-input")
def handle_name_change(self, event: Input.Changed):
"""Handle name input changes."""
submit_btn = self.query_one("#submit-btn", Button)
submit_btn.disabled = len(event.value.strip()) == 0from textual.app import App
from textual.widget import Widget
from textual.message import Message
from textual.widgets import Button
class CustomWidget(Widget):
"""A widget that sends custom events."""
class DataReady(Message):
"""Custom event sent when data is ready."""
def __init__(self, widget: Widget, data: dict):
super().__init__()
self.widget = widget
self.data = data
def compose(self):
yield Button("Load Data", id="load")
def on_button_pressed(self, event: Button.Pressed):
"""Handle button press and send custom event."""
if event.button.id == "load":
# Simulate data loading
data = {"result": "success", "items": [1, 2, 3]}
# Send custom event
self.post_message(self.DataReady(self, data))
class CustomEventApp(App):
def compose(self):
yield CustomWidget(id="custom")
def on_custom_widget_data_ready(self, event: CustomWidget.DataReady):
"""Handle custom data ready event."""
self.log(f"Data received: {event.data}")from textual.app import App
from textual.widgets import Static
from textual.events import MouseDown, MouseUp, MouseMove
class MouseApp(App):
def compose(self):
yield Static("Click and drag on me!", id="target")
def on_mouse_down(self, event: MouseDown):
"""Handle mouse button press."""
self.log(f"Mouse down at ({event.x}, {event.y}), button {event.button}")
def on_mouse_up(self, event: MouseUp):
"""Handle mouse button release."""
self.log(f"Mouse up at ({event.x}, {event.y})")
def on_mouse_move(self, event: MouseMove):
"""Handle mouse movement."""
# Only log occasionally to avoid spam
if event.x % 5 == 0 and event.y % 5 == 0:
self.log(f"Mouse at ({event.x}, {event.y})")from textual.app import App
from textual.widgets import Input
from textual.events import Key
class PreventionApp(App):
def compose(self):
yield Input(placeholder="Try typing numbers...", id="text-only")
def on_key(self, event: Key):
"""Prevent numeric input in text field."""
# Check if the focused widget is our text-only input
focused = self.focused
if focused and focused.id == "text-only":
# Prevent numeric characters
if event.character and event.character.isdigit():
event.prevent_default() # Stop default handling
event.stop() # Stop event propagation
self.log("Numeric input blocked!")Install with Tessl CLI
npx tessl i tessl/pypi-textualevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10