mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
@@ -76,30 +76,30 @@ Textual knows to *await* your event handlers if they are coroutines (i.e. prefix
|
|||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
For a friendly introduction to async programming in Python, see FastAPI's [concurrent burgers](https://fastapi.tiangolo.com/async/) article.
|
For a friendly introduction to async programming in Python, see FastAPI's [concurrent burgers](https://fastapi.tiangolo.com/async/) article.
|
||||||
|
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
|
|
||||||
Widgets are self-contained components responsible for generating the output for a portion of the screen. Widgets respond to events in much the same way as the App. Most apps that do anything interesting will contain at least one (and probably many) widgets which together form a User Interface.
|
Widgets are self-contained components responsible for generating the output for a portion of the screen. Widgets respond to events in much the same way as the App. Most apps that do anything interesting will contain at least one (and probably many) widgets which together form a User Interface.
|
||||||
|
|
||||||
Widgets can be as simple as a piece of text, a button, or a fully-fledge component like a text editor or file browser (which may contain widgets of their own).
|
Widgets can be as simple as a piece of text, a button, or a fully-fledged component like a text editor or file browser (which may contain widgets of their own).
|
||||||
|
|
||||||
### Composing
|
### Composing
|
||||||
|
|
||||||
To add widgets to your app implement a [`compose()`][textual.app.App.compose] method which should return an iterable of Widget instances. A list would work, but it is convenient to yield widgets, making the method a *generator*.
|
To add widgets to your app implement a [`compose()`][textual.app.App.compose] method which should return an iterable of `Widget` instances. A list would work, but it is convenient to yield widgets, making the method a *generator*.
|
||||||
|
|
||||||
The following example imports a builtin Welcome widget and yields it from `App.compose()`.
|
The following example imports a builtin `Welcome` widget and yields it from `App.compose()`.
|
||||||
|
|
||||||
```python title="widgets01.py"
|
```python title="widgets01.py"
|
||||||
--8<-- "docs/examples/app/widgets01.py"
|
--8<-- "docs/examples/app/widgets01.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
When you run this code, Textual will *mount* the Welcome widget which contains Markdown content and a button:
|
When you run this code, Textual will *mount* the `Welcome` widget which contains Markdown content and a button:
|
||||||
|
|
||||||
```{.textual path="docs/examples/app/widgets01.py"}
|
```{.textual path="docs/examples/app/widgets01.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notice the `on_button_pressed` method which handles the [Button.Pressed][textual.widgets.Button] event sent by a button contained in the Welcome widget. The handler calls [App.exit()][textual.app.App.exit] to exit the app.
|
Notice the `on_button_pressed` method which handles the [Button.Pressed][textual.widgets.Button] event sent by a button contained in the `Welcome` widget. The handler calls [App.exit()][textual.app.App.exit] to exit the app.
|
||||||
|
|
||||||
### Mounting
|
### Mounting
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ You may have noticed that we subclassed `App[str]` rather than the usual `App`.
|
|||||||
--8<-- "docs/examples/app/question01.py"
|
--8<-- "docs/examples/app/question01.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
The addition of `[str]` tells Mypy that `run()` is expected to return a string. It may also return `None` if [App.exit()][textual.app.App.exit] is called without a return value, so the return type of `run` will be `str | None`. Replace the `str` in `[str]` with the type of the value you intend to call the exit method with.
|
The addition of `[str]` tells mypy that `run()` is expected to return a string. It may also return `None` if [App.exit()][textual.app.App.exit] is called without a return value, so the return type of `run` will be `str | None`. Replace the `str` in `[str]` with the type of the value you intend to call the exit method with.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ The addition of `[str]` tells Mypy that `run()` is expected to return a string.
|
|||||||
|
|
||||||
Textual apps can reference [CSS](CSS.md) files which define how your app and widgets will look, while keeping your Python code free of display related code (which tends to be messy).
|
Textual apps can reference [CSS](CSS.md) files which define how your app and widgets will look, while keeping your Python code free of display related code (which tends to be messy).
|
||||||
|
|
||||||
The chapter on [Textual CSS](CSS.md) describes how to use CSS in detail. For now lets look at how your app references external CSS files.
|
The chapter on [Textual CSS](CSS.md) describes how to use CSS in detail. For now let's look at how your app references external CSS files.
|
||||||
|
|
||||||
The following example enables loading of CSS by adding a `CSS_PATH` class variable:
|
The following example enables loading of CSS by adding a `CSS_PATH` class variable:
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ When `"question02.py"` runs it will load `"question02.css"` and update the app a
|
|||||||
|
|
||||||
### Classvar CSS
|
### Classvar CSS
|
||||||
|
|
||||||
While external CSS files are recommended for most applications, and enable some cool features like *live editing*, you can also specify the CSS directly within the Python code.
|
While external CSS files are recommended for most applications, and enable some cool features like *live editing*, you can also specify the CSS directly within the Python code.
|
||||||
|
|
||||||
To do this set a `CSS` class variable on the app to a string containing your CSS.
|
To do this set a `CSS` class variable on the app to a string containing your CSS.
|
||||||
|
|
||||||
@@ -184,4 +184,4 @@ Here's the question app with classvar CSS:
|
|||||||
|
|
||||||
## What's next
|
## What's next
|
||||||
|
|
||||||
In the following chapter we will learn more about how to apply styles to you widgets and app.
|
In the following chapter we will learn more about how to apply styles to your widgets and app.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
!!! note inline end
|
!!! note inline end
|
||||||
|
|
||||||
If you don't have the `textual` command on your path, you may have forgotten so install with the `dev` switch.
|
If you don't have the `textual` command on your path, you may have forgotten so install with the `dev` switch.
|
||||||
|
|
||||||
See [getting started](../getting_started.md#installation) for details.
|
See [getting started](../getting_started.md#installation) for details.
|
||||||
|
|
||||||
Textual comes with a command line application of the same name. The `textual` command is a super useful tool that will help you to build apps.
|
Textual comes with a command line application of the same name. The `textual` command is a super useful tool that will help you to build apps.
|
||||||
@@ -23,7 +23,9 @@ You can run Textual apps with the `run` subcommand. If you supply a path to a Py
|
|||||||
textual run my_app.py
|
textual run my_app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The `run` sub-command assumes you have an App instance called `app` in the global scope of your Python file. If the application is called something different, you can specify it with a colon following the filename:
|
The `run` sub-command will first look for a `App` instance called `app` in the global scope of your Python file. If there is no `app`, it will create an instance of the first `App` class it finds and run that.
|
||||||
|
|
||||||
|
Alternatively, you can add the name of an `App` instance or class after a colon to run a specific app in the Python file. Here's an example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
textual run my_app.py:alternative_app
|
textual run my_app.py:alternative_app
|
||||||
@@ -44,7 +46,7 @@ textual run --dev my_app.py
|
|||||||
|
|
||||||
One of the the features of *dev* mode is live editing of CSS files: any changes to your CSS will be reflected in the terminal a few milliseconds later.
|
One of the the features of *dev* mode is live editing of CSS files: any changes to your CSS will be reflected in the terminal a few milliseconds later.
|
||||||
|
|
||||||
This is a great feature for iterating on your app's look and feel. Open the CSS in your editor and have your app running in a terminal. Edits to your CSS will appear almost immediately after you save.
|
This is a great feature for iterating on your app's look and feel. Open the CSS in your editor and have your app running in a terminal. Edits to your CSS will appear almost immediately after you save.
|
||||||
|
|
||||||
## Console
|
## Console
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ log("[bold red]DANGER![/] We're having too much fun")
|
|||||||
|
|
||||||
### Log method
|
### Log method
|
||||||
|
|
||||||
There's a convenient shortcut to `log` available on the App and Widget objects. This is useful in event handlers. Here's an example:
|
There's a convenient shortcut to `log` available on the `App` and `Widget` objects. This is useful in event handlers. Here's an example:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from textual.app import App
|
from textual.app import App
|
||||||
@@ -120,4 +122,3 @@ if __name__ == "__main__":
|
|||||||
LogApp.run()
|
LogApp.run()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
---
|
---
|
||||||
hide:
|
hide:
|
||||||
- navigation
|
- navigation
|
||||||
---
|
---
|
||||||
|
|
||||||
# Tutorial
|
# Tutorial
|
||||||
|
|
||||||
Welcome to the Textual Tutorial!
|
Welcome to the Textual Tutorial!
|
||||||
|
|
||||||
By the end of this page you should have a solid understanding of app development with Textual.
|
By the end of this page you should have a solid understanding of app development with Textual.
|
||||||
|
|
||||||
!!! quote
|
!!! quote
|
||||||
|
|
||||||
If you want people to build things, make it fun.
|
If you want people to build things, make it fun.
|
||||||
|
|
||||||
— **Will McGugan** (creator of Rich and Textual)
|
— **Will McGugan** (creator of Rich and Textual)
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ python stopwatch.py
|
|||||||
|
|
||||||
Type hints are entirely optional in Textual. We've included them in the example code but it's up to you whether you add them to your own projects.
|
Type hints are entirely optional in Textual. We've included them in the example code but it's up to you whether you add them to your own projects.
|
||||||
|
|
||||||
We're a big fan of Python type hints at Textualize. If you haven't encountered type hinting, it's a way to express the types of your data, parameters, and return values. Type hinting allows tools like [Mypy](https://mypy.readthedocs.io/en/stable/) to catch bugs before your code runs.
|
We're a big fan of Python type hints at Textualize. If you haven't encountered type hinting, it's a way to express the types of your data, parameters, and return values. Type hinting allows tools like [mypy](https://mypy.readthedocs.io/en/stable/) to catch bugs before your code runs.
|
||||||
|
|
||||||
The following function contains type hints:
|
The following function contains type hints:
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ Return types follow `->`. So `-> str:` indicates this method returns a string.
|
|||||||
|
|
||||||
The first step in building a Textual app is to import and extend the `App` class. Here's a basic app class we will use as a starting point for the stopwatch app.
|
The first step in building a Textual app is to import and extend the `App` class. Here's a basic app class we will use as a starting point for the stopwatch app.
|
||||||
|
|
||||||
```python title="stopwatch01.py"
|
```python title="stopwatch01.py"
|
||||||
--8<-- "docs/examples/tutorial/stopwatch01.py"
|
--8<-- "docs/examples/tutorial/stopwatch01.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ Hit ++ctrl+c++ to exit the app and return to the command prompt.
|
|||||||
|
|
||||||
### A closer look at the App class
|
### A closer look at the App class
|
||||||
|
|
||||||
Let's examine stopwatch01.py in more detail.
|
Let's examine `stopwatch01.py` in more detail.
|
||||||
|
|
||||||
```python title="stopwatch01.py" hl_lines="1 2"
|
```python title="stopwatch01.py" hl_lines="1 2"
|
||||||
--8<-- "docs/examples/tutorial/stopwatch01.py"
|
--8<-- "docs/examples/tutorial/stopwatch01.py"
|
||||||
@@ -134,7 +134,7 @@ The final three lines create an instance of the app and calls the [run()][textua
|
|||||||
|
|
||||||
## Designing a UI with widgets
|
## Designing a UI with widgets
|
||||||
|
|
||||||
Textual comes with a number of builtin widgets, like Header and Footer, which are versatile and re-usable. We will need to build some custom widgets for the stopwatch. Before we dive in to that, let's first sketch a design for the app — so we know what we're aiming for.
|
Textual comes with a number of builtin widgets, like Header and Footer, which are versatile and re-usable. We will need to build some custom widgets for the stopwatch. Before we dive in to that, let's first sketch a design for the app — so we know what we're aiming for.
|
||||||
|
|
||||||
<div class="excalidraw">
|
<div class="excalidraw">
|
||||||
--8<-- "docs/images/stopwatch.excalidraw.svg"
|
--8<-- "docs/images/stopwatch.excalidraw.svg"
|
||||||
@@ -157,9 +157,9 @@ Let's add those to the app. Just a skeleton for now, we will add the rest of the
|
|||||||
--8<-- "docs/examples/tutorial/stopwatch02.py"
|
--8<-- "docs/examples/tutorial/stopwatch02.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
We've imported two new widgets in this code: `Button`, which creates a clickable button, and `Static` which is a base class for a simple control. We've also imported `Container` from `textual.containers` which (as the name suggests) is a Widget which contains other widgets.
|
We've imported two new widgets in this code: `Button`, which creates a clickable button, and `Static` which is a base class for a simple control. We've also imported `Container` from `textual.containers` which (as the name suggests) is a `Widget` which contains other widgets.
|
||||||
|
|
||||||
We've defined an empty `TimeDisplay` widget by extending `Static`. We will flesh this out later.
|
We've defined an empty `TimeDisplay` widget by extending `Static`. We will flesh this out later.
|
||||||
|
|
||||||
The Stopwatch widget class also extends `Static`. This class has a `compose()` method which yields child widgets, consisting of three `Button` objects and a single `TimeDisplay` object. These widgets will form the stopwatch in our sketch.
|
The Stopwatch widget class also extends `Static`. This class has a `compose()` method which yields child widgets, consisting of three `Button` objects and a single `TimeDisplay` object. These widgets will form the stopwatch in our sketch.
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ The Stopwatch widget class also extends `Static`. This class has a `compose()` m
|
|||||||
The Button constructor takes a label to be displayed in the button (`"Start"`, `"Stop"`, or `"Reset"`). Additionally some of the buttons set the following parameters:
|
The Button constructor takes a label to be displayed in the button (`"Start"`, `"Stop"`, or `"Reset"`). Additionally some of the buttons set the following parameters:
|
||||||
|
|
||||||
- `id` is an identifier we can use to tell the buttons apart in code and apply styles. More on that later.
|
- `id` is an identifier we can use to tell the buttons apart in code and apply styles. More on that later.
|
||||||
- `variant` is a string which selects a default style. The "success" variant makes the button green, and the "error" variant makes it red.
|
- `variant` is a string which selects a default style. The "success" variant makes the button green, and the "error" variant makes it red.
|
||||||
|
|
||||||
### Composing the widgets
|
### Composing the widgets
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ The new line in `Stopwatch.compose()` yields a single `Container` object which w
|
|||||||
|
|
||||||
### The unstyled app
|
### The unstyled app
|
||||||
|
|
||||||
Let's see what happens when we run "stopwatch02.py".
|
Let's see what happens when we run `stopwatch02.py`.
|
||||||
|
|
||||||
```{.textual path="docs/examples/tutorial/stopwatch02.py" title="stopwatch02.py"}
|
```{.textual path="docs/examples/tutorial/stopwatch02.py" title="stopwatch02.py"}
|
||||||
```
|
```
|
||||||
@@ -194,8 +194,8 @@ Every widget has a `styles` object with a number of attributes that impact how t
|
|||||||
self.styles.background = "blue"
|
self.styles.background = "blue"
|
||||||
self.styles.color = "white"
|
self.styles.color = "white"
|
||||||
```
|
```
|
||||||
|
|
||||||
While it's possible to set all styles for an app this way, it is rarely necessary. Textual has support for CSS (Cascading Style Sheets), a technology used by web browsers. CSS files are data files loaded by your app which contain information about styles to apply to your widgets.
|
While it's possible to set all styles for an app this way, it is rarely necessary. Textual has support for CSS (Cascading Style Sheets), a technology used by web browsers. CSS files are data files loaded by your app which contain information about styles to apply to your widgets.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ Let's add a CSS file to our application.
|
|||||||
|
|
||||||
Adding the `CSS_PATH` class variable tells Textual to load the following file when the app starts:
|
Adding the `CSS_PATH` class variable tells Textual to load the following file when the app starts:
|
||||||
|
|
||||||
```sass title="stopwatch03.css"
|
```sass title="stopwatch03.css"
|
||||||
--8<-- "docs/examples/tutorial/stopwatch03.css"
|
--8<-- "docs/examples/tutorial/stopwatch03.css"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -222,13 +222,13 @@ If we run the app now, it will look *very* different.
|
|||||||
```{.textual path="docs/examples/tutorial/stopwatch03.py" title="stopwatch03.py"}
|
```{.textual path="docs/examples/tutorial/stopwatch03.py" title="stopwatch03.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
This app looks much more like our sketch. let's look at how the Textual uses `stopwatch03.css` to apply styles.
|
This app looks much more like our sketch. Let's look at how the Textual uses `stopwatch03.css` to apply styles.
|
||||||
|
|
||||||
### CSS basics
|
### CSS basics
|
||||||
|
|
||||||
CSS files contain a number of _declaration blocks_. Here's the first such block from `stopwatch03.css` again:
|
CSS files contain a number of _declaration blocks_. Here's the first such block from `stopwatch03.css` again:
|
||||||
|
|
||||||
```sass
|
```sass
|
||||||
Stopwatch {
|
Stopwatch {
|
||||||
layout: horizontal;
|
layout: horizontal;
|
||||||
background: $boost;
|
background: $boost;
|
||||||
@@ -250,7 +250,7 @@ Here's how this CSS code changes how the `Stopwatch` widget is displayed.
|
|||||||
- `background: $boost` sets the background color to `$boost`. The `$` prefix picks a pre-defined color from the builtin theme. There are other ways to specify colors such as `"blue"` or `rgb(20,46,210)`.
|
- `background: $boost` sets the background color to `$boost`. The `$` prefix picks a pre-defined color from the builtin theme. There are other ways to specify colors such as `"blue"` or `rgb(20,46,210)`.
|
||||||
- `height: 5` sets the height of our widget to 5 lines of text.
|
- `height: 5` sets the height of our widget to 5 lines of text.
|
||||||
- `padding: 1` sets a padding of 1 cell around the child widgets.
|
- `padding: 1` sets a padding of 1 cell around the child widgets.
|
||||||
- `margin: 1` sets a margin of 1 cell around the Stopwatch widget to create a little space between widgets in the list.
|
- `margin: 1` sets a margin of 1 cell around the `Stopwatch` widget to create a little space between widgets in the list.
|
||||||
|
|
||||||
|
|
||||||
Here's the rest of `stopwatch03.css` which contains further declaration blocks:
|
Here's the rest of `stopwatch03.css` which contains further declaration blocks:
|
||||||
@@ -263,7 +263,7 @@ TimeDisplay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
width: 16;
|
width: 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
#start {
|
#start {
|
||||||
@@ -284,7 +284,7 @@ The `TimeDisplay` block aligns text to the center (`content-align`), fades it sl
|
|||||||
|
|
||||||
The `Button` block sets the width (`width`) of buttons to 16 cells (character widths).
|
The `Button` block sets the width (`width`) of buttons to 16 cells (character widths).
|
||||||
|
|
||||||
The last 3 blocks have a slightly different format. When the declaration begins with a `#` then the styles will be applied to widgets with a matching "id" attribute. We've set an ID on the Button widgets we yielded in compose. For instance the first button has `id="start"` which matches `#start` in the CSS.
|
The last 3 blocks have a slightly different format. When the declaration begins with a `#` then the styles will be applied to widgets with a matching "id" attribute. We've set an ID on the `Button` widgets we yielded in `compose`. For instance the first button has `id="start"` which matches `#start` in the CSS.
|
||||||
|
|
||||||
The buttons have a `dock` style which aligns the widget to a given edge. The start and stop buttons are docked to the left edge, while the reset button is docked to the right edge.
|
The buttons have a `dock` style which aligns the widget to a given edge. The start and stop buttons are docked to the left edge, while the reset button is docked to the right edge.
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ You may have noticed that the stop button (`#stop` in the CSS) has `display: non
|
|||||||
|
|
||||||
### Dynamic CSS
|
### Dynamic CSS
|
||||||
|
|
||||||
We want our Stopwatch widget to have two states: a default state with a Start and Reset button; and a _started_ state with a Stop button. When a stopwatch is started it should also have a green background and bold text.
|
We want our `Stopwatch` widget to have two states: a default state with a Start and Reset button; and a _started_ state with a Stop button. When a stopwatch is started it should also have a green background and bold text.
|
||||||
|
|
||||||
<div class="excalidraw">
|
<div class="excalidraw">
|
||||||
--8<-- "docs/images/css_stopwatch.excalidraw.svg"
|
--8<-- "docs/images/css_stopwatch.excalidraw.svg"
|
||||||
@@ -317,7 +317,7 @@ Some of the new styles have more than one selector separated by a space. The spa
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `.started` selector matches any widget with a `"started"` CSS class. While `#start` matches a child widget with an ID of "start". So it matches the Start button only for Stopwatches in a started state.
|
The `.started` selector matches any widget with a `"started"` CSS class. While `#start` matches a child widget with an ID of `"start"`. So it matches the Start button only for Stopwatches in a started state.
|
||||||
|
|
||||||
The rule is `"display: none"` which tells Textual to hide the button.
|
The rule is `"display: none"` which tells Textual to hide the button.
|
||||||
|
|
||||||
@@ -335,7 +335,7 @@ The following code will start or stop the stopwatches in response to clicking a
|
|||||||
|
|
||||||
The `on_button_pressed` method is an *event handler*. Event handlers are methods called by Textual in response to an *event* such as a key press, mouse click, etc. Event handlers begin with `on_` followed by the name of the event they will handler. Hence `on_button_pressed` will handle the button pressed event.
|
The `on_button_pressed` method is an *event handler*. Event handlers are methods called by Textual in response to an *event* such as a key press, mouse click, etc. Event handlers begin with `on_` followed by the name of the event they will handler. Hence `on_button_pressed` will handle the button pressed event.
|
||||||
|
|
||||||
If you run "stopwatch04.py" now you will be able to toggle between the two states by clicking the first button:
|
If you run `stopwatch04.py` now you will be able to toggle between the two states by clicking the first button:
|
||||||
|
|
||||||
```{.textual path="docs/examples/tutorial/stopwatch04.py" title="stopwatch04.py" press="tab,tab,tab,_,enter,_,_,_"}
|
```{.textual path="docs/examples/tutorial/stopwatch04.py" title="stopwatch04.py" press="tab,tab,tab,_,enter,_,_,_"}
|
||||||
```
|
```
|
||||||
@@ -350,11 +350,11 @@ You can declare a reactive attribute with [reactive][textual.reactive.reactive].
|
|||||||
--8<-- "docs/examples/tutorial/stopwatch05.py"
|
--8<-- "docs/examples/tutorial/stopwatch05.py"
|
||||||
```
|
```
|
||||||
|
|
||||||
We have added two reactive attributes: `start_time` will contain the time in seconds when the stopwatch was started, and `time` will contain the time to be displayed on the Stopwatch.
|
We have added two reactive attributes: `start_time` will contain the time in seconds when the stopwatch was started, and `time` will contain the time to be displayed on the `Stopwatch`.
|
||||||
|
|
||||||
Both attributes will be available on `self` as if you had assigned them in `__init__`. If you write to either of these attributes the widget will update automatically.
|
Both attributes will be available on `self` as if you had assigned them in `__init__`. If you write to either of these attributes the widget will update automatically.
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
The `monotonic` function in this example is imported from the standard library `time` module. It is similar to `time.time` but won't go backwards if the system clock is changed.
|
The `monotonic` function in this example is imported from the standard library `time` module. It is similar to `time.time` but won't go backwards if the system clock is changed.
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ The end result is that the `Stopwatch` widgets show the time elapsed since the w
|
|||||||
```{.textual path="docs/examples/tutorial/stopwatch05.py" title="stopwatch05.py"}
|
```{.textual path="docs/examples/tutorial/stopwatch05.py" title="stopwatch05.py"}
|
||||||
```
|
```
|
||||||
|
|
||||||
We've seen how we can update widgets with a timer, but we still need to wire up the buttons so we can operate Stopwatches independently.
|
We've seen how we can update widgets with a timer, but we still need to wire up the buttons so we can operate stopwatches independently.
|
||||||
|
|
||||||
### Wiring buttons
|
### Wiring buttons
|
||||||
|
|
||||||
@@ -414,9 +414,9 @@ This code supplies missing features and makes our app useful. We've made the fol
|
|||||||
- The first line retrieves `id` attribute of the button that was pressed. We can use this to decide what to do in response.
|
- The first line retrieves `id` attribute of the button that was pressed. We can use this to decide what to do in response.
|
||||||
- The second line calls `query_one` to get a reference to the `TimeDisplay` widget.
|
- The second line calls `query_one` to get a reference to the `TimeDisplay` widget.
|
||||||
- We call the method on `TimeDisplay` that matches the pressed button.
|
- We call the method on `TimeDisplay` that matches the pressed button.
|
||||||
- We add the "started" class when the Stopwatch is started (`self.add_class("started)`), and remove it (`self.remove_class("started")`) when it is stopped. This will update the Stopwatch visuals via CSS.
|
- We add the `"started"` class when the Stopwatch is started (`self.add_class("started")`), and remove it (`self.remove_class("started")`) when it is stopped. This will update the Stopwatch visuals via CSS.
|
||||||
|
|
||||||
If you run stopwatch06.py you will be able to use the stopwatches independently.
|
If you run `stopwatch06.py` you will be able to use the stopwatches independently.
|
||||||
|
|
||||||
```{.textual path="docs/examples/tutorial/stopwatch06.py" title="stopwatch06.py" press="tab,enter,_,_,tab,enter,_,tab"}
|
```{.textual path="docs/examples/tutorial/stopwatch06.py" title="stopwatch06.py" press="tab,enter,_,_,tab,enter,_,tab"}
|
||||||
```
|
```
|
||||||
@@ -435,12 +435,12 @@ Let's use these methods to implement adding and removing stopwatches to our app.
|
|||||||
|
|
||||||
Here's a summary of the changes:
|
Here's a summary of the changes:
|
||||||
|
|
||||||
- The Container object in StopWatchApp grew a "timers" ID.
|
- The `Container` object in `StopWatchApp` grew a `"timers"` ID.
|
||||||
- Added `action_add_stopwatch` to add a new stopwatch.
|
- Added `action_add_stopwatch` to add a new stopwatch.
|
||||||
- Added `action_remove_stopwatch` to remove a stopwatch.
|
- Added `action_remove_stopwatch` to remove a stopwatch.
|
||||||
- Added keybindings for the actions.
|
- Added keybindings for the actions.
|
||||||
|
|
||||||
The `action_add_stopwatch` method creates and mounts a new stopwatch. Note the call to [query_one()][textual.dom.DOMNode.query_one] with a CSS selector of `"#timers"` which gets the timer's container via its ID. Once mounted, the new Stopwatch will appear in the terminal. That last line in `action_add_stopwatch` calls [scroll_visible()][textual.widget.Widget.scroll_visible] which will scroll the container to make the new Stopwatch visible (if required).
|
The `action_add_stopwatch` method creates and mounts a new stopwatch. Note the call to [query_one()][textual.dom.DOMNode.query_one] with a CSS selector of `"#timers"` which gets the timer's container via its ID. Once mounted, the new Stopwatch will appear in the terminal. That last line in `action_add_stopwatch` calls [scroll_visible()][textual.widget.Widget.scroll_visible] which will scroll the container to make the new `Stopwatch` visible (if required).
|
||||||
|
|
||||||
The `action_remove_stopwatch` function calls [query()][textual.dom.DOMNode.query] with a CSS selector of `"Stopwatch"` which gets all the `Stopwatch` widgets. If there are stopwatches then the action calls [last()][textual.css.query.DOMQuery.last] to get the last stopwatch, and [remove()][textual.css.query.DOMQuery.remove] to remove it.
|
The `action_remove_stopwatch` function calls [query()][textual.dom.DOMNode.query] with a CSS selector of `"Stopwatch"` which gets all the `Stopwatch` widgets. If there are stopwatches then the action calls [last()][textual.css.query.DOMQuery.last] to get the last stopwatch, and [remove()][textual.css.query.DOMQuery.remove] to remove it.
|
||||||
|
|
||||||
@@ -451,6 +451,6 @@ If you run `stopwatch.py` now you can add a new stopwatch with the ++a++ key and
|
|||||||
|
|
||||||
## What next?
|
## What next?
|
||||||
|
|
||||||
Congratulations on building your first Textual application! This tutorial has covered a lot of ground. If you are the type that prefers to learn a framework by coding, feel free. You could tweak stopwatch.py or look through the examples.
|
Congratulations on building your first Textual application! This tutorial has covered a lot of ground. If you are the type that prefers to learn a framework by coding, feel free. You could tweak `stopwatch.py` or look through the examples.
|
||||||
|
|
||||||
Read the guide for the full details on how to build sophisticated TUI applications with Textual.
|
Read the guide for the full details on how to build sophisticated TUI applications with Textual.
|
||||||
|
|||||||
Reference in New Issue
Block a user