0
# Event System
1
2
Comprehensive event handling system for user interactions, widget lifecycle, and system events, including keyboard input, mouse interactions, focus management, and custom message passing.
3
4
## Capabilities
5
6
### Base Event Classes
7
8
Foundation classes for all events and messages in the Textual framework.
9
10
```python { .api }
11
class Message:
12
"""Base class for all messages."""
13
14
def __init__(self) -> None:
15
"""Initialize a message."""
16
17
def prevent_default(self) -> None:
18
"""Prevent the default handler for this message."""
19
20
def stop(self) -> None:
21
"""Stop message propagation."""
22
23
# Properties
24
sender: MessageTarget | None
25
time: float
26
handler_name: str | None
27
28
class Event(Message):
29
"""Base class for all events."""
30
31
def __init__(self) -> None:
32
"""Initialize an event."""
33
34
# Properties
35
bubble: bool # Whether the event bubbles up the DOM
36
verbose: bool # Whether to include in verbose logging
37
38
class InputEvent(Event):
39
"""Base class for input-related events."""
40
pass
41
```
42
43
### Keyboard Events
44
45
Events for handling keyboard input and key combinations.
46
47
```python { .api }
48
class Key(InputEvent):
49
"""Keyboard key press event."""
50
51
def __init__(self, key: str, character: str | None = None):
52
"""
53
Initialize a key event.
54
55
Parameters:
56
- key: The key identifier (e.g., "enter", "escape", "ctrl+c")
57
- character: The character representation (if printable)
58
"""
59
60
@property
61
def is_printable(self) -> bool:
62
"""Whether the key produces a printable character."""
63
64
# Properties
65
key: str # Key identifier
66
character: str | None # Character representation
67
name: str # Human-readable key name
68
aliases: list[str] # Alternative key names
69
```
70
71
### Mouse Events
72
73
Events for handling mouse interactions including clicks, movement, and scrolling.
74
75
```python { .api }
76
class MouseEvent(InputEvent):
77
"""Base class for mouse events."""
78
79
def __init__(self, x: int, y: int, button: int = 0):
80
"""
81
Initialize a mouse event.
82
83
Parameters:
84
- x: Mouse X coordinate
85
- y: Mouse Y coordinate
86
- button: Mouse button number
87
"""
88
89
# Properties
90
x: int # Mouse X coordinate
91
y: int # Mouse Y coordinate
92
button: int # Mouse button (0=left, 1=middle, 2=right)
93
screen_x: int # Screen-relative X
94
screen_y: int # Screen-relative Y
95
96
class MouseDown(MouseEvent):
97
"""Mouse button pressed down event."""
98
pass
99
100
class MouseUp(MouseEvent):
101
"""Mouse button released event."""
102
pass
103
104
class Click(MouseEvent):
105
"""Mouse click event (down + up)."""
106
pass
107
108
class MouseMove(MouseEvent):
109
"""Mouse movement event."""
110
pass
111
112
class MouseScrollDown(MouseEvent):
113
"""Mouse scroll down event."""
114
pass
115
116
class MouseScrollUp(MouseEvent):
117
"""Mouse scroll up event."""
118
pass
119
```
120
121
### Focus and Visibility Events
122
123
Events related to widget focus state and visibility changes.
124
125
```python { .api }
126
class Focus(Event):
127
"""Widget gained focus event."""
128
129
def __init__(self, widget: Widget):
130
"""
131
Initialize a focus event.
132
133
Parameters:
134
- widget: The widget that gained focus
135
"""
136
137
# Properties
138
widget: Widget
139
140
class Blur(Event):
141
"""Widget lost focus event."""
142
143
def __init__(self, widget: Widget):
144
"""
145
Initialize a blur event.
146
147
Parameters:
148
- widget: The widget that lost focus
149
"""
150
151
# Properties
152
widget: Widget
153
154
class Show(Event):
155
"""Widget became visible event."""
156
157
def __init__(self, widget: Widget):
158
"""
159
Initialize a show event.
160
161
Parameters:
162
- widget: The widget that became visible
163
"""
164
165
class Hide(Event):
166
"""Widget became hidden event."""
167
168
def __init__(self, widget: Widget):
169
"""
170
Initialize a hide event.
171
172
Parameters:
173
- widget: The widget that became hidden
174
"""
175
```
176
177
### Widget Lifecycle Events
178
179
Events that occur during widget creation, mounting, and destruction.
180
181
```python { .api }
182
class Mount(Event):
183
"""Widget was mounted to the DOM event."""
184
185
def __init__(self, widget: Widget):
186
"""
187
Initialize a mount event.
188
189
Parameters:
190
- widget: The widget that was mounted
191
"""
192
193
class Unmount(Event):
194
"""Widget was unmounted from the DOM event."""
195
196
def __init__(self, widget: Widget):
197
"""
198
Initialize an unmount event.
199
200
Parameters:
201
- widget: The widget that was unmounted
202
"""
203
204
class Compose(Event):
205
"""Widget composition completed event."""
206
pass
207
208
class Ready(Event):
209
"""Application is ready to receive events."""
210
pass
211
212
class Load(Event):
213
"""Application/widget loading completed event."""
214
pass
215
```
216
217
### System and Layout Events
218
219
Events related to system state and layout changes.
220
221
```python { .api }
222
class Resize(Event):
223
"""Screen or widget resize event."""
224
225
def __init__(self, size: Size, virtual_size: Size):
226
"""
227
Initialize a resize event.
228
229
Parameters:
230
- size: New size of the widget/screen
231
- virtual_size: Virtual size including scrollable area
232
"""
233
234
# Properties
235
size: Size
236
virtual_size: Size
237
238
class Idle(Event):
239
"""Application idle event (no recent input)."""
240
pass
241
242
class ExitApp(Event):
243
"""Application exit requested event."""
244
245
def __init__(self, result: Any = None):
246
"""
247
Initialize an exit event.
248
249
Parameters:
250
- result: Exit result value
251
"""
252
253
# Properties
254
result: Any
255
256
class Callback(Event):
257
"""Callback function invocation event."""
258
259
def __init__(self, callback: Callable, *args, **kwargs):
260
"""
261
Initialize a callback event.
262
263
Parameters:
264
- callback: Function to call
265
- *args: Positional arguments
266
- **kwargs: Keyword arguments
267
"""
268
269
class Timer(Event):
270
"""Timer callback event."""
271
272
def __init__(self, timer: Timer, time: float):
273
"""
274
Initialize a timer event.
275
276
Parameters:
277
- timer: The timer instance
278
- time: Current time
279
"""
280
```
281
282
### Custom Widget Events
283
284
Common event patterns used by built-in widgets.
285
286
```python { .api }
287
class Changed(Event):
288
"""Generic value changed event."""
289
290
def __init__(self, widget: Widget, value: Any):
291
"""
292
Initialize a changed event.
293
294
Parameters:
295
- widget: Widget that changed
296
- value: New value
297
"""
298
299
# Properties
300
widget: Widget
301
value: Any
302
303
class Pressed(Event):
304
"""Generic button/item pressed event."""
305
306
def __init__(self, widget: Widget):
307
"""
308
Initialize a pressed event.
309
310
Parameters:
311
- widget: Widget that was pressed
312
"""
313
314
class Selected(Event):
315
"""Generic selection event."""
316
317
def __init__(self, widget: Widget, item: Any):
318
"""
319
Initialize a selected event.
320
321
Parameters:
322
- widget: Widget with selection
323
- item: Selected item
324
"""
325
326
class Toggled(Event):
327
"""Generic toggle state changed event."""
328
329
def __init__(self, widget: Widget, toggled: bool):
330
"""
331
Initialize a toggled event.
332
333
Parameters:
334
- widget: Widget that was toggled
335
- toggled: New toggle state
336
"""
337
```
338
339
## Usage Examples
340
341
### Basic Event Handling
342
343
```python
344
from textual.app import App
345
from textual.widgets import Button, Input
346
from textual.events import Key
347
348
class EventApp(App):
349
def compose(self):
350
yield Input(placeholder="Type here...", id="input")
351
yield Button("Click me!", id="button")
352
353
def on_key(self, event: Key):
354
"""Handle any key press."""
355
self.log(f"Key pressed: {event.key}")
356
357
if event.key == "ctrl+q":
358
self.exit()
359
360
def on_button_pressed(self, event: Button.Pressed):
361
"""Handle button presses."""
362
self.log(f"Button {event.button.id} was pressed!")
363
364
def on_input_changed(self, event: Input.Changed):
365
"""Handle input value changes."""
366
self.log(f"Input value: {event.value}")
367
368
def on_input_submitted(self, event: Input.Submitted):
369
"""Handle input submission (Enter key)."""
370
self.log(f"Input submitted: {event.value}")
371
```
372
373
### Event Handling with Decorators
374
375
```python
376
from textual.app import App
377
from textual.widgets import Button, Input
378
from textual import on
379
380
class DecoratorApp(App):
381
def compose(self):
382
yield Input(id="name-input")
383
yield Button("Submit", id="submit-btn")
384
yield Button("Clear", id="clear-btn")
385
386
@on(Button.Pressed, "#submit-btn")
387
def handle_submit(self, event: Button.Pressed):
388
"""Handle submit button with CSS selector."""
389
input_widget = self.query_one("#name-input", Input)
390
self.log(f"Submitting: {input_widget.value}")
391
392
@on(Button.Pressed, "#clear-btn")
393
def handle_clear(self, event: Button.Pressed):
394
"""Handle clear button."""
395
input_widget = self.query_one("#name-input", Input)
396
input_widget.value = ""
397
398
@on(Input.Changed, "#name-input")
399
def handle_name_change(self, event: Input.Changed):
400
"""Handle name input changes."""
401
submit_btn = self.query_one("#submit-btn", Button)
402
submit_btn.disabled = len(event.value.strip()) == 0
403
```
404
405
### Custom Event Creation
406
407
```python
408
from textual.app import App
409
from textual.widget import Widget
410
from textual.message import Message
411
from textual.widgets import Button
412
413
class CustomWidget(Widget):
414
"""A widget that sends custom events."""
415
416
class DataReady(Message):
417
"""Custom event sent when data is ready."""
418
419
def __init__(self, widget: Widget, data: dict):
420
super().__init__()
421
self.widget = widget
422
self.data = data
423
424
def compose(self):
425
yield Button("Load Data", id="load")
426
427
def on_button_pressed(self, event: Button.Pressed):
428
"""Handle button press and send custom event."""
429
if event.button.id == "load":
430
# Simulate data loading
431
data = {"result": "success", "items": [1, 2, 3]}
432
433
# Send custom event
434
self.post_message(self.DataReady(self, data))
435
436
class CustomEventApp(App):
437
def compose(self):
438
yield CustomWidget(id="custom")
439
440
def on_custom_widget_data_ready(self, event: CustomWidget.DataReady):
441
"""Handle custom data ready event."""
442
self.log(f"Data received: {event.data}")
443
```
444
445
### Mouse Event Handling
446
447
```python
448
from textual.app import App
449
from textual.widgets import Static
450
from textual.events import MouseDown, MouseUp, MouseMove
451
452
class MouseApp(App):
453
def compose(self):
454
yield Static("Click and drag on me!", id="target")
455
456
def on_mouse_down(self, event: MouseDown):
457
"""Handle mouse button press."""
458
self.log(f"Mouse down at ({event.x}, {event.y}), button {event.button}")
459
460
def on_mouse_up(self, event: MouseUp):
461
"""Handle mouse button release."""
462
self.log(f"Mouse up at ({event.x}, {event.y})")
463
464
def on_mouse_move(self, event: MouseMove):
465
"""Handle mouse movement."""
466
# Only log occasionally to avoid spam
467
if event.x % 5 == 0 and event.y % 5 == 0:
468
self.log(f"Mouse at ({event.x}, {event.y})")
469
```
470
471
### Event Prevention and Stopping
472
473
```python
474
from textual.app import App
475
from textual.widgets import Input
476
from textual.events import Key
477
478
class PreventionApp(App):
479
def compose(self):
480
yield Input(placeholder="Try typing numbers...", id="text-only")
481
482
def on_key(self, event: Key):
483
"""Prevent numeric input in text field."""
484
# Check if the focused widget is our text-only input
485
focused = self.focused
486
if focused and focused.id == "text-only":
487
# Prevent numeric characters
488
if event.character and event.character.isdigit():
489
event.prevent_default() # Stop default handling
490
event.stop() # Stop event propagation
491
self.log("Numeric input blocked!")
492
```