diff --git a/docs/examples/messages_and_events/beep.py b/docs/examples/messages_and_events/beep.py new file mode 100644 index 000000000..6dfa99a6a --- /dev/null +++ b/docs/examples/messages_and_events/beep.py @@ -0,0 +1,9 @@ +from textual.app import App + + +class Beeper(App): + async def on_key(self, event): + self.console.bell() + + +Beeper.run() diff --git a/docs/examples/messages_and_events/beep_typed.py b/docs/examples/messages_and_events/beep_typed.py new file mode 100644 index 000000000..179965378 --- /dev/null +++ b/docs/examples/messages_and_events/beep_typed.py @@ -0,0 +1,10 @@ +from textual.app import App +from textual import events + + +class Beeper(App): + async def on_key(self, event: events.Key) -> None: + self.console.bell() + + +Beeper.run() diff --git a/docs/examples/messages_and_events/color_changer.py b/docs/examples/messages_and_events/color_changer.py new file mode 100644 index 000000000..a3d4e6b84 --- /dev/null +++ b/docs/examples/messages_and_events/color_changer.py @@ -0,0 +1,10 @@ +from textual.app import App + + +class ColorChanger(App): + async def on_key(self, event): + if event.key.isdigit(): + self.background = f"on color({event.key})" + + +ColorChanger.run() diff --git a/docs/messages.md b/docs/messages.md new file mode 100644 index 000000000..e7eddc28d --- /dev/null +++ b/docs/messages.md @@ -0,0 +1,42 @@ +# Messages & Events + +Each component of a Textual application has it its heart a queue of messages and a task which monitors this queue and calls Python code in response. The queue and task are collectively known as a _message pump_. + +You will most often deal with _events_ which are a particular type of message that are created in response to user actions, such as key presses and mouse clicks, but also internal events such as timers. These events typically originate from a Driver class which sends them to an App class which is where you write code to respond to those events. + +Lets write an _app_ which responds to a key event. This is probably the simplest Textual application that I can conceive of: + +```python +from textual.app import App + + +class Beeper(App): + async def on_key(self, event): + self.console.bell() + + +Beeper.run() +``` + +If you run the above code, Textual will switch the terminal in to _application mode_. The terminal will go blank and the app will start processing events. If you hit any key you should hear a beep. Hit ctrl+C (control key and C key at the same time) to exit application mode and return to the terminal. + +Although simple, this app follows the same pattern as more sophisticated applications. It starts by deriving a class from `App`; in this case `Beeper`. Calling the classmethod `run()` starts the application. + +In our Beeper class there is a single event handler `on_key` which is called in response to a `Key` event. The method name is assumed by concatenating `on_` with the event name, hence `on_key` for a Key event, `on_timer` for a Timer event, etc. In Beeper, the on_key event calls `self.console.bell()` which is what plays the beep noise (if supported by your terminal). + +The `on_key` method is preceded by the keyword `async` making it an asynchronous method. Textual is an asynchronous framework so event handlers and most methods are async. + +Out Beeper app is missing typing information. Although completely optional, I recommend adding typing information which will help catch bugs (using tools such as [Mypy](https://mypy.readthedocs.io/en/stable/)). Here is the Beeper class with added typing: + +```python +from textual.app import App +from textual import events + + +class Beeper(App): + async def on_key(self, event: events.Key) -> None: + self.console.bell() + + +Beeper.run() +``` diff --git a/docs/reference/events.md b/docs/reference/events.md new file mode 100644 index 000000000..0be7c2868 --- /dev/null +++ b/docs/reference/events.md @@ -0,0 +1 @@ +::: textual.events diff --git a/docs/stylesheets/custom.css b/docs/stylesheets/custom.css new file mode 100644 index 000000000..0ccc3b2eb --- /dev/null +++ b/docs/stylesheets/custom.css @@ -0,0 +1,14 @@ +h2.doc.doc-heading { + border-top: 1px solid rgba(0, 0, 0, 0.1); + padding-top: 48px; +} + +h3 .doc-heading code { + font-size: 16px; +} + +.doc-heading code { + font-weight: normal; + font-family: "Roboto Mono", "SFMono-Regular", Consolas, "Courier New", Courier, + monospace; +} diff --git a/examples/animation.py b/examples/animation.py new file mode 100644 index 000000000..3d06188e2 --- /dev/null +++ b/examples/animation.py @@ -0,0 +1,41 @@ +from textual import events +from textual.app import App +from textual.reactive import Reactive +from textual.views import DockView +from textual.widgets import Footer, Placeholder + + +class SmoothApp(App): + """Demonstrates smooth animation""" + + async def on_load(self, event: events.Load) -> None: + await self.bind("q,ctrl+c", "quit") + await self.bind("x", "bang") + await self.bind("b", "toggle_sidebar") + + show_bar: Reactive[bool] = Reactive(False) + + async def watch_show_bar(self, show_bar: bool) -> None: + self.animator.animate(self.bar, "layout_offset_x", 0 if show_bar else -40) + + async def action_toggle_sidebar(self) -> None: + self.show_bar = not self.show_bar + + async def on_startup(self, event: events.Startup) -> None: + + view = await self.push_view(DockView()) + + footer = Footer() + self.bar = Placeholder(name="left") + self.bar.layout_offset_x = -40 + + footer.add_key("b", "Toggle sidebar") + footer.add_key("q", "Quit") + + await view.dock(footer, edge="bottom") + await view.dock(self.bar, edge="left", size=40, z=1) + + await view.dock(Placeholder(), Placeholder(), edge="top") + + +SmoothApp.run() \ No newline at end of file