mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' into mount-wait
This commit is contained in:
@@ -82,7 +82,7 @@ With the above example, the DOM will look like the following:
|
||||
This doesn't look much like a tree yet. Let's add a header and a footer to this application, which will create more _branches_ of the tree:
|
||||
|
||||
=== "dom2.py"
|
||||
|
||||
|
||||
```python hl_lines="7 8"
|
||||
--8<-- "docs/examples/guide/dom2.py"
|
||||
```
|
||||
@@ -105,7 +105,7 @@ With a header and a footer widget the DOM looks the this:
|
||||
|
||||
Both Header and Footer are children of the Screen object.
|
||||
|
||||
To further explore the DOM, we're going to build a simple dialog with a question and two buttons. To do this we're going import and use a few more builtin widgets:
|
||||
To further explore the DOM, we're going to build a simple dialog with a question and two buttons. To do this we're going to import and use a few more builtin widgets:
|
||||
|
||||
- `textual.layout.Container` For our top-level dialog.
|
||||
- `textual.layout.Horizontal` To arrange widgets left to right.
|
||||
@@ -187,7 +187,7 @@ Let's look at the selectors supported by Textual CSS.
|
||||
The _type_ selector matches the name of the (Python) class. For example, the following widget can be matched with a `Button` selector:
|
||||
|
||||
```python
|
||||
from textual.widgets import Widget
|
||||
from textual.widgets import Static
|
||||
|
||||
class Button(Static):
|
||||
pass
|
||||
@@ -218,7 +218,7 @@ You may have noticed that the `border` rule exists in both Static and Button. Wh
|
||||
|
||||
### ID selector
|
||||
|
||||
Every Widget can have a single `id` attribute, which is set via the constructor. The ID should be unique to it's container.
|
||||
Every Widget can have a single `id` attribute, which is set via the constructor. The ID should be unique to its container.
|
||||
|
||||
Here's an example of a widget with an ID:
|
||||
|
||||
@@ -280,7 +280,7 @@ Unlike the `id` attribute, a widget's classes can be changed after the widget wa
|
||||
- [add_class()][textual.dom.DOMNode.add_class] Adds one or more classes to a widget.
|
||||
- [remove_class()][textual.dom.DOMNode.remove_class] Removes class name(s) from a widget.
|
||||
- [toggle_class()][textual.dom.DOMNode.toggle_class] Removes a class name if it is present, or adds the name if it's not already present.
|
||||
- [has_class()][textual.dom.DOMNode.has_class] Checks if a class(es) is set on a widget.
|
||||
- [has_class()][textual.dom.DOMNode.has_class] Checks if one or more classes are set on a widget.
|
||||
- [classes][textual.dom.DOMNode.classes] Is a frozen set of the class(es) set on a widget.
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ It is possible that several selectors match a given widget. If the same style is
|
||||
|
||||
- The selector with the most IDs wins. For instance `#next` beats `.button` and `#dialog #next` beats `#next`. If the selectors have the same number of IDs then move to the next rule.
|
||||
|
||||
- The selector with the most class names wins. For instance `.button.success` beats `.success`. For the purposes of specificity, pseudo classes are treated the same as regular class names, so ".button:hover" counts as _2_ class names. If the selectors have the same number of class names then move to the next rule.
|
||||
- The selector with the most class names wins. For instance `.button.success` beats `.success`. For the purposes of specificity, pseudo classes are treated the same as regular class names, so `.button:hover` counts as _2_ class names. If the selectors have the same number of class names then move to the next rule.
|
||||
|
||||
- The selector with the most types wins. For instance `Container Button` beats `Button`.
|
||||
|
||||
@@ -427,4 +427,3 @@ Variables can refer to other variables.
|
||||
Let's say we define a variable `$success: lime;`.
|
||||
Our `$border` variable could then be updated to `$border: wide $success;`, which will
|
||||
be translated to `$border: wide lime;`.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ Action strings have the following format:
|
||||
|
||||
- The name of an action on is own will call the action method with no parameters. For example, an action string of `"bell"` will call `action_bell()`.
|
||||
- Actions may be followed by braces containing Python objects. For example, the action string `set_background("red")` will call `action_set_background("red")`.
|
||||
- Actions may be prefixed with a _namespace_ (see below) follow by a dot.
|
||||
- Actions may be prefixed with a _namespace_ (see below) follow by a dot.
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/actions/format.excalidraw.svg"
|
||||
@@ -50,7 +50,7 @@ Action strings have the following format:
|
||||
|
||||
### Parameters
|
||||
|
||||
If the action strings contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbols.
|
||||
If the action string contains parameters, these must be valid Python literals. Which means you can include numbers, strings, dicts, lists etc. but you can't include variables or references to any other python symbol.
|
||||
|
||||
Consequently `"set_background('blue')"` is a valid action string, but `"set_background(new_color)"` is not — because `new_color` is a variable and not a literal.
|
||||
|
||||
@@ -71,7 +71,7 @@ The following example mounts simple static text with embedded action links.
|
||||
```{.textual path="docs/examples/guide/actions/actions03.py"}
|
||||
```
|
||||
|
||||
When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color and plays the terminals bell.
|
||||
When you click any of the links, Textual runs the `"set_background"` action to change the background to the given color and plays the terminal's bell.
|
||||
|
||||
## Bindings
|
||||
|
||||
@@ -104,7 +104,7 @@ The following example defines a custom widget with its own `set_background` acti
|
||||
|
||||
=== "actions05.css"
|
||||
|
||||
```sass title="actions05.css"
|
||||
```sass title="actions05.css"
|
||||
--8<-- "docs/examples/guide/actions/actions05.css"
|
||||
```
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Ths chapter discusses how to use Textual's animation system to create visual eff
|
||||
|
||||
## Animating styles
|
||||
|
||||
Textual's animator can change an attribute from one value to another in fixed increments over a period of time. You can apply animations to [styles](styles.md) such `offset` to move widgets around the screen, and `opacity` to create fading effects.
|
||||
Textual's animator can change an attribute from one value to another in fixed increments over a period of time. You can apply animations to [styles](styles.md) such as `offset` to move widgets around the screen, and `opacity` to create fading effects.
|
||||
|
||||
Apps and widgets both have an [animate][textual.app.App.animate] method which will animate properties on those objects. Additionally, `styles` objects have an identical `animate` method which will animate styles.
|
||||
|
||||
@@ -77,7 +77,7 @@ You can specify which easing method to use via the `easing` parameter on the `an
|
||||
|
||||
## Completion callbacks
|
||||
|
||||
You can pass an callable to the animator via the `on_complete` parameter. Textual will run the callable when the animation has completed.
|
||||
You can pass a callable to the animator via the `on_complete` parameter. Textual will run the callable when the animation has completed.
|
||||
|
||||
## Delaying animations
|
||||
|
||||
|
||||
@@ -76,30 +76,30 @@ Textual knows to *await* your event handlers if they are coroutines (i.e. prefix
|
||||
!!! tip
|
||||
|
||||
For a friendly introduction to async programming in Python, see FastAPI's [concurrent burgers](https://fastapi.tiangolo.com/async/) article.
|
||||
|
||||
|
||||
|
||||
## 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 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
|
||||
|
||||
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"
|
||||
--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"}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
@@ -141,7 +141,7 @@ You may have noticed that we subclassed `App[str]` rather than the usual `App`.
|
||||
--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
|
||||
|
||||
@@ -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).
|
||||
|
||||
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:
|
||||
|
||||
@@ -172,7 +172,7 @@ When `"question02.py"` runs it will load `"question02.css"` and update the app a
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -184,4 +184,4 @@ Here's the question app with classvar CSS:
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -103,7 +105,7 @@ log("[bold red]DANGER![/] We're having too much fun")
|
||||
|
||||
### 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
|
||||
from textual.app import App
|
||||
@@ -120,4 +122,3 @@ if __name__ == "__main__":
|
||||
LogApp.run()
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ More on that later, but for now keep in mind that events are also messages, and
|
||||
|
||||
Every [App][textual.app.App] and [Widget][textual.widget.Widget] object contains a *message queue*. You can think of a message queue as orders at a restaurant. The chef takes an order and makes the dish. Orders that arrive while the chef is cooking are placed in a line. When the chef has finished a dish they pick up the next order in the line.
|
||||
|
||||
Textual processes messages in the same way. Messages are picked off a queue and processed (cooked) by a handler method. This guarantees messages and events are processed even if your code can not handle them right way.
|
||||
Textual processes messages in the same way. Messages are picked off a queue and processed (cooked) by a handler method. This guarantees messages and events are processed even if your code can not handle them right way.
|
||||
|
||||
This processing of messages is done within an asyncio Task which is started when you mount the widget. The task monitors a queue for new messages and dispatches them to the appropriate handler when they arrive.
|
||||
|
||||
@@ -20,7 +20,7 @@ This processing of messages is done within an asyncio Task which is started when
|
||||
|
||||
The FastAPI docs have an [excellent introduction](https://fastapi.tiangolo.com/async/) to Python async programming.
|
||||
|
||||
By way of an example, let's consider what happens if you were to type "Text" in to a `Input` widget. When you hit the ++t++ key, Textual creates a [key][textual.events.Key] event and sends it to the widget's message queue. Ditto for ++e++, ++x++, and ++t++.
|
||||
By way of an example, let's consider what happens if you were to type "Text" in to a `Input` widget. When you hit the ++t++ key, Textual creates a [key][textual.events.Key] event and sends it to the widget's message queue. Ditto for ++e++, ++x++, and ++t++.
|
||||
|
||||
The widget's task will pick the first message from the queue (a key event for the ++t++ key) and call the `on_key` method with the event as the first argument. In other words it will call `Input.on_key(event)`, which updates the display to show the new letter.
|
||||
|
||||
@@ -69,7 +69,7 @@ After Textual calls `Button.on_key` the event _bubbles_ to the button's parent a
|
||||
--8<-- "docs/images/events/bubble2.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
As before, the event bubbles to it's parent (the App class).
|
||||
As before, the event bubbles to its parent (the App class).
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/events/bubble3.excalidraw.svg"
|
||||
@@ -177,7 +177,7 @@ Let's look at an example which looks up word definitions from an [api](https://d
|
||||
```
|
||||
=== "dictionary.css"
|
||||
|
||||
```python title="dictionary.css"
|
||||
```python title="dictionary.css"
|
||||
--8<-- "docs/examples/events/dictionary.css"
|
||||
```
|
||||
|
||||
@@ -186,4 +186,4 @@ Let's look at an example which looks up word definitions from an [api](https://d
|
||||
```{.textual path="docs/examples/events/dictionary.py" press="t,e,x,t,_,_,_,_,_,_,_,_,_,_,_"}
|
||||
```
|
||||
|
||||
Note the highlighted line in the above code which calls `asyncio.create_task` to run coroutine in the background. Without this you would find typing in to the text box to be unresponsive.
|
||||
Note the highlighted line in the above code which calls `asyncio.create_task` to run a coroutine in the background. Without this you would find typing in to the text box to be unresponsive.
|
||||
|
||||
@@ -10,7 +10,7 @@ This chapter will discuss how to make your app respond to input in the form of k
|
||||
|
||||
## Keyboard input
|
||||
|
||||
The most fundamental way to receive input in via [Key](./events/key) events. Let's write an app to show key events as you type.
|
||||
The most fundamental way to receive input is via [Key](./events/key) events. Let's write an app to show key events as you type.
|
||||
|
||||
=== "key01.py"
|
||||
|
||||
@@ -23,13 +23,13 @@ The most fundamental way to receive input in via [Key](./events/key) events. Let
|
||||
```{.textual path="docs/examples/guide/input/key01.py", press="T,e,x,t,u,a,l,!,_"}
|
||||
```
|
||||
|
||||
Note the key event handler on the app which logs all key events. if you press any key it will show up on the screen.
|
||||
Note the key event handler on the app which logs all key events. If you press any key it will show up on the screen.
|
||||
|
||||
### Attributes
|
||||
|
||||
There are two main attributes on a key event. The `key` attribute is the _name_ of the key which may be a single character, or a longer identifier. Textual ensures that the `key` attribute could always be used in a method name.
|
||||
|
||||
Key events also contain a `char` attribute which contains single character if it is printable, or ``None`` if it is not printable (like a function key which has no corresponding character).
|
||||
Key events also contain a `char` attribute which contains a single character if it is printable, or ``None`` if it is not printable (like a function key which has no corresponding character).
|
||||
|
||||
To illustrate the difference between `key` and `char`, try `key01.py` with the space key. You should see something like the following:
|
||||
|
||||
@@ -78,7 +78,7 @@ The following example shows how focus works in practice.
|
||||
```{.textual path="docs/examples/guide/input/key03.py", press="tab,H,e,l,l,o,tab,W,o,r,l,d,!,_"}
|
||||
```
|
||||
|
||||
The app splits the screen in to quarters, with a TextLog widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that thw widget has focus. Key events will be sent to the focused widget only.
|
||||
The app splits the screen in to quarters, with a `TextLog` widget in each quarter. If you click any of the text logs, you should see that it is highlighted to show that thw widget has focus. Key events will be sent to the focused widget only.
|
||||
|
||||
!!! tip
|
||||
|
||||
@@ -156,7 +156,7 @@ Coordinates may be relative to the screen, so `(0, 0)` would be the top left of
|
||||
|
||||
### Mouse movements
|
||||
|
||||
When you move the mouse cursor over a widget it will receive [MouseMove](../events/mouse_move.md) events which contain the coordinate of the mouse and information about what modified keys (++ctrl++, ++shift++ etc).
|
||||
When you move the mouse cursor over a widget it will receive [MouseMove](../events/mouse_move.md) events which contain the coordinate of the mouse and information about what modifier keys (++ctrl++, ++shift++ etc) are held down.
|
||||
|
||||
The following example shows mouse movements being used to _attach_ a widget to the mouse cursor.
|
||||
|
||||
@@ -200,7 +200,7 @@ If you want your app to respond to a mouse click you should prefer the Click eve
|
||||
|
||||
### Scroll events
|
||||
|
||||
Most mice have a scroll wheel which you can use to scroll window underneath the cursor. Scrollable containers in Textual will handle these automatically, but you can handle [MouseDown](../events/mouse_scroll_down.md) and [MouseUp](../events/mouse_scroll_up) if you want build your own scrolling functionality.
|
||||
Most mice have a scroll wheel which you can use to scroll the window underneath the cursor. Scrollable containers in Textual will handle these automatically, but you can handle [MouseScrollDown](../events/mouse_scroll_down.md) and [MouseScrollUp](../events/mouse_scroll_up) if you want build your own scrolling functionality.
|
||||
|
||||
!!! information
|
||||
|
||||
|
||||
@@ -468,7 +468,7 @@ If we wished for the sidebar to appear below the header, it'd simply be a case o
|
||||
|
||||
## Layers
|
||||
|
||||
Textual has a concept of _layers_ which gives you finely grained control over the order widgets are place.
|
||||
Textual has a concept of _layers_ which gives you finely grained control over the order widgets are placed.
|
||||
|
||||
When drawing widgets, Textual will first draw on _lower_ layers, working its way up to higher layers.
|
||||
As such, widgets on higher layers will be drawn on top of those on lower layers.
|
||||
@@ -548,4 +548,4 @@ The example below shows how an advanced layout can be built by combining the var
|
||||
--8<-- "docs/examples/guide/layout/combining_layouts.css"
|
||||
```
|
||||
|
||||
Textual layouts make it easy design build real-life applications with relatively little code.
|
||||
Textual layouts make it easy to design and build real-life applications with relatively little code.
|
||||
|
||||
@@ -33,9 +33,9 @@ The `reactive` constructor accepts a default value as the first positional argum
|
||||
|
||||
!!! information
|
||||
|
||||
Textual uses Python's _descriptor protocol_ to create reactive attributes, which is the same protocol used by the builtin `property` decorator.
|
||||
Textual uses Python's _descriptor protocol_ to create reactive attributes, which is the same protocol used by the builtin `property` decorator.
|
||||
|
||||
You can get and set these attributes in the same way as if you had assigned them in a `__init__` method. For instance `self.name = "Jessica"`, `self.count += 1`, or `print(self.is_cool)`.
|
||||
You can get and set these attributes in the same way as if you had assigned them in an `__init__` method. For instance `self.name = "Jessica"`, `self.count += 1`, or `print(self.is_cool)`.
|
||||
|
||||
### Dynamic defaults
|
||||
|
||||
@@ -69,7 +69,7 @@ The first superpower we will look at is "smart refresh". When you modify a react
|
||||
|
||||
!!! information
|
||||
|
||||
If you modify multiple reactive attribute, Textual will only do a single refresh to minimize updates.
|
||||
If you modify multiple reactive attributes, Textual will only do a single refresh to minimize updates.
|
||||
|
||||
Let's look at an example which illustrates this. In the following app, the value of an input is used to update a "Hello, World!" type greeting.
|
||||
|
||||
@@ -81,7 +81,7 @@ Let's look at an example which illustrates this. In the following app, the value
|
||||
|
||||
=== "refresh01.css"
|
||||
|
||||
```sass
|
||||
```sass
|
||||
--8<-- "docs/examples/guide/reactivity/refresh01.css"
|
||||
```
|
||||
|
||||
@@ -121,7 +121,7 @@ The following example modifies "refresh01.py" so that the greeting has an automa
|
||||
--8<-- "docs/examples/guide/reactivity/refresh02.py"
|
||||
```
|
||||
|
||||
1. This attribute will update the layout when changed.
|
||||
1. This attribute will update the layout when changed.
|
||||
|
||||
=== "refresh02.css"
|
||||
|
||||
@@ -152,7 +152,7 @@ A common use for this is to restrict numbers to a given range. The following exa
|
||||
|
||||
=== "validate01.css"
|
||||
|
||||
```sass
|
||||
```sass
|
||||
--8<-- "docs/examples/guide/reactivity/validate01.css"
|
||||
```
|
||||
|
||||
@@ -167,7 +167,7 @@ If you click the buttons in the above example it will show the current count. Wh
|
||||
|
||||
Watch methods are another superpower. Textual will call watch methods when reactive attributes are modified. Watch methods begin with `watch_` followed by the name of the attribute. If the watch method accepts a positional argument, it will be called with the new assigned value. If the watch method accepts *two* positional arguments, it will be called with both the *old* value and the *new* value.
|
||||
|
||||
The follow app will display any color you type in to the input. Try it with a valid color in Textual CSS. For example `"darkorchid"` or `"#52de44".
|
||||
The following app will display any color you type in to the input. Try it with a valid color in Textual CSS. For example `"darkorchid"` or `"#52de44"`.
|
||||
|
||||
=== "watch01.py"
|
||||
|
||||
@@ -181,7 +181,7 @@ The follow app will display any color you type in to the input. Try it with a va
|
||||
|
||||
=== "watch01.css"
|
||||
|
||||
```sass
|
||||
```sass
|
||||
--8<-- "docs/examples/guide/reactivity/watch01.css"
|
||||
```
|
||||
|
||||
@@ -196,9 +196,9 @@ The color is parsed in `on_input_submitted` and assigned to `self.color`. Becaus
|
||||
|
||||
Compute methods are the final superpower offered by the `reactive` descriptor. Textual runs compute methods to calculate the value of a reactive attribute. Compute methods begin with `compute_` followed by the name of the reactive value.
|
||||
|
||||
You could be forgiven in thinking this sounds a lot like Python's property decorator. The difference is that Textual will cache the value of compute methods, and update them when any other reactive attribute changes.
|
||||
You could be forgiven in thinking this sounds a lot like Python's property decorator. The difference is that Textual will cache the value of compute methods, and update them when any other reactive attribute changes.
|
||||
|
||||
The following example uses a computed attribute. It displays three inputs for the each color component (red, green, and blue). If you enter numbers in to these inputs, the background color of another widget changes.
|
||||
The following example uses a computed attribute. It displays three inputs for each color component (red, green, and blue). If you enter numbers in to these inputs, the background color of another widget changes.
|
||||
|
||||
=== "computed01.py"
|
||||
|
||||
@@ -211,7 +211,7 @@ The following example uses a computed attribute. It displays three inputs for th
|
||||
|
||||
=== "computed01.css"
|
||||
|
||||
```sass
|
||||
```sass
|
||||
--8<-- "docs/examples/guide/reactivity/computed01.css"
|
||||
```
|
||||
|
||||
|
||||
@@ -39,15 +39,15 @@ Let's look at a simple example of writing a screen class to simulate Window's [b
|
||||
```{.textual path="docs/examples/guide/screens/screen01.py" press="b,_"}
|
||||
```
|
||||
|
||||
If you run this you will see an empty screen. Hit the ++b++ screen to show a blue screen of death. Hit ++escape++ to return to the default screen.
|
||||
If you run this you will see an empty screen. Hit the ++b++ key to show a blue screen of death. Hit ++escape++ to return to the default screen.
|
||||
|
||||
The `BSOD` class above defines a screen with a key binding and compose method. These should be familiar as they work in the same way as apps.
|
||||
|
||||
The app class has a new `SCREENS` class variable. Textual uses this class variable to associated a name with screen object (the name is used to reference screens in the screen API). Also in the app is a key binding associated with the action `"push_screen('bsod')"`. The screen class has a similar action `"pop_screen"` bound to the ++escape++ key. We will cover these actions below.
|
||||
The app class has a new `SCREENS` class variable. Textual uses this class variable to associate a name with screen object (the name is used to reference screens in the screen API). Also in the app is a key binding associated with the action `"push_screen('bsod')"`. The screen class has a similar action `"pop_screen"` bound to the ++escape++ key. We will cover these actions below.
|
||||
|
||||
## Named screens
|
||||
|
||||
You can associate a screen with a name by defining a `SCREENS` class variable in your app, which should be dict that maps names on to Screen objects. The name of the screen may be used interchangeably with screen objects in much of the screen API.
|
||||
You can associate a screen with a name by defining a `SCREENS` class variable in your app, which should be a `dict` that maps names on to `Screen` objects. The name of the screen may be used interchangeably with screen objects in much of the screen API.
|
||||
|
||||
You can also _install_ new named screens dynamically with the [install_screen][textual.app.App.install_screen] method. The following example installs the `BSOD` screen in a mount handler rather than from the `SCREENS` variable.
|
||||
|
||||
@@ -68,7 +68,7 @@ You can also _install_ new named screens dynamically with the [install_screen][t
|
||||
```{.textual path="docs/examples/guide/screens/screen02.py" press="b,_"}
|
||||
```
|
||||
|
||||
Although both do the same thing, we recommend the `SCREENS` for screens that exist for the lifetime of your app.
|
||||
Although both do the same thing, we recommend `SCREENS` for screens that exist for the lifetime of your app.
|
||||
|
||||
### Uninstalling screens
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Styles
|
||||
|
||||
In this chapter will explore how you can apply styles to your application to create beautiful user interfaces.
|
||||
In this chapter we will explore how you can apply styles to your application to create beautiful user interfaces.
|
||||
|
||||
## Styles object
|
||||
|
||||
@@ -48,9 +48,9 @@ Note how the combined height of the widget is three rows in the terminal. This i
|
||||
|
||||
## Colors
|
||||
|
||||
There are a number of style attribute which accept colors. The most commonly used are [color](../styles/color.md) which sets the default color of text on a widget, and [background](..styles/background/md) which sets the background color (beneath the text).
|
||||
There are a number of style attributes which accept colors. The most commonly used are [color](../styles/color.md) which sets the default color of text on a widget, and [background](..styles/background/md) which sets the background color (beneath the text).
|
||||
|
||||
You can set a color value to one of a number of pre-defined color constants, such as "crimson", "lime", and "palegreen". You can find a full list in the [Color reference](../reference/color.md#textual.color--named-colors).
|
||||
You can set a color value to one of a number of pre-defined color constants, such as `"crimson"`, `"lime"`, and `"palegreen"`. You can find a full list in the [Color reference](../reference/color.md#textual.color--named-colors).
|
||||
|
||||
Here's how you would set the screen background to lime:
|
||||
|
||||
@@ -88,15 +88,15 @@ There are a few ways you can set alpha on a color in Textual.
|
||||
|
||||
- You can set the alpha value of a color by adding a fourth digit or pair of digits to a hex color. The extra digits form an alpha component which ranges from 0 for completely transparent to 255 (completely opaque). Any value between 0 and 255 will be translucent. For example `"#9932CC7f"` is a dark orchid which is roughly 50% translucent.
|
||||
- You can also set alpha with the `rgba` format, which is identical to `rgb` with the additional of a fourth value that should be between 0 and 1, where 0 is invisible and 1 is opaque. For example `"rgba(192,78,96,0.5)"`.
|
||||
- You can add the `a` parameter on a [Color][textual.color.Color] object. For example `Color(192, 78, 96, a=0.5)` creates a translucent dark orchid.
|
||||
- You can add the `a` parameter on a [Color][textual.color.Color] object. For example `Color(192, 78, 96, a=0.5)` creates a translucent dark orchid.
|
||||
|
||||
The following examples shows what happens when you set alpha on background colors:
|
||||
The following example shows what happens when you set alpha on background colors:
|
||||
|
||||
```python title="colors01.py" hl_lines="12-15"
|
||||
--8<-- "docs/examples/guide/styles/colors02.py"
|
||||
```
|
||||
|
||||
Notice that an alpha of 0.1 the background almost matches the screen, but at 1.0 it is a solid color.
|
||||
Notice that at an alpha of 0.1 the background almost matches the screen, but at 1.0 it is a solid color.
|
||||
|
||||
```{.textual path="docs/examples/guide/styles/colors02.py"}
|
||||
```
|
||||
@@ -110,7 +110,7 @@ Widgets occupy a rectangular region of the screen, which may be as small as a si
|
||||
The following styles influence the dimensions of a widget.
|
||||
|
||||
- [width](../styles/width.md) and [height](../styles/width.md) define the size of the widget.
|
||||
- [padding](../styles/padding.md) adds optional space around the content area.
|
||||
- [padding](../styles/padding.md) adds optional space around the content area.
|
||||
- [border](../styles/border.md) draws an optional rectangular border around the padding and the content area.
|
||||
|
||||
Additionally, the [margin](../styles/margin.md) style adds space around a widget's border, which isn't technically part of the widget, but provides visual separation between widgets.
|
||||
@@ -138,7 +138,7 @@ Note how the text wraps in the widget, and is cropped because it doesn't fit in
|
||||
|
||||
#### Auto dimensions
|
||||
|
||||
In practice, we generally want the size of a widget to adapt to it's content, which we can do by setting a dimension to `"auto"`.
|
||||
In practice, we generally want the size of a widget to adapt to its content, which we can do by setting a dimension to `"auto"`.
|
||||
|
||||
Let's set the height to auto and see what happens.
|
||||
|
||||
@@ -195,7 +195,7 @@ Let's look at an example. We will create two widgets, one with a height of `"2fr
|
||||
--8<-- "docs/examples/guide/styles/dimensions04.py"
|
||||
```
|
||||
|
||||
The total `fr` units for height is 3. The first widget will have a screen height of two thirds because its height style is set to `2fr`. The second widget's height styles is `1fr` so its screen height will be one third. Here's what that looks like.
|
||||
The total `fr` units for height is 3. The first widget will have a screen height of two thirds because its height style is set to `2fr`. The second widget's height style is `1fr` so its screen height will be one third. Here's what that looks like.
|
||||
|
||||
```{.textual path="docs/examples/guide/styles/dimensions04.py"}
|
||||
```
|
||||
@@ -235,7 +235,7 @@ Compare the output of this example to the previous example:
|
||||
|
||||
You can also set padding to a tuple of *four* values which applies padding to each edge individually. The first value is the padding for the top of the widget, followed by the right of the widget, then bottom, then left.
|
||||
|
||||
### Border
|
||||
### Border
|
||||
|
||||
The [border](../styles/border.md) style draws a border around a widget. To add a border set `styles.border` to a tuple of two values. The first value is the border type, which should be a string. The second value is the border color which will accept any value that works with [color](../styles/color.md) and [background](../styles/background.md).
|
||||
|
||||
@@ -301,7 +301,7 @@ The second widget sets `box_sizing` to `"content-box"`.
|
||||
--8<-- "docs/examples/guide/styles/box_sizing01.py"
|
||||
```
|
||||
|
||||
The padding and border of the first widget is subtracted from the height leaving only 2 lines in the content area. The second widget also has a height of 6, but the padding and border adds additional height so that the content area remains 6 lines.
|
||||
The padding and border of the first widget is subtracted from the height leaving only 2 lines in the content area. The second widget also has a height of 6, but the padding and border adds additional height so that the content area remains 6 lines.
|
||||
|
||||
```{.textual path="docs/examples/guide/styles/box_sizing01.py"}
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ A widget is a component of your UI responsible for managing a rectangular region
|
||||
|
||||
There is a growing collection of [builtin widgets](../widgets/index.md) in Textual, but you can build entirely custom widgets that work in the same way.
|
||||
|
||||
The first step in building a widget is to import and extend a widget class. This can either be [Widget][textual.widget.Widget] which is the base class of all widgets, or one of it's subclasses.
|
||||
The first step in building a widget is to import and extend a widget class. This can either be [Widget][textual.widget.Widget] which is the base class of all widgets, or one of its subclasses.
|
||||
|
||||
Let's create a simple custom widget to display a greeting.
|
||||
|
||||
@@ -26,7 +26,7 @@ Let's create a simple custom widget to display a greeting.
|
||||
|
||||
The three highlighted lines define a custom widget class with just a [render()][textual.widget.Widget.render] method. Textual will display whatever is returned from render in the content area of your widget. We have returned a string in the code above, but there are other possible return types which we will cover later.
|
||||
|
||||
Note that the text contains tags in square brackets, i.e. `[b]`. This is [console markup](https://rich.readthedocs.io/en/latest/markup.html) which allows you to embed various styles within your content. If you run this you will find that `World` is in bold.
|
||||
Note that the text contains tags in square brackets, i.e. `[b]`. This is [console markup](https://rich.readthedocs.io/en/latest/markup.html) which allows you to embed various styles within your content. If you run this you will find that `World` is in bold.
|
||||
|
||||
```{.textual path="docs/examples/guide/widgets/hello01.py"}
|
||||
```
|
||||
@@ -65,7 +65,7 @@ Let's use Static to create a widget which cycles through "hello" in various lang
|
||||
|
||||
=== "hello03.css"
|
||||
|
||||
```sass title="hello03.css"
|
||||
```sass title="hello03.css"
|
||||
--8<-- "docs/examples/guide/widgets/hello03.css"
|
||||
```
|
||||
|
||||
@@ -76,11 +76,11 @@ Let's use Static to create a widget which cycles through "hello" in various lang
|
||||
|
||||
Note that there is no `render()` method on this widget. The Static class is handling the render for us. Instead we call `update()` when we want to update the content within the widget.
|
||||
|
||||
The `next_word` method updates the greeting. We call this method from the mount handler to get the first word, and from an click handler to cycle through the greetings when we click the widget.
|
||||
The `next_word` method updates the greeting. We call this method from the mount handler to get the first word, and from a click handler to cycle through the greetings when we click the widget.
|
||||
|
||||
### Default CSS
|
||||
|
||||
When building an app it is best to keep your CSS in an external file. This allows you to see all your CSS in one place, and to enable live editing. However if you intent to distribute a widget (via PyPI for instance) it can be convenient to bundle the code and CSS together. You can do this by adding a `DEFAULT_CSS` class variable inside your widget class.
|
||||
When building an app it is best to keep your CSS in an external file. This allows you to see all your CSS in one place, and to enable live editing. However if you intend to distribute a widget (via PyPI for instance) it can be convenient to bundle the code and CSS together. You can do this by adding a `DEFAULT_CSS` class variable inside your widget class.
|
||||
|
||||
Textual's builtin widgets bundle CSS in this way, which is why you can see nicely styled widgets without having to copy any CSS code.
|
||||
|
||||
@@ -94,7 +94,7 @@ Here's the Hello example again, this time the widget has embedded default CSS:
|
||||
|
||||
=== "hello04.css"
|
||||
|
||||
```sass title="hello04.css"
|
||||
```sass title="hello04.css"
|
||||
--8<-- "docs/examples/guide/widgets/hello04.css"
|
||||
```
|
||||
|
||||
@@ -117,7 +117,7 @@ Text in a widget may be marked up with links which perform an action when clicke
|
||||
"Click [@click='app.bell']Me[/]"
|
||||
```
|
||||
|
||||
The `@click` tag introduces a click handler, which runs the `app.bell` action.
|
||||
The `@click` tag introduces a click handler, which runs the `app.bell` action.
|
||||
|
||||
Let's use markup links in the hello example so that the greeting becomes a link which updates the widget.
|
||||
|
||||
@@ -167,7 +167,7 @@ This app will "play" fizz buzz by displaying a table of the first 15 numbers and
|
||||
```{.textual path="docs/examples/guide/widgets/fizzbuzz01.py"}
|
||||
```
|
||||
|
||||
## Content size
|
||||
## Content size
|
||||
|
||||
Textual will auto-detect the dimensions of the content area from rich renderables if width or height is set to `auto`. You can override auto dimensions by implementing [get_content_width()][textual.widget.Widget.get_content_width] or [get_content_height()][textual.widget.Widget.get_content_height].
|
||||
|
||||
@@ -182,7 +182,7 @@ Let's modify the default width for the fizzbuzz example. By default, the table w
|
||||
|
||||
=== "fizzbuzz02.css"
|
||||
|
||||
```sass title="fizzbuzz02.css"
|
||||
```sass title="fizzbuzz02.css"
|
||||
--8<-- "docs/examples/guide/widgets/fizzbuzz02.css"
|
||||
```
|
||||
|
||||
@@ -191,7 +191,7 @@ Let's modify the default width for the fizzbuzz example. By default, the table w
|
||||
```{.textual path="docs/examples/guide/widgets/fizzbuzz02.py"}
|
||||
```
|
||||
|
||||
Note that we've added `expand=True` to tell the Table to expand beyond the optimal width, so that it fills the 50 characters returned by `get_content_width`.
|
||||
Note that we've added `expand=True` to tell the `Table` to expand beyond the optimal width, so that it fills the 50 characters returned by `get_content_width`.
|
||||
|
||||
|
||||
## Compound widgets
|
||||
|
||||
Reference in New Issue
Block a user