mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'main' of github.com:Textualize/textual into datatable-events
This commit is contained in:
@@ -12,5 +12,4 @@ repos:
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
exclude: ^tests/
|
||||
exclude: ^tests/snapshot_tests
|
||||
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -12,15 +12,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- Added `TreeNode.parent` -- a read-only property for accessing a node's parent https://github.com/Textualize/textual/issues/1397
|
||||
- Added public `TreeNode` label access via `TreeNode.label` https://github.com/Textualize/textual/issues/1396
|
||||
- Added read-only public access to the children of a `TreeNode` via `TreeNode.children` https://github.com/Textualize/textual/issues/1398
|
||||
- Added `Tree.get_node_by_id` to allow getting a node by its ID https://github.com/Textualize/textual/pull/1535
|
||||
- Added a `Tree.NodeHighlighted` message, giving a `on_tree_node_highlighted` event handler https://github.com/Textualize/textual/issues/1400
|
||||
- Added a `inherit_component_classes` subclassing parameter to control whether or not component classes are inherited from base classes https://github.com/Textualize/textual/issues/1399
|
||||
- Added `diagnose` as a `textual` command https://github.com/Textualize/textual/issues/1542
|
||||
|
||||
### Changed
|
||||
|
||||
- `MouseScrollUp` and `MouseScrollDown` now inherit from `MouseEvent` and have attached modifier keys. https://github.com/Textualize/textual/pull/1458
|
||||
- Fail-fast and print pretty tracebacks for Widget compose errors https://github.com/Textualize/textual/pull/1505
|
||||
- Added Widget._refresh_scroll to avoid expensive layout when scrolling https://github.com/Textualize/textual/pull/1524
|
||||
- `events.Paste` now bubbles https://github.com/Textualize/textual/issues/1434
|
||||
- Clock color in the `Header` widget now matches the header color https://github.com/Textualize/textual/issues/1459
|
||||
- `COMPONENT_CLASSES` are now inherited from base classes https://github.com/Textualize/textual/issues/1399
|
||||
- Watch methods may now take no parameters
|
||||
- Added `compute` parameter to reactive
|
||||
|
||||
### Fixed
|
||||
|
||||
- The styles `scrollbar-background-active` and `scrollbar-color-hover` are no longer ignored https://github.com/Textualize/textual/pull/1480
|
||||
- The widget `Placeholder` can now have its width set to `auto` https://github.com/Textualize/textual/pull/1508
|
||||
- Behavior of widget `Input` when rendering after programmatic value change and related scenarios https://github.com/Textualize/textual/issues/1477 https://github.com/Textualize/textual/issues/1443
|
||||
|
||||
## [0.9.1] - 2022-12-30
|
||||
|
||||
|
||||
323
docs/blog/posts/looking-for-help.md
Normal file
323
docs/blog/posts/looking-for-help.md
Normal file
@@ -0,0 +1,323 @@
|
||||
---
|
||||
draft: false
|
||||
date: 2023-01-09
|
||||
categories:
|
||||
- DevLog
|
||||
authors:
|
||||
- davep
|
||||
---
|
||||
|
||||
# So you're looking for a wee bit of Textual help...
|
||||
|
||||
## Introduction
|
||||
|
||||
!!! quote
|
||||
|
||||
Patience, Highlander. You have done well. But it'll take time. You are
|
||||
generations being born and dying. You are at one with all living things.
|
||||
Each man's thoughts and dreams are yours to know. You have power beyond
|
||||
imagination. Use it well, my friend. Don't lose your head.
|
||||
|
||||
<cite>Juan Sánchez Villalobos Ramírez, Chief metallurgist to King Charles V of Spain</cite>
|
||||
|
||||
As of the time of writing, I'm a couple or so days off having been with
|
||||
Textualize for 3 months. It's been fun, and educational, and every bit as
|
||||
engaging as I'd hoped, and more. One thing I hadn't quite prepared for
|
||||
though, but which I really love, is how so many other people are learning
|
||||
Textual along with me.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
Even in those three months the library has changed and expanded quite a lot,
|
||||
and it continues to do so. Meanwhile, more people are turning up and using
|
||||
the framework; you can see this online in social media, blogs and of course
|
||||
[in the ever-growing list of projects on GitHub which depend on
|
||||
Textual](https://github.com/Textualize/textual/network/dependents).
|
||||
|
||||
This inevitably means there's a lot of people getting to grips with a new
|
||||
tool, and one that is still a bit of a moving target. This in turn means
|
||||
lots of people are coming to us to get help.
|
||||
|
||||
As I've watched this happen I've noticed a few patterns emerging. Some of
|
||||
these good or neutral, some... let's just say not really beneficial to those
|
||||
seeking the help, or to those trying to provide the help. So I wanted to
|
||||
write a little bit about the different ways you can get help with Textual
|
||||
and your Textual-based projects, and to also try and encourage people to
|
||||
take the most helpful and positive approach to getting that help.
|
||||
|
||||
Now, before I go on, I want to make something *very* clear: I'm writing this
|
||||
as an individual. This is my own personal view, and my own advice from me to
|
||||
anyone who wishes to take it. It's not Textual (the project) or Textualize
|
||||
(the company) policy, rules or guidelines. This is just some ageing hacker's
|
||||
take on how best to go about asking for help, informed by years of asking
|
||||
for and also providing help in email, on Usenet, on forums, etc.
|
||||
|
||||
Or, put another way: if what you read in here seems sensible to you, I
|
||||
figure we'll likely have already hit it off [over on
|
||||
GitHub](https://github.com/Textualize/textual) or in [the Discord
|
||||
server](https://discord.gg/Enf6Z3qhVr). ;-)
|
||||
|
||||
## Where to go for help
|
||||
|
||||
At this point this is almost a bit of an FAQ itself, so I thought I'd
|
||||
address it here: where's the best place to ask for help about Textual, and
|
||||
what's the difference between GitHub Issues, Discussions and our Discord
|
||||
server?
|
||||
|
||||
I'd suggest thinking of them like this:
|
||||
|
||||
### Discord
|
||||
|
||||
You have a question, or need help with something, and perhaps you could do
|
||||
with a reply as soon as possible. But, and this is the **really important
|
||||
part**, it doesn't matter if you don't get a response. If you're in this
|
||||
situation then the Discord server is possibly a good place to start. If
|
||||
you're lucky someone will be hanging about who can help out.
|
||||
|
||||
I can't speak for anyone else, but keep this in mind: when I look in on
|
||||
Discord I tend not to go scrolling back much to see if anything has been
|
||||
missed. If something catches my eye, I'll try and reply, but if it
|
||||
doesn't... well, it's mostly an instant chat thing so I don't dive too
|
||||
deeply back in time.
|
||||
|
||||
!!! tip inline end "Going from Discord to a GitHub issue"
|
||||
|
||||
As a slight aside here: sometimes people will pop up in Discord, ask a
|
||||
question about something that turns out looking like a bug, and that's
|
||||
the last we hear of it. Please, please, **please**, if this happens, the
|
||||
most helpful thing you can do is go raise an issue for us. It'll help us
|
||||
to keep track of problems, it'll help get your problem fixed, it'll mean
|
||||
everyone benefits.
|
||||
|
||||
My own advice would be to treat Discord as an ephemeral resource. It happens
|
||||
in the moment but fades away pretty quickly. It's like knocking on a
|
||||
friend's door to see if they're in. If they're not in, you might leave them
|
||||
a note, which is sort of like going to...
|
||||
|
||||
### GitHub
|
||||
|
||||
On the other hand, if you have a question or need some help or something
|
||||
where you want to stand a good chance of the Textual developers (amongst
|
||||
others) seeing it and responding, I'd recommend that GitHub is the place to
|
||||
go. Dropping something into the discussions there, or leaving an issue,
|
||||
ensures it'll get seen. It won't get lost.
|
||||
|
||||
As for which you should use -- a discussion or an issue -- I'd suggest this:
|
||||
if you need help with something, or you want to check your understanding of
|
||||
something, or you just want to be sure something is a problem before taking
|
||||
it further, a discussion might be the best thing. On the other hand, if
|
||||
you've got a clear bug or feature request on your hands, an issue makes a
|
||||
lot of sense.
|
||||
|
||||
Don't worry if you're not sure which camp your question or whatever falls
|
||||
into though; go with what you think is right. There's no harm done either
|
||||
way (I may move an issue to a discussion first before replying, if it's
|
||||
really just a request for help -- but that's mostly so everyone can benefit
|
||||
from finding it in the right place later on down the line).
|
||||
|
||||
## The dos and don'ts of getting help
|
||||
|
||||
Now on to the fun part. This is where I get a bit preachy. Ish. Kinda. A
|
||||
little bit. Again, please remember, this isn't a set of rules, this isn't a
|
||||
set of official guidelines, this is just a bunch of *"if you want my advice,
|
||||
and I know you didn't ask but you've read this far so you actually sort of
|
||||
did don't say I didn't warn you!"* waffle.
|
||||
|
||||
This isn't going to be an exhaustive collection, far from it. But I feel
|
||||
these are some important highlights.
|
||||
|
||||
### Do...
|
||||
|
||||
When looking for help, in any of the locations mentioned above, I'd totally
|
||||
encourage:
|
||||
|
||||
#### Be clear and detailed
|
||||
|
||||
Too much detail is almost always way better than not enough. *"My program
|
||||
didn't run"*, often even with some of the code supplied, is so much harder
|
||||
to help than *"I ran this code I'm posting here, and I expected this
|
||||
particular outcome, and I expected it because I'd read this particular thing
|
||||
in the docs and had comprehended it to mean this, but instead the outcome
|
||||
was this exception here, and I'm a bit stuck -- can someone offer some
|
||||
pointers?"*
|
||||
|
||||
The former approach means there often ends up having to be a back and forth
|
||||
which can last a long time, and which can sometimes be frustrating for the
|
||||
person asking. Manage frustration: be clear, tell us everything you can.
|
||||
|
||||
#### Say what resources you've used already
|
||||
|
||||
If you've read the potions of the documentation that relate to what you're
|
||||
trying to do, it's going to be really helpful if you say so. If you don't,
|
||||
it might be assumed you haven't and you may end up being pointed at them.
|
||||
|
||||
So, please, if you've checked the documentation, looked in the FAQ, done a
|
||||
search of past issues or discussions or perhaps even done a search on the
|
||||
Discord server... please say so.
|
||||
|
||||
#### Be polite
|
||||
|
||||
This one can go a long way when looking for help. Look, I get it,
|
||||
programming is bloody frustrating at times. We've all rage-quit some code at
|
||||
some point, I'm sure. It's likely going to be your moment of greatest
|
||||
frustration when you go looking for help. But if you turn up looking for
|
||||
help acting all grumpy and stuff it's not going to come over well. Folk are
|
||||
less likely to be motivated to lend a hand to someone who seems rather
|
||||
annoyed.
|
||||
|
||||
If you throw in a please and thank-you here and there that makes it all the
|
||||
better.
|
||||
|
||||
#### Fully consider the replies
|
||||
|
||||
You could find yourself getting a reply that you're sure won't help at all.
|
||||
That's fair. But be sure to fully consider it first. Perhaps you missed the
|
||||
obvious along the way and this is 100% the course correction you'd
|
||||
unknowingly come looking for in the first place. Sure, the person replying
|
||||
might have totally misunderstood what was being asked, or might be giving a
|
||||
wrong answer (it me! I've totally done that and will again!), but even then
|
||||
a reply along the lines of *"I'm not sure that's what I'm looking for,
|
||||
because..."* gets everyone to the solution faster than *"lol nah"*.
|
||||
|
||||
#### Entertain what might seem like odd questions
|
||||
|
||||
Aye, I get it, being asked questions when you're looking for an *answer* can
|
||||
be a bit frustrating. But if you find yourself on the receiving end of a
|
||||
small series of questions about your question, keep this in mind: Textual is
|
||||
still rather new and still developing and it's possible that what you're
|
||||
trying to do isn't the correct way to do that thing. To the person looking
|
||||
to help you it may seem to them you have an [XY
|
||||
problem](https://en.wikipedia.org/wiki/XY_problem).
|
||||
|
||||
Entertaining those questions might just get you to the real solution to your
|
||||
problem.
|
||||
|
||||
#### Allow for language differences
|
||||
|
||||
You don't need me to tell you that a project such as Textual has a global
|
||||
audience. With that rather obvious fact comes the other fact that we don't
|
||||
all share the same first language. So, please, as much as possible, try and
|
||||
allow for that. If someone is trying to help you out, and they make it clear
|
||||
they're struggling to follow you, keep this in mind.
|
||||
|
||||
#### Acknowledge the answer
|
||||
|
||||
I suppose this is a variation on "be polite" (really, a thanks can go a long
|
||||
way), but there's more to this than a friendly acknowledgement. If someone
|
||||
has gone to the trouble of offering some help, it's helpful to everyone who
|
||||
comes after you to acknowledge if it worked or not. That way a future
|
||||
help-seeker will know if the answer they're reading stands a chance of being
|
||||
the right one.
|
||||
|
||||
#### Accept that Textual is zero-point software (right now)
|
||||
|
||||
Of course the aim is to have every release of Textual be stable and useful,
|
||||
but things will break. So, please, do keep in mind things like:
|
||||
|
||||
- Textual likely doesn't have your feature of choice just yet.
|
||||
- We might accidentally break something (perhaps pinning Textual and testing
|
||||
each release is a good plan here?).
|
||||
- We might deliberately break something because we've decided to take a
|
||||
particular feature or way of doing things in a better direction.
|
||||
|
||||
Of course it can be a bit frustrating a times, but overall the aim is to
|
||||
have the best framework possible in the long run.
|
||||
|
||||
### Don't...
|
||||
|
||||
Okay, now for a bit of old-hacker finger-wagging. Here's a few things I'd
|
||||
personally discourage:
|
||||
|
||||
#### Lack patience
|
||||
|
||||
Sure, it can be annoying. You're in your flow, you've got a neat idea for a
|
||||
thing you want to build, you're stuck on one particular thing and you really
|
||||
need help right now! Thing is, that's unlikely to happen. Badgering
|
||||
individuals, or a whole resource, to reply right now, or complaining that
|
||||
it's been `$TIME_PERIOD` since you asked and nobody has replied... that's
|
||||
just going to make people less likely to reply.
|
||||
|
||||
#### Unnecessarily tag individuals
|
||||
|
||||
This one often goes hand in hand with the "lack patience" thing: Be it
|
||||
asking on Discord, or in GitHub issues, discussions or even PRs,
|
||||
unnecessarily tagging individuals is a bit rude. Speaking for myself and
|
||||
only myself: I *love* helping folk with Textual. If I could help everyone
|
||||
all the time the moment they have a problem, I would. But it doesn't work
|
||||
like that. There's any number of reasons I might not be responding to a
|
||||
particular request, including but not limited to (here I'm talking
|
||||
personally because I don't want to speak for anyone else, but I'm sure I'm
|
||||
not alone here):
|
||||
|
||||
- I have a job. Sure, my job is (in part) Textual, but there's more to it
|
||||
than that particular issue. I might be doing other stuff.
|
||||
- I have my own projects to work on too. I like coding for fun as well (or
|
||||
writing preaching old dude blog posts like this I guess, but you get the
|
||||
idea).
|
||||
- I actually have other interests outside of work hours so I might actually
|
||||
be out doing a 10k in the local glen, or battling headcrabs in VR, or
|
||||
something.
|
||||
- Housework. :-/
|
||||
|
||||
You get the idea though. So while I'm off having a well-rounded life, it's
|
||||
not good to get unnecessarily intrusive alerts to something that either a)
|
||||
doesn't actually directly involve me or b) could wait.
|
||||
|
||||
#### Seek personal support
|
||||
|
||||
Again, I'm going to speak totally for myself here, but I also feel the
|
||||
general case is polite for all: there's a lot of good support resources
|
||||
available already; sending DMs on Discord or Twitter or in the Fediverse,
|
||||
looking for direct personal support, isn't really the best way to get help.
|
||||
Using the public/collective resources is absolutely the *best* way to get
|
||||
that help. Why's it a bad idea to dive into DMs? Here's some reasons I think
|
||||
it's not a good idea:
|
||||
|
||||
- It's a variation on "unnecessarily tagging individuals".
|
||||
- You're short-changing yourself when it comes to getting help. If you ask
|
||||
somewhere more public you're asking a much bigger audience, who
|
||||
collectively have more time, more knowledge and more experience than a
|
||||
single individual.
|
||||
- Following on from that, any answers can be (politely) fact-checked or
|
||||
enhanced by that audience, resulting in a better chance of getting the
|
||||
best help possible.
|
||||
- The next seeker-of-help gets to miss out on your question and the answer.
|
||||
If asked and answered in public, it's a record that can help someone else
|
||||
in the future.
|
||||
|
||||
#### Doubt your ability or skill level
|
||||
|
||||
I suppose this should really be phrased as a do rather than a don't, as here
|
||||
I want to encourage something positive. A few times I've helped people out
|
||||
who have been very apologetic about their questions being "noob" questions,
|
||||
or about how they're fairly new to Python, or programming in general.
|
||||
Really, please, don't feel the need to apologise and don't be ashamed of
|
||||
where you're at.
|
||||
|
||||
If you've asked something that's obviously answered in the documentation,
|
||||
that's not a problem; you'll likely get pointed at the docs and it's what
|
||||
happens next that's the key bit. If the attitude is *"oh, cool, that's
|
||||
exactly what I needed to be reading, thanks!"* that's a really positive
|
||||
thing. The only time it's a problem is when there's a real reluctance to use
|
||||
the available resources. We've all seen that person somewhere at some point,
|
||||
right? ;-)
|
||||
|
||||
Not knowing things [is totally cool](https://xkcd.com/1053/).
|
||||
|
||||
## Conclusion
|
||||
|
||||
So, that's my waffle over. As I said at the start: this is my own personal
|
||||
thoughts on how to get help with Textual, both as someone whose job it is to
|
||||
work on Textual and help people with Textual, and also as a FOSS advocate
|
||||
and supporter who can normally be found helping Textual users when he's not
|
||||
"on the clock" too.
|
||||
|
||||
What I've written here isn't exhaustive. Neither is it novel. Plenty has
|
||||
been written on the general subject in the past, and I'm sure more will be
|
||||
written on the subject in the future. I do, however, feel that these are the
|
||||
most common things I notice. I'd say those dos and don'ts cover 90% of *"can
|
||||
I get some help?"* interactions; perhaps closer to 99%.
|
||||
|
||||
Finally, and I think this is the most important thing to remember, the next
|
||||
time you are battling some issue while working with Textual: [don't lose
|
||||
your head](https://www.youtube.com/watch?v=KdYvKF9O7Y8)!
|
||||
@@ -396,7 +396,7 @@ Below you can see the code I wrote and a short animation of the app working.
|
||||
|
||||
=== "CSS"
|
||||
|
||||
```css
|
||||
```sass
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
70
docs/css_types/_template.md
Normal file
70
docs/css_types/_template.md
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- Template file for a Textual CSS type reference page. -->
|
||||
|
||||
# <type-name>
|
||||
|
||||
<!-- Short description of the type. -->
|
||||
|
||||
## Syntax
|
||||
|
||||
|
||||
<!--
|
||||
For a simple type like <integer>:
|
||||
|
||||
Describe the type in a short paragraph with an absolute link to the type page.
|
||||
E.g., “The [`<my-type>`](/css_types/my_type) type is such and such with sprinkles on top.”
|
||||
-->
|
||||
|
||||
<!--
|
||||
For a type with many different values like <color>:
|
||||
|
||||
Introduce the type with a link to [`<my-type>`](/css_types/my_type).
|
||||
Then, a bullet list with the variants accepted:
|
||||
|
||||
- you can create this type with X Y Z;
|
||||
- you can also do A B C; and
|
||||
- also use D E F.
|
||||
-->
|
||||
|
||||
<!--
|
||||
For a type that accepts specific options like <border>:
|
||||
|
||||
Add a sentence and a table. Consider ordering values in alphabetical order if there is no other obvious ordering. See below:
|
||||
|
||||
The [`<my-type>`](/css_types/my_type) type can take any of the following values:
|
||||
|
||||
| Value | Description |
|
||||
|---------------|-----------------------------------------------|
|
||||
| `abc` | Describe here. |
|
||||
| `other val` | Describe this one also. |
|
||||
| `value three` | Please use full stops. |
|
||||
| `zyx` | Describe the value without assuming any rule. |
|
||||
-->
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
<!--
|
||||
Include a good variety of examples.
|
||||
If the type has many different syntaxes, cover all of them.
|
||||
Add comments when needed/if helpful.
|
||||
-->
|
||||
|
||||
```sass
|
||||
.some-class {
|
||||
rule: type-value-1;
|
||||
rule: type-value-2;
|
||||
rule: type-value-3;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
<!-- Same examples as above. -->
|
||||
|
||||
```py
|
||||
widget.styles.rule = type_value_1
|
||||
widget.styles.rule = type_value_2
|
||||
widget.styles.rule = type_value_3
|
||||
```
|
||||
54
docs/css_types/border.md
Normal file
54
docs/css_types/border.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# <border>
|
||||
|
||||
The `<border>` CSS type represents a border style.
|
||||
|
||||
## Syntax
|
||||
|
||||
The [`<border>`](/css_types/border) type can take any of the following values:
|
||||
|
||||
| Border type | Description |
|
||||
|-------------|----------------------------------------------------------|
|
||||
| `ascii` | A border with plus, hyphen, and vertical bar characters. |
|
||||
| `blank` | A blank border (reserves space for a border). |
|
||||
| `dashed` | Dashed line border. |
|
||||
| `double` | Double lined border. |
|
||||
| `heavy` | Heavy border. |
|
||||
| `hidden` | Alias for "none". |
|
||||
| `hkey` | Horizontal key-line border. |
|
||||
| `inner` | Thick solid border. |
|
||||
| `none` | Disabled border. |
|
||||
| `outer` | Solid border with additional space around content. |
|
||||
| `round` | Rounded corners. |
|
||||
| `solid` | Solid border. |
|
||||
| `tall` | Solid border with additional space top and bottom. |
|
||||
| `vkey` | Vertical key-line border. |
|
||||
| `wide` | Solid border with additional space left and right. |
|
||||
|
||||
## Border command
|
||||
|
||||
The `textual` CLI has a subcommand which will let you explore the various border types interactively, when applied to the CSS rule [`border`](../styles/border.md):
|
||||
|
||||
```
|
||||
textual borders
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
#container {
|
||||
border: heavy red;
|
||||
}
|
||||
|
||||
#heading {
|
||||
border-bottom: solid blue;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.border = ("heavy", "red")
|
||||
widget.styles.border_bottom = ("solid", "blue")
|
||||
```
|
||||
138
docs/css_types/color.md
Normal file
138
docs/css_types/color.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# <color>
|
||||
|
||||
The `<color>` CSS type represents a color.
|
||||
|
||||
!!! warning
|
||||
|
||||
Not to be confused with the [`color`](../styles/color.md) CSS rule to set text color.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<color>`](/css_types/color) should be in one of the formats explained in this section.
|
||||
A bullet point summary of the formats available follows:
|
||||
|
||||
- a recognised [named color](#named-colors) (e.g., `red`);
|
||||
- a 3 or 6 hexadecimal digit number representing the [RGB values](#hex-rgb-value) of the color (e.g., `#F35573`);
|
||||
- a 4 or 8 hexadecimal digit number representing the [RGBA values](#hex-rgba-value) of the color (e.g., `#F35573A0`);
|
||||
- a color description in the RGB system, [with](#rgba-description) or [without](#rgb-description) transparency (e.g., `rgb(23, 78, 200)`);
|
||||
- a color description in the HSL system, [with](#hsla-description) or [without](#hsl-description) transparency (e.g., `hsl(290, 70%, 80%)`);
|
||||
|
||||
[Textual's default themes](../../guide/design#theme-reference) also provide many CSS variables with colors that can be used out of the box.
|
||||
|
||||
### Named colors
|
||||
|
||||
A named color is a [`<name>`](./name.md) that Textual recognises.
|
||||
Below, you can find a (collapsed) list of all of the named colors that Textual recognises, along with their hexadecimal values, their RGB values, and a visual sample.
|
||||
|
||||
<details>
|
||||
<summary>All named colors available.</summary>
|
||||
|
||||
```{.rich columns="80" title="colors"}
|
||||
from textual._color_constants import COLOR_NAME_TO_RGB
|
||||
from textual.color import Color
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
table = Table("Name", "hex", "RGB", "Color", expand=True, highlight=True)
|
||||
|
||||
for name, triplet in sorted(COLOR_NAME_TO_RGB.items()):
|
||||
if len(triplet) != 3:
|
||||
continue
|
||||
color = Color(*triplet)
|
||||
r, g, b = triplet
|
||||
table.add_row(
|
||||
f'"{name}"',
|
||||
Text(f"{color.hex}", "bold green"),
|
||||
f"rgb({r}, {g}, {b})",
|
||||
Text(" ", style=f"on rgb({r},{g},{b})")
|
||||
)
|
||||
output = table
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Hex RGB value
|
||||
|
||||
The hexadecimal RGB format starts with an octothorpe `#` and is then followed by 3 or 6 hexadecimal digits: `0123456789ABCDEF`.
|
||||
Casing is ignored.
|
||||
|
||||
- If 6 digits are used, the format is `#RRGGBB`:
|
||||
- `RR` represents the red channel;
|
||||
- `GG` represents the green channel; and
|
||||
- `BB` represents the blue channel.
|
||||
- If 3 digits are used, the format is `#RGB`.
|
||||
|
||||
In a 3 digit color, each channel is represented by a single digit which is duplicated when converting to the 6 digit format.
|
||||
For example, the color `#A2F` is the same as `#AA22FF`.
|
||||
|
||||
### Hex RGBA value
|
||||
|
||||
This is the same as the [hex RGB value](#hex-rgb-value), but with an extra channel for the alpha component (that sets transparency).
|
||||
|
||||
- If 8 digits are used, the format is `#RRGGBBAA`, equivalent to the format `#RRGGBB` with two extra digits for transparency.
|
||||
- If 4 digits are used, the format is `#RGBA`, equivalent to the format `#RGB` with an extra digit for transparency.
|
||||
|
||||
### `rgb` description
|
||||
|
||||
The `rgb` format description is a functional description of a color in the RGB color space.
|
||||
This description follows the format `rgb(red, green, blue)`, where `red`, `green`, and `blue` are decimal integers between 0 and 255.
|
||||
They represent the value of the channel with the same name.
|
||||
|
||||
For example, `rgb(0, 255, 32)` is equivalent to `#00FF20`.
|
||||
|
||||
### `rgba` description
|
||||
|
||||
The `rgba` format description is the same as the `rgb` with an extra parameter for transparency, which should be a value between `0` and `1`.
|
||||
|
||||
For example, `rgba(0, 255, 32, 0.5)` is the color `rgb(0, 255, 32)` with 50% transparency.
|
||||
|
||||
### `hsl` description
|
||||
|
||||
The `hsl` format description is a functional description of a color in the HSL color space.
|
||||
This description follows the format `hsl(hue, saturation, lightness)`, where
|
||||
|
||||
- `hue` is a float between 0 and 360;
|
||||
- `saturation` is a percentage between `0%` and `100%`; and
|
||||
- `lightness` is a percentage between `0%` and `100%`.
|
||||
|
||||
For example, the color `#00FF20` would be represented as `hsl(128, 100%, 50%)` in the HSL color space.
|
||||
|
||||
### `hsla` description
|
||||
|
||||
The `hsla` format description is the same as the `hsl` with an extra parameter for transparency, which should be a value between `0` and `1`.
|
||||
|
||||
For example, `hsla(128, 100%, 50%, 0.5)` is the color `hsl(128, 100%, 50%)` with 50% transparency.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
Header {
|
||||
background: red; /* Color name */
|
||||
}
|
||||
|
||||
.accent {
|
||||
color: $accent; /* Textual variable */
|
||||
}
|
||||
|
||||
#footer {
|
||||
tint: hsl(300, 20%, 70%); /* HSL description */
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
In Python, rules that expect a `<color>` can also accept an instance of the type [`Color`][textual.color.Color].
|
||||
|
||||
```py
|
||||
# Mimicking the CSS syntax
|
||||
widget.styles.background = "red" # Color name
|
||||
widget.styles.color = "$accent" # Textual variable
|
||||
widget.styles.tint = "hsl(300, 20%, 70%)" # HSL description
|
||||
|
||||
from textual.color import Color
|
||||
# Using a Color object directly...
|
||||
color = Color(16, 200, 45)
|
||||
# ... which can also parse the CSS syntax
|
||||
color = Color.parse("#A8F")
|
||||
```
|
||||
29
docs/css_types/horizontal.md
Normal file
29
docs/css_types/horizontal.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# <horizontal>
|
||||
|
||||
The `<horizontal>` CSS type represents a position along the horizontal axis.
|
||||
|
||||
## Syntax
|
||||
|
||||
The [`<horizontal>`](/css_types/horizontal) type can take any of the following values:
|
||||
|
||||
| Value | Description |
|
||||
| ---------------- | -------------------------------------------- |
|
||||
| `center` | Aligns in the center of the horizontal axis. |
|
||||
| `left` (default) | Aligns on the left of the horizontal axis. |
|
||||
| `right` | Aligns on the right of the horizontal axis. |
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
.container {
|
||||
align-horizontal: right;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.align_horizontal = "right"
|
||||
```
|
||||
12
docs/css_types/index.md
Normal file
12
docs/css_types/index.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# CSS Types
|
||||
|
||||
CSS types define the values that Textual CSS styles accept.
|
||||
|
||||
CSS types will be linked from within the [styles reference](../styles/index.md) in the "Formal Syntax" section of each style.
|
||||
The CSS types will be denoted by a keyword enclosed by angle brackets `<` and `>`.
|
||||
|
||||
For example, the style [`align-horizontal`](../styles/align.md) references the CSS type [`<horizontal>`](./horizontal.md):
|
||||
|
||||
--8<-- "docs/snippets/syntax_block_start.md"
|
||||
align-horizontal: <a href="./horizontal.md"><horizontal></a>;
|
||||
--8<-- "docs/snippets/syntax_block_end.md"
|
||||
29
docs/css_types/integer.md
Normal file
29
docs/css_types/integer.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# <integer>
|
||||
|
||||
The `<integer>` CSS type represents an integer number.
|
||||
|
||||
## Syntax
|
||||
|
||||
An [`<integer>`](/css_types/integer) is any valid integer number like `-10` or `42`.
|
||||
|
||||
!!! note
|
||||
|
||||
Some CSS rules may expect an `<integer>` within certain bounds. If that is the case, it will be noted in that rule.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
.classname {
|
||||
offset: 10 -20
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
In Python, a rule that expects a CSS type `<integer>` will expect a value of the type `int`:
|
||||
|
||||
```py
|
||||
widget.styles.offset = (10, -20)
|
||||
```
|
||||
26
docs/css_types/name.md
Normal file
26
docs/css_types/name.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# <name>
|
||||
|
||||
The `<name>` type represents a sequence of characters that identifies something.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<name>`](/css_types/name) is any non-empty sequence of characters:
|
||||
|
||||
- starting with a letter `a-z`, `A-Z`, or underscore `_`; and
|
||||
- followed by zero or more letters `a-zA-Z`, digits `0-9`, underscores `_`, and hiphens `-`.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
Screen {
|
||||
layers: onlyLetters Letters-and-hiphens _lead-under letters-1-digit;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.layers = "onlyLetters Letters-and-hiphens _lead-under letters-1-digit"
|
||||
```
|
||||
30
docs/css_types/number.md
Normal file
30
docs/css_types/number.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# <number>
|
||||
|
||||
The `<number>` CSS type represents a real number, which can be an integer or a number with a decimal part (akin to a `float` in Python).
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<number>`](/css_types/number) is an [`<integer>`](/css_types/integer), optionally followed by the decimal point `.` and a decimal part composed of one or more digits.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
Grid {
|
||||
grid-size: 3 6 /* Integers are numbers */
|
||||
}
|
||||
|
||||
.translucid {
|
||||
opacity: 0.5 /* Numbers can have a decimal part */
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
In Python, a rule that expects a CSS type `<number>` will accept an `int` or a `float`:
|
||||
|
||||
```py
|
||||
widget.styles.grid_size = (3, 6) # Integers are numbers
|
||||
widget.styles.opacity = 0.5 # Numbers can have a decimal part
|
||||
```
|
||||
29
docs/css_types/overflow.md
Normal file
29
docs/css_types/overflow.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# <overflow>
|
||||
|
||||
The `<overflow>` CSS type represents overflow modes.
|
||||
|
||||
## Syntax
|
||||
|
||||
The [`<overflow>`](/css_types/overflow) type can take any of the following values:
|
||||
|
||||
| Value | Description |
|
||||
|----------|----------------------------------------|
|
||||
| `auto` | Determine overflow mode automatically. |
|
||||
| `hidden` | Don't overflow. |
|
||||
| `scroll` | Allow overflowing. |
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
#container {
|
||||
overflow-y: hidden; /* Don't overflow */
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.overflow_y = "hidden" # Don't overflow
|
||||
```
|
||||
37
docs/css_types/percentage.md
Normal file
37
docs/css_types/percentage.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# <percentage>
|
||||
|
||||
The `<percentage>` CSS type represents a percentage value.
|
||||
It is often used to represent values that are relative to the parent's values.
|
||||
|
||||
!!! warning
|
||||
|
||||
Not to be confused with the [`<scalar>`](./scalar.md) type.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<percentage>`](/css_types/percentage) is a [`<number>`](/css_types/number) followed by the percent sign `%` (without spaces).
|
||||
Some rules may clamp the values between `0%` and `100%`.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
#footer {
|
||||
/* Integer followed by % */
|
||||
color: red 70%;
|
||||
|
||||
/* The number can be negative/decimal, although that may not make sense */
|
||||
offset: -30% 12.5%;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
# Integer followed by %
|
||||
widget.styles.color = "red 70%"
|
||||
|
||||
# The number can be negative/decimal, althought that may not make sense
|
||||
widget.styles.offset = ("-30%", "12.5%")
|
||||
```
|
||||
113
docs/css_types/scalar.md
Normal file
113
docs/css_types/scalar.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# <scalar>
|
||||
|
||||
The `<scalar>` CSS type represents a length.
|
||||
It can be a [`<number>`](./number.md) and a unit, or the special value `auto`.
|
||||
It is used to represent lengths, for example in the [`width`](../styles/width.md) and [`height`](../styles/height.md) rules.
|
||||
|
||||
!!! warning
|
||||
|
||||
Not to be confused with the [`<number>`](./number.md) or [`<percentage>`](./percentage.md) types.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<scalar>`](/css_types/scalar) can be any of the following:
|
||||
|
||||
- a fixed number of cells (e.g., `10`);
|
||||
- a fractional proportion relative to the sizes of the other widgets (e.g., `1fr`);
|
||||
- a percentage relative to the container widget (e.g., `50%`);
|
||||
- a percentage relative to the container width/height (e.g., `25w`/`75h`);
|
||||
- a percentage relative to the viewport width/height (e.g., `25vw`/`75vh`); or
|
||||
- the special value `auto` to compute the optimal size to fit without scrolling.
|
||||
|
||||
A complete reference table and detailed explanations follow.
|
||||
You can [skip to the examples](#examples).
|
||||
|
||||
| Unit symbol | Unit | Example | Description |
|
||||
|-------------|-----------------|---------|-------------------------------------------------------------|
|
||||
| `""` | Cell | `10` | Number of cells (rows or columns). |
|
||||
| `"fr"` | Fraction | `1fr` | Specifies the proportion of space the widget should occupy. |
|
||||
| `"%"` | Percent | `75%` | Length relative to the container widget. |
|
||||
| `"w"` | Width | `25w` | Percentage relative to the width of the container widget. |
|
||||
| `"h"` | Height | `75h` | Percentage relative to the height of the container widget. |
|
||||
| `"vw"` | Viewport width | `25vw` | Percentage relative to the viewport width. |
|
||||
| `"vh"` | Viewport height | `75vh` | Percentage relative to the viewport height. |
|
||||
| - | Auto | `auto` | Tries to compute the optimal size to fit without scrolling. |
|
||||
|
||||
### Cell
|
||||
|
||||
The number of cells is the only unit for a scalar that is _absolute_.
|
||||
This can be an integer or a float but floats are truncated to integers.
|
||||
|
||||
If used to specify a horizontal length, it corresponds to the number of columns.
|
||||
For example, in `width: 15`, this sets the width of a widget to be equal to 15 cells, which translates to 15 columns.
|
||||
|
||||
If used to specify a vertical length, it corresponds to the number of lines.
|
||||
For example, in `height: 10`, this sets the height of a widget to be equal to 10 cells, which translates to 10 lines.
|
||||
|
||||
### Fraction
|
||||
|
||||
The unit fraction is used to represent proportional sizes.
|
||||
|
||||
For example, if two widgets are side by side and one has `width: 1fr` and the other has `width: 3fr`, the second one will be three times as wide as the first one.
|
||||
|
||||
### Percent
|
||||
|
||||
The percent unit matches a [`<percentage>`](./percentage.md) and is used to specify a total length relative to the space made available by the container widget.
|
||||
|
||||
If used to specify a horizontal length, it will be relative to the width of the container.
|
||||
For example, `width: 50%` sets the width of a widget to 50% of the width of its container.
|
||||
|
||||
If used to specify a vertical length, it will be relative to the height of the container.
|
||||
For example, `height: 50%` sets the height of a widget to 50% of the height of its container.
|
||||
|
||||
### Width
|
||||
|
||||
The width unit is similar to the percent unit, except it sets the percentage to be relative to the width of the container.
|
||||
|
||||
For example, `width: 25w` sets the width of a widget to 25% of the width of its container and `height: 25w` sets the height of a widget to 25% of the width of its container.
|
||||
So, if the container has a width of 100 cells, the width and the height of the child widget will be of 25 cells.
|
||||
|
||||
### Height
|
||||
|
||||
The height unit is similar to the percent unit, except it sets the percentage to be relative to the height of the container.
|
||||
|
||||
For example, `height: 75h` sets the height of a widget to 75% of the height of its container and `width: 75h` sets the width of a widget to 75% of the height of its container.
|
||||
So, if the container has a height of 100 cells, the width and the height of the child widget will be of 75 cells.
|
||||
|
||||
### Viewport width
|
||||
|
||||
This is the same as the [width unit](#width), except that it is relative to the width of the viewport instead of the width of the immediate container.
|
||||
The width of the viewport is the width of the terminal minus the widths of widgets that are docked left or right.
|
||||
|
||||
For example, `width: 25vw` will try to set the width of a widget to be 25% of the viewport width, regardless of the widths of its containers.
|
||||
|
||||
### Viewport height
|
||||
|
||||
This is the same as the [height unit](#height), except that it is relative to the height of the viewport instead of the height of the immediate container.
|
||||
The height of the viewport is the height of the terminal minus the heights of widgets that are docked top or bottom.
|
||||
|
||||
For example, `height: 75vh` will try to set the height of a widget to be 75% of the viewport height, regardless of the height of its containers.
|
||||
|
||||
### Auto
|
||||
|
||||
This special value will try to calculate the optimal size to fit the contents of the widget without scrolling.
|
||||
|
||||
For example, if its container is big enough, a label with `width: auto` will be just as wide as its text.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
Horizontal {
|
||||
width: 60; /* 60 cells */
|
||||
height: 1fr; /* proportional size of 1 */
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.width = 16 # Cell unit can be specified with an int/float
|
||||
widget.styles.height = "1fr" # proportional size of 1
|
||||
```
|
||||
40
docs/css_types/text_align.md
Normal file
40
docs/css_types/text_align.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# <text-align>
|
||||
|
||||
The `<text-align>` CSS type represents alignments that can be applied to text.
|
||||
|
||||
!!! warning
|
||||
|
||||
Not to be confused with the [`text-align`](../styles/text_align.md) CSS rule that sets the alignment of text in a widget.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<text-align>`](/css_types/text_align) can be any of the following values:
|
||||
|
||||
| Value | Alignment type |
|
||||
|-----------|--------------------------------------|
|
||||
| `center` | Center alignment. |
|
||||
| `end` | Alias for `right`. |
|
||||
| `justify` | Text is justified inside the widget. |
|
||||
| `left` | Left alignment. |
|
||||
| `right` | Right alignment. |
|
||||
| `start` | Alias for `left`. |
|
||||
|
||||
!!! tip
|
||||
|
||||
The meanings of `start` and `end` will likely change when RTL languages become supported by Textual.
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
Label {
|
||||
rule: justify;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.text_align = "justify"
|
||||
```
|
||||
46
docs/css_types/text_style.md
Normal file
46
docs/css_types/text_style.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# <text-style>
|
||||
|
||||
The `<text-style>` CSS type represents styles that can be applied to text.
|
||||
|
||||
!!! warning
|
||||
|
||||
Not to be confused with the [`text-style`](../styles/text_style.md) CSS rule that sets the style of text in a widget.
|
||||
|
||||
## Syntax
|
||||
|
||||
A [`<text-style>`](/css_types/text_style) can be any _space-separated_ combination of the following values:
|
||||
|
||||
| Value | Description |
|
||||
|-------------|-----------------------------------------------------------------|
|
||||
| `bold` | **Bold text.** |
|
||||
| `italic` | _Italic text._ |
|
||||
| `none` | Plain text with no styling. |
|
||||
| `reverse` | Reverse video text (foreground and background colors reversed). |
|
||||
| `strike` | <s>Strikethrough text.</s> |
|
||||
| `underline` | <u>Underline text.</u> |
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
#label1 {
|
||||
/* You can specify any value by itself. */
|
||||
rule: strike;
|
||||
}
|
||||
|
||||
#label2 {
|
||||
/* You can also combine multiple values. */
|
||||
rule: strike bold italic reverse;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
# You can specify any value by itself
|
||||
widget.styles.text_style = "strike"
|
||||
|
||||
# You can also combine multiple values
|
||||
widget.styles.text_style = "bold underline italic"
|
||||
```
|
||||
29
docs/css_types/vertical.md
Normal file
29
docs/css_types/vertical.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# <vertical>
|
||||
|
||||
The `<vertical>` CSS type represents a position along the vertical axis.
|
||||
|
||||
## Syntax
|
||||
|
||||
The [`<vertical>`](/css_types/vertical) type can take any of the following values:
|
||||
|
||||
| Value | Description |
|
||||
| --------------- | ------------------------------------------ |
|
||||
| `bottom` | Aligns at the bottom of the vertical axis. |
|
||||
| `middle` | Aligns in the middle of the vertical axis. |
|
||||
| `top` (default) | Aligns at the top of the vertical axis. |
|
||||
|
||||
## Examples
|
||||
|
||||
### CSS
|
||||
|
||||
```sass
|
||||
.container {
|
||||
align-vertical: top;
|
||||
}
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```py
|
||||
widget.styles.align_vertical = "top"
|
||||
```
|
||||
@@ -12,7 +12,7 @@ from textual.widgets import Static, Input
|
||||
|
||||
|
||||
class DictionaryApp(App):
|
||||
"""Searches ab dictionary API as-you-type."""
|
||||
"""Searches a dictionary API as-you-type."""
|
||||
|
||||
CSS_PATH = "dictionary.css"
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ Static {
|
||||
}
|
||||
|
||||
#box1 {
|
||||
background: darkcyan;
|
||||
layer: above;
|
||||
background: darkcyan;
|
||||
}
|
||||
|
||||
#box2 {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class AlignApp(App):
|
||||
def compose(self):
|
||||
yield Static("Vertical alignment with [b]Textual[/]", classes="box")
|
||||
yield Static("Take note, browsers.", classes="box")
|
||||
yield Label("Vertical alignment with [b]Textual[/]", classes="box")
|
||||
yield Label("Take note, browsers.", classes="box")
|
||||
|
||||
|
||||
app = AlignApp(css_path="align.css")
|
||||
|
||||
53
docs/examples/styles/align_all.css
Normal file
53
docs/examples/styles/align_all.css
Normal file
@@ -0,0 +1,53 @@
|
||||
#left-top {
|
||||
/* align: left top; this is the default value and is implied. */
|
||||
}
|
||||
|
||||
#center-top {
|
||||
align: center top;
|
||||
}
|
||||
|
||||
#right-top {
|
||||
align: right top;
|
||||
}
|
||||
|
||||
#left-middle {
|
||||
align: left middle;
|
||||
}
|
||||
|
||||
#center-middle {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#right-middle {
|
||||
align: right middle;
|
||||
}
|
||||
|
||||
#left-bottom {
|
||||
align: left bottom;
|
||||
}
|
||||
|
||||
#center-bottom {
|
||||
align: center bottom;
|
||||
}
|
||||
|
||||
#right-bottom {
|
||||
align: right bottom;
|
||||
}
|
||||
|
||||
Screen {
|
||||
layout: grid;
|
||||
grid-size: 3 3;
|
||||
grid-gutter: 1;
|
||||
}
|
||||
|
||||
Container {
|
||||
background: $boost;
|
||||
border: solid gray;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
Label {
|
||||
width: auto;
|
||||
height: 1;
|
||||
background: $accent;
|
||||
}
|
||||
20
docs/examples/styles/align_all.py
Normal file
20
docs/examples/styles/align_all.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class AlignAllApp(App):
|
||||
"""App that illustrates all alignments."""
|
||||
|
||||
CSS_PATH = "align_all.css"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Container(Label("left top"), id="left-top")
|
||||
yield Container(Label("center top"), id="center-top")
|
||||
yield Container(Label("right top"), id="right-top")
|
||||
yield Container(Label("left middle"), id="left-middle")
|
||||
yield Container(Label("center middle"), id="center-middle")
|
||||
yield Container(Label("right middle"), id="right-middle")
|
||||
yield Container(Label("left bottom"), id="left-bottom")
|
||||
yield Container(Label("center bottom"), id="center-bottom")
|
||||
yield Container(Label("right bottom"), id="right-bottom")
|
||||
@@ -1,14 +1,18 @@
|
||||
Static {
|
||||
Label {
|
||||
width: 100%;
|
||||
height: 1fr;
|
||||
content-align: center middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#static1 {
|
||||
background: red;
|
||||
}
|
||||
|
||||
#static2 {
|
||||
background: rgb(0, 255, 0);
|
||||
}
|
||||
|
||||
#static3 {
|
||||
background: hsl(240, 100%, 50%);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class BackgroundApp(App):
|
||||
def compose(self):
|
||||
yield Static("Widget 1", id="static1")
|
||||
yield Static("Widget 2", id="static2")
|
||||
yield Static("Widget 3", id="static3")
|
||||
yield Label("Widget 1", id="static1")
|
||||
yield Label("Widget 2", id="static2")
|
||||
yield Label("Widget 3", id="static3")
|
||||
|
||||
|
||||
app = BackgroundApp(css_path="background.css")
|
||||
|
||||
49
docs/examples/styles/background_transparency.css
Normal file
49
docs/examples/styles/background_transparency.css
Normal file
@@ -0,0 +1,49 @@
|
||||
#t10 {
|
||||
background: red 10%;
|
||||
}
|
||||
|
||||
#t20 {
|
||||
background: red 20%;
|
||||
}
|
||||
|
||||
#t30 {
|
||||
background: red 30%;
|
||||
}
|
||||
|
||||
#t40 {
|
||||
background: red 40%;
|
||||
}
|
||||
|
||||
#t50 {
|
||||
background: red 50%;
|
||||
}
|
||||
|
||||
#t60 {
|
||||
background: red 60%;
|
||||
}
|
||||
|
||||
#t70 {
|
||||
background: red 70%;
|
||||
}
|
||||
|
||||
#t80 {
|
||||
background: red 80%;
|
||||
}
|
||||
|
||||
#t90 {
|
||||
background: red 90%;
|
||||
}
|
||||
|
||||
#t100 {
|
||||
background: red 100%;
|
||||
}
|
||||
|
||||
Screen {
|
||||
layout: horizontal;
|
||||
}
|
||||
|
||||
Static {
|
||||
height: 100%;
|
||||
width: 1fr;
|
||||
content-align: center middle;
|
||||
}
|
||||
20
docs/examples/styles/background_transparency.py
Normal file
20
docs/examples/styles/background_transparency.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
class BackgroundTransparencyApp(App):
|
||||
"""Simple app to exemplify different transparency settings."""
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Static("10%", id="t10")
|
||||
yield Static("20%", id="t20")
|
||||
yield Static("30%", id="t30")
|
||||
yield Static("40%", id="t40")
|
||||
yield Static("50%", id="t50")
|
||||
yield Static("60%", id="t60")
|
||||
yield Static("70%", id="t70")
|
||||
yield Static("80%", id="t80")
|
||||
yield Static("90%", id="t90")
|
||||
yield Static("100%", id="t100")
|
||||
|
||||
|
||||
app = BackgroundTransparencyApp(css_path="background_transparency.css")
|
||||
@@ -1,25 +1,30 @@
|
||||
#label1 {
|
||||
background: red 20%;
|
||||
color: red;
|
||||
border: solid red;
|
||||
}
|
||||
|
||||
#label2 {
|
||||
background: green 20%;
|
||||
color: green;
|
||||
border: dashed green;
|
||||
}
|
||||
|
||||
#label3 {
|
||||
background: blue 20%;
|
||||
color: blue;
|
||||
border: tall blue;
|
||||
}
|
||||
|
||||
Screen {
|
||||
background: white;
|
||||
}
|
||||
Screen > Static {
|
||||
|
||||
Screen > Label {
|
||||
width: 100%;
|
||||
height: 5;
|
||||
content-align: center middle;
|
||||
color: white;
|
||||
margin: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#static1 {
|
||||
background: red 20%;
|
||||
color: red;
|
||||
border: solid red;
|
||||
}
|
||||
#static2 {
|
||||
background: green 20%;
|
||||
color: green;
|
||||
border: dashed green;
|
||||
}
|
||||
#static3 {
|
||||
background: blue 20%;
|
||||
color: blue;
|
||||
border: tall blue;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class BorderApp(App):
|
||||
def compose(self):
|
||||
yield Static("My border is solid red", id="static1")
|
||||
yield Static("My border is dashed green", id="static2")
|
||||
yield Static("My border is tall blue", id="static3")
|
||||
yield Label("My border is solid red", id="label1")
|
||||
yield Label("My border is dashed green", id="label2")
|
||||
yield Label("My border is tall blue", id="label3")
|
||||
|
||||
|
||||
app = BorderApp(css_path="border.css")
|
||||
|
||||
71
docs/examples/styles/border_all.css
Normal file
71
docs/examples/styles/border_all.css
Normal file
@@ -0,0 +1,71 @@
|
||||
#ascii {
|
||||
border: ascii $accent;
|
||||
}
|
||||
|
||||
#blank {
|
||||
border: blank $accent;
|
||||
}
|
||||
|
||||
#dashed {
|
||||
border: dashed $accent;
|
||||
}
|
||||
|
||||
#double {
|
||||
border: double $accent;
|
||||
}
|
||||
|
||||
#heavy {
|
||||
border: heavy $accent;
|
||||
}
|
||||
|
||||
#hidden {
|
||||
border: hidden $accent;
|
||||
}
|
||||
|
||||
#hkey {
|
||||
border: hkey $accent;
|
||||
}
|
||||
|
||||
#inner {
|
||||
border: inner $accent;
|
||||
}
|
||||
|
||||
#none {
|
||||
border: none $accent;
|
||||
}
|
||||
|
||||
#outer {
|
||||
border: outer $accent;
|
||||
}
|
||||
|
||||
#round {
|
||||
border: round $accent;
|
||||
}
|
||||
|
||||
#solid {
|
||||
border: solid $accent;
|
||||
}
|
||||
|
||||
#tall {
|
||||
border: tall $accent;
|
||||
}
|
||||
|
||||
#vkey {
|
||||
border: vkey $accent;
|
||||
}
|
||||
|
||||
#wide {
|
||||
border: wide $accent;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 3 5;
|
||||
align: center middle;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Label {
|
||||
width: 20;
|
||||
height: 3;
|
||||
content-align: center middle;
|
||||
}
|
||||
26
docs/examples/styles/border_all.py
Normal file
26
docs/examples/styles/border_all.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class AllBordersApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("ascii", id="ascii"),
|
||||
Label("blank", id="blank"),
|
||||
Label("dashed", id="dashed"),
|
||||
Label("double", id="double"),
|
||||
Label("heavy", id="heavy"),
|
||||
Label("hidden/none", id="hidden"),
|
||||
Label("hkey", id="hkey"),
|
||||
Label("inner", id="inner"),
|
||||
Label("none", id="none"),
|
||||
Label("outer", id="outer"),
|
||||
Label("round", id="round"),
|
||||
Label("solid", id="solid"),
|
||||
Label("tall", id="tall"),
|
||||
Label("vkey", id="vkey"),
|
||||
Label("wide", id="wide"),
|
||||
)
|
||||
|
||||
app = AllBordersApp(css_path="border_all.css")
|
||||
@@ -1,7 +1,16 @@
|
||||
#static1 {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#static2 {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
Screen {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
App Static {
|
||||
background: blue 20%;
|
||||
height: 5;
|
||||
@@ -9,9 +18,3 @@ App Static {
|
||||
padding: 1;
|
||||
border: wide black;
|
||||
}
|
||||
#static1 {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#static2 {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
Static {
|
||||
Label {
|
||||
height: 1fr;
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
}
|
||||
#static1 {
|
||||
|
||||
#label1 {
|
||||
color: red;
|
||||
}
|
||||
#static2 {
|
||||
|
||||
#label2 {
|
||||
color: rgb(0, 255, 0);
|
||||
}
|
||||
#static3 {
|
||||
color: hsl(240, 100%, 50%)
|
||||
|
||||
#label3 {
|
||||
color: hsl(240, 100%, 50%);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class ColorApp(App):
|
||||
def compose(self):
|
||||
yield Static("I'm red!", id="static1")
|
||||
yield Static("I'm rgb(0, 255, 0)!", id="static2")
|
||||
yield Static("I'm hsl(240, 100%, 50%)!", id="static3")
|
||||
yield Label("I'm red!", id="label1")
|
||||
yield Label("I'm rgb(0, 255, 0)!", id="label2")
|
||||
yield Label("I'm hsl(240, 100%, 50%)!", id="label3")
|
||||
|
||||
|
||||
app = ColorApp(css_path="color.css")
|
||||
|
||||
26
docs/examples/styles/color_auto.css
Normal file
26
docs/examples/styles/color_auto.css
Normal file
@@ -0,0 +1,26 @@
|
||||
Label {
|
||||
color: auto 80%;
|
||||
content-align: center middle;
|
||||
height: 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#lbl1 {
|
||||
background: red 80%;
|
||||
}
|
||||
|
||||
#lbl2 {
|
||||
background: yellow 80%;
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
background: blue 80%;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
background: pink 80%;
|
||||
}
|
||||
|
||||
#lbl5 {
|
||||
background: green 80%;
|
||||
}
|
||||
14
docs/examples/styles/color_auto.py
Normal file
14
docs/examples/styles/color_auto.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class ColorApp(App):
|
||||
def compose(self):
|
||||
yield Label("The quick brown fox jumps over the lazy dog!", id="lbl1")
|
||||
yield Label("The quick brown fox jumps over the lazy dog!", id="lbl2")
|
||||
yield Label("The quick brown fox jumps over the lazy dog!", id="lbl3")
|
||||
yield Label("The quick brown fox jumps over the lazy dog!", id="lbl4")
|
||||
yield Label("The quick brown fox jumps over the lazy dog!", id="lbl5")
|
||||
|
||||
|
||||
app = ColorApp(css_path="color_auto.css")
|
||||
30
docs/examples/styles/column_span.css
Normal file
30
docs/examples/styles/column_span.css
Normal file
@@ -0,0 +1,30 @@
|
||||
#p1 {
|
||||
column-span: 4;
|
||||
}
|
||||
#p2 {
|
||||
column-span: 3;
|
||||
}
|
||||
#p3 {
|
||||
column-span: 1; /* Didn't need to be set explicitly. */
|
||||
}
|
||||
#p4 {
|
||||
column-span: 2;
|
||||
}
|
||||
#p5 {
|
||||
column-span: 2;
|
||||
}
|
||||
#p6 {
|
||||
/* Default value is 1. */
|
||||
}
|
||||
#p7 {
|
||||
column-span: 3;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 4 4;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
height: 100%;
|
||||
}
|
||||
19
docs/examples/styles/column_span.py
Normal file
19
docs/examples/styles/column_span.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Placeholder(id="p1"),
|
||||
Placeholder(id="p2"),
|
||||
Placeholder(id="p3"),
|
||||
Placeholder(id="p4"),
|
||||
Placeholder(id="p5"),
|
||||
Placeholder(id="p6"),
|
||||
Placeholder(id="p7"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="column_span.css")
|
||||
@@ -4,7 +4,8 @@
|
||||
}
|
||||
|
||||
#box2 {
|
||||
content-align: center middle;
|
||||
content-align-horizontal: center;
|
||||
content-align-vertical: middle;
|
||||
background: green;
|
||||
}
|
||||
|
||||
@@ -13,7 +14,8 @@
|
||||
background: blue;
|
||||
}
|
||||
|
||||
Static {
|
||||
Label {
|
||||
width: 100%;
|
||||
height: 1fr;
|
||||
padding: 1;
|
||||
color: white;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class ContentAlignApp(App):
|
||||
def compose(self):
|
||||
yield Static("With [i]content-align[/] you can...", id="box1")
|
||||
yield Static("...[b]Easily align content[/]...", id="box2")
|
||||
yield Static("...Horizontally [i]and[/] vertically!", id="box3")
|
||||
yield Label("With [i]content-align[/] you can...", id="box1")
|
||||
yield Label("...[b]Easily align content[/]...", id="box2")
|
||||
yield Label("...Horizontally [i]and[/] vertically!", id="box3")
|
||||
|
||||
|
||||
app = ContentAlignApp(css_path="content_align.css")
|
||||
|
||||
39
docs/examples/styles/content_align_all.css
Normal file
39
docs/examples/styles/content_align_all.css
Normal file
@@ -0,0 +1,39 @@
|
||||
#left-top {
|
||||
/* content-align: left top; this is the default implied value. */
|
||||
}
|
||||
#center-top {
|
||||
content-align: center top;
|
||||
}
|
||||
#right-top {
|
||||
content-align: right top;
|
||||
}
|
||||
#left-middle {
|
||||
content-align: left middle;
|
||||
}
|
||||
#center-middle {
|
||||
content-align: center middle;
|
||||
}
|
||||
#right-middle {
|
||||
content-align: right middle;
|
||||
}
|
||||
#left-bottom {
|
||||
content-align: left bottom;
|
||||
}
|
||||
#center-bottom {
|
||||
content-align: center bottom;
|
||||
}
|
||||
#right-bottom {
|
||||
content-align: right bottom;
|
||||
}
|
||||
|
||||
Screen {
|
||||
layout: grid;
|
||||
grid-size: 3 3;
|
||||
grid-gutter: 1;
|
||||
}
|
||||
|
||||
Label {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: $primary;
|
||||
}
|
||||
18
docs/examples/styles/content_align_all.py
Normal file
18
docs/examples/styles/content_align_all.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class AllContentAlignApp(App):
|
||||
def compose(self):
|
||||
yield Label("left top", id="left-top")
|
||||
yield Label("center top", id="center-top")
|
||||
yield Label("right top", id="right-top")
|
||||
yield Label("left middle", id="left-middle")
|
||||
yield Label("center middle", id="center-middle")
|
||||
yield Label("right middle", id="right-middle")
|
||||
yield Label("left bottom", id="left-bottom")
|
||||
yield Label("center bottom", id="center-bottom")
|
||||
yield Label("right bottom", id="right-bottom")
|
||||
|
||||
|
||||
app = AllContentAlignApp(css_path="content_align_all.css")
|
||||
@@ -1,12 +1,14 @@
|
||||
Screen {
|
||||
background: green;
|
||||
}
|
||||
|
||||
Static {
|
||||
height: 5;
|
||||
background: white;
|
||||
color: blue;
|
||||
border: heavy blue;
|
||||
}
|
||||
|
||||
Static.remove {
|
||||
display: none;
|
||||
}
|
||||
|
||||
34
docs/examples/styles/dock_all.css
Normal file
34
docs/examples/styles/dock_all.css
Normal file
@@ -0,0 +1,34 @@
|
||||
#left {
|
||||
dock: left;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
align-vertical: middle;
|
||||
}
|
||||
#top {
|
||||
dock: top;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
align-horizontal: center;
|
||||
}
|
||||
#right {
|
||||
dock: right;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
align-vertical: middle;
|
||||
}
|
||||
#bottom {
|
||||
dock: bottom;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
align-horizontal: center;
|
||||
}
|
||||
|
||||
Screen {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
#big_container {
|
||||
width: 75%;
|
||||
height: 75%;
|
||||
border: round white;
|
||||
}
|
||||
17
docs/examples/styles/dock_all.py
Normal file
17
docs/examples/styles/dock_all.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class DockAllApp(App):
|
||||
def compose(self):
|
||||
yield Container(
|
||||
Container(Label("left"), id="left"),
|
||||
Container(Label("top"), id="top"),
|
||||
Container(Label("right"), id="right"),
|
||||
Container(Label("bottom"), id="bottom"),
|
||||
id="big_container",
|
||||
)
|
||||
|
||||
|
||||
app = DockAllApp(css_path="dock_all.css")
|
||||
11
docs/examples/styles/grid_columns.css
Normal file
11
docs/examples/styles/grid_columns.css
Normal file
@@ -0,0 +1,11 @@
|
||||
Grid {
|
||||
grid-size: 5 2;
|
||||
grid-columns: 1fr 16 2fr;
|
||||
}
|
||||
|
||||
Label {
|
||||
border: round white;
|
||||
content-align-horizontal: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
22
docs/examples/styles/grid_columns.py
Normal file
22
docs/examples/styles/grid_columns.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("1fr"),
|
||||
Label("width = 16"),
|
||||
Label("2fr"),
|
||||
Label("1fr"),
|
||||
Label("width = 16"),
|
||||
Label("1fr"),
|
||||
Label("width = 16"),
|
||||
Label("2fr"),
|
||||
Label("1fr"),
|
||||
Label("width = 16"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="grid_columns.css")
|
||||
11
docs/examples/styles/grid_gutter.css
Normal file
11
docs/examples/styles/grid_gutter.css
Normal file
@@ -0,0 +1,11 @@
|
||||
Grid {
|
||||
grid-size: 2 4;
|
||||
grid-gutter: 1 2; /* (1)! */
|
||||
}
|
||||
|
||||
Label {
|
||||
border: round white;
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
20
docs/examples/styles/grid_gutter.py
Normal file
20
docs/examples/styles/grid_gutter.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("1"),
|
||||
Label("2"),
|
||||
Label("3"),
|
||||
Label("4"),
|
||||
Label("5"),
|
||||
Label("6"),
|
||||
Label("7"),
|
||||
Label("8"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="grid_gutter.css")
|
||||
11
docs/examples/styles/grid_rows.css
Normal file
11
docs/examples/styles/grid_rows.css
Normal file
@@ -0,0 +1,11 @@
|
||||
Grid {
|
||||
grid-size: 2 5;
|
||||
grid-rows: 1fr 6 25%;
|
||||
}
|
||||
|
||||
Label {
|
||||
border: round white;
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
22
docs/examples/styles/grid_rows.py
Normal file
22
docs/examples/styles/grid_rows.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("1fr"),
|
||||
Label("1fr"),
|
||||
Label("height = 6"),
|
||||
Label("height = 6"),
|
||||
Label("25%"),
|
||||
Label("25%"),
|
||||
Label("1fr"),
|
||||
Label("1fr"),
|
||||
Label("height = 6"),
|
||||
Label("height = 6"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="grid_rows.css")
|
||||
10
docs/examples/styles/grid_size_both.css
Normal file
10
docs/examples/styles/grid_size_both.css
Normal file
@@ -0,0 +1,10 @@
|
||||
Grid {
|
||||
grid-size: 2 4; /* (1)! */
|
||||
}
|
||||
|
||||
Label {
|
||||
border: round white;
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
17
docs/examples/styles/grid_size_both.py
Normal file
17
docs/examples/styles/grid_size_both.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("1"),
|
||||
Label("2"),
|
||||
Label("3"),
|
||||
Label("4"),
|
||||
Label("5"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="grid_size_both.css")
|
||||
10
docs/examples/styles/grid_size_columns.css
Normal file
10
docs/examples/styles/grid_size_columns.css
Normal file
@@ -0,0 +1,10 @@
|
||||
Grid {
|
||||
grid-size: 2; /* (1)! */
|
||||
}
|
||||
|
||||
Label {
|
||||
border: round white;
|
||||
content-align: center middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
17
docs/examples/styles/grid_size_columns.py
Normal file
17
docs/examples/styles/grid_size_columns.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("1"),
|
||||
Label("2"),
|
||||
Label("3"),
|
||||
Label("4"),
|
||||
Label("5"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="grid_size_columns.css")
|
||||
39
docs/examples/styles/height_comparison.css
Normal file
39
docs/examples/styles/height_comparison.css
Normal file
@@ -0,0 +1,39 @@
|
||||
#cells {
|
||||
height: 2; /* (1)! */
|
||||
}
|
||||
#percent {
|
||||
height: 12.5%; /* (2)! */
|
||||
}
|
||||
#w {
|
||||
height: 5w; /* (3)! */
|
||||
}
|
||||
#h {
|
||||
height: 12.5h; /* (4)! */
|
||||
}
|
||||
#vw {
|
||||
height: 6.25vw; /* (5)! */
|
||||
}
|
||||
#vh {
|
||||
height: 12.5vh; /* (6)! */
|
||||
}
|
||||
#auto {
|
||||
height: auto; /* (7)! */
|
||||
}
|
||||
#fr1 {
|
||||
height: 1fr; /* (8)! */
|
||||
}
|
||||
#fr2 {
|
||||
height: 2fr; /* (9)! */
|
||||
}
|
||||
|
||||
Screen {
|
||||
layers: ruler;
|
||||
}
|
||||
|
||||
Ruler {
|
||||
layer: ruler;
|
||||
dock: right;
|
||||
overflow: hidden;
|
||||
width: 1;
|
||||
background: $accent;
|
||||
}
|
||||
28
docs/examples/styles/height_comparison.py
Normal file
28
docs/examples/styles/height_comparison.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Placeholder, Label, Static
|
||||
|
||||
|
||||
class Ruler(Static):
|
||||
def compose(self):
|
||||
ruler_text = "·\n·\n·\n·\n•\n" * 100
|
||||
yield Label(ruler_text)
|
||||
|
||||
|
||||
class HeightComparisonApp(App):
|
||||
def compose(self):
|
||||
yield Vertical(
|
||||
Placeholder(id="cells"), # (1)!
|
||||
Placeholder(id="percent"),
|
||||
Placeholder(id="w"),
|
||||
Placeholder(id="h"),
|
||||
Placeholder(id="vw"),
|
||||
Placeholder(id="vh"),
|
||||
Placeholder(id="auto"),
|
||||
Placeholder(id="fr1"),
|
||||
Placeholder(id="fr2"),
|
||||
)
|
||||
yield Ruler()
|
||||
|
||||
|
||||
app = HeightComparisonApp(css_path="height_comparison.css")
|
||||
@@ -10,7 +10,7 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
Static {
|
||||
Label {
|
||||
margin: 1;
|
||||
width: 12;
|
||||
color: black;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LayoutApp(App):
|
||||
def compose(self):
|
||||
yield Container(
|
||||
Static("Layout"),
|
||||
Static("Is"),
|
||||
Static("Vertical"),
|
||||
Label("Layout"),
|
||||
Label("Is"),
|
||||
Label("Vertical"),
|
||||
id="vertical-layout",
|
||||
)
|
||||
yield Container(
|
||||
Static("Layout"),
|
||||
Static("Is"),
|
||||
Static("Horizontal"),
|
||||
Label("Layout"),
|
||||
Label("Is"),
|
||||
Label("Horizontal"),
|
||||
id="horizontal-layout",
|
||||
)
|
||||
|
||||
|
||||
11
docs/examples/styles/link_background.css
Normal file
11
docs/examples/styles/link_background.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-background: red; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-background: hsl(60,100%,50%) 50%;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
link-background: $accent;
|
||||
}
|
||||
25
docs/examples/styles/link_background.py
Normal file
25
docs/examples/styles/link_background.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkBackgroundApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkBackgroundApp(css_path="link_background.css")
|
||||
11
docs/examples/styles/link_color.css
Normal file
11
docs/examples/styles/link_color.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-color: red; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-color: hsl(60,100%,50%) 50%;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
link-color: $accent;
|
||||
}
|
||||
25
docs/examples/styles/link_color.py
Normal file
25
docs/examples/styles/link_color.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkColorApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkColorApp(css_path="link_color.css")
|
||||
11
docs/examples/styles/link_hover_background.css
Normal file
11
docs/examples/styles/link_hover_background.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-hover-background: red; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-hover-background: hsl(60,100%,50%) 50%;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
/* Empty to show the default hover background */ /* (2)! */
|
||||
}
|
||||
25
docs/examples/styles/link_hover_background.py
Normal file
25
docs/examples/styles/link_hover_background.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkHoverBackgroundApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkHoverBackgroundApp(css_path="link_hover_background.css")
|
||||
11
docs/examples/styles/link_hover_color.css
Normal file
11
docs/examples/styles/link_hover_color.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-hover-color: red; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-hover-color: hsl(60,100%,50%) 50%;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
link-hover-color: black;
|
||||
}
|
||||
25
docs/examples/styles/link_hover_color.py
Normal file
25
docs/examples/styles/link_hover_color.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkHoverColorApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkHoverColorApp(css_path="link_hover_color.css")
|
||||
11
docs/examples/styles/link_hover_style.css
Normal file
11
docs/examples/styles/link_hover_style.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-hover-style: bold italic; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-hover-style: reverse strike;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
link-hover-style: bold;
|
||||
}
|
||||
25
docs/examples/styles/link_hover_style.py
Normal file
25
docs/examples/styles/link_hover_style.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkHoverStyleApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkHoverStyleApp(css_path="link_hover_style.css")
|
||||
11
docs/examples/styles/link_style.css
Normal file
11
docs/examples/styles/link_style.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#lbl1, #lbl2 {
|
||||
link-style: bold italic; /* (1)! */
|
||||
}
|
||||
|
||||
#lbl3 {
|
||||
link-style: reverse strike;
|
||||
}
|
||||
|
||||
#lbl4 {
|
||||
link-style: bold;
|
||||
}
|
||||
25
docs/examples/styles/link_style.py
Normal file
25
docs/examples/styles/link_style.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class LinkStyleApp(App):
|
||||
def compose(self):
|
||||
yield Label(
|
||||
"Visit the [link=https://textualize.io]Textualize[/link] website.",
|
||||
id="lbl1", # (1)!
|
||||
)
|
||||
yield Label(
|
||||
"Click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl2", # (2)!
|
||||
)
|
||||
yield Label(
|
||||
"You can also click [@click=app.bell]here[/] for the bell sound.",
|
||||
id="lbl3", # (3)!
|
||||
)
|
||||
yield Label(
|
||||
"[@click=app.quit]Exit this application.[/]",
|
||||
id="lbl4", # (4)!
|
||||
)
|
||||
|
||||
|
||||
app = LinkStyleApp(css_path="link_style.css")
|
||||
@@ -3,8 +3,9 @@ Screen {
|
||||
color: black;
|
||||
}
|
||||
|
||||
Static {
|
||||
Label {
|
||||
margin: 4 8;
|
||||
background: blue 20%;
|
||||
border: blue wide;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
@@ -12,7 +12,7 @@ Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
class MarginApp(App):
|
||||
def compose(self):
|
||||
yield Static(TEXT)
|
||||
yield Label(TEXT)
|
||||
|
||||
|
||||
app = MarginApp(css_path="margin.css")
|
||||
|
||||
54
docs/examples/styles/margin_all.css
Normal file
54
docs/examples/styles/margin_all.css
Normal file
@@ -0,0 +1,54 @@
|
||||
Screen {
|
||||
background: $background;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 4;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
Container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border: white round;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
/* default is no margin */
|
||||
}
|
||||
|
||||
#p2 {
|
||||
margin: 1;
|
||||
}
|
||||
|
||||
#p3 {
|
||||
margin: 1 5;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
margin: 1 1 2 6;
|
||||
}
|
||||
|
||||
#p5 {
|
||||
margin-top: 4;
|
||||
}
|
||||
|
||||
#p6 {
|
||||
margin-right: 3;
|
||||
}
|
||||
|
||||
#p7 {
|
||||
margin-bottom: 4;
|
||||
}
|
||||
|
||||
#p8 {
|
||||
margin-left: 3;
|
||||
}
|
||||
20
docs/examples/styles/margin_all.py
Normal file
20
docs/examples/styles/margin_all.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Container, Grid
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MarginAllApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Container(Placeholder("no margin", id="p1"), classes="bordered"),
|
||||
Container(Placeholder("margin: 1", id="p2"), classes="bordered"),
|
||||
Container(Placeholder("margin: 1 5", id="p3"), classes="bordered"),
|
||||
Container(Placeholder("margin: 1 1 2 6", id="p4"), classes="bordered"),
|
||||
Container(Placeholder("margin-top: 4", id="p5"), classes="bordered"),
|
||||
Container(Placeholder("margin-right: 3", id="p6"), classes="bordered"),
|
||||
Container(Placeholder("margin-bottom: 4", id="p7"), classes="bordered"),
|
||||
Container(Placeholder("margin-left: 3", id="p8"), classes="bordered"),
|
||||
)
|
||||
|
||||
|
||||
app = MarginAllApp(css_path="margin_all.css")
|
||||
25
docs/examples/styles/max_height.css
Normal file
25
docs/examples/styles/max_height.css
Normal file
@@ -0,0 +1,25 @@
|
||||
Horizontal {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
height: 100%;
|
||||
width: 1fr;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
max-height: 10w;
|
||||
}
|
||||
|
||||
#p2 {
|
||||
max-height: 999; /* (1)! */
|
||||
}
|
||||
|
||||
#p3 {
|
||||
max-height: 50%;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
max-height: 10;
|
||||
}
|
||||
16
docs/examples/styles/max_height.py
Normal file
16
docs/examples/styles/max_height.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Horizontal
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MaxHeightApp(App):
|
||||
def compose(self):
|
||||
yield Horizontal(
|
||||
Placeholder("max-height: 10w", id="p1"),
|
||||
Placeholder("max-height: 999", id="p2"),
|
||||
Placeholder("max-height: 50%", id="p3"),
|
||||
Placeholder("max-height: 10", id="p4"),
|
||||
)
|
||||
|
||||
|
||||
app = MaxHeightApp(css_path="max_height.css")
|
||||
25
docs/examples/styles/max_width.css
Normal file
25
docs/examples/styles/max_width.css
Normal file
@@ -0,0 +1,25 @@
|
||||
Horizontal {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
width: 100%;
|
||||
height: 1fr;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
max-width: 50h;
|
||||
}
|
||||
|
||||
#p2 {
|
||||
max-width: 999; /* (1)! */
|
||||
}
|
||||
|
||||
#p3 {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
max-width: 30;
|
||||
}
|
||||
16
docs/examples/styles/max_width.py
Normal file
16
docs/examples/styles/max_width.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MaxWidthApp(App):
|
||||
def compose(self):
|
||||
yield Vertical(
|
||||
Placeholder("max-width: 50h", id="p1"),
|
||||
Placeholder("max-width: 999", id="p2"),
|
||||
Placeholder("max-width: 50%", id="p3"),
|
||||
Placeholder("max-width: 30", id="p4"),
|
||||
)
|
||||
|
||||
|
||||
app = MaxWidthApp(css_path="max_width.css")
|
||||
26
docs/examples/styles/min_height.css
Normal file
26
docs/examples/styles/min_height.css
Normal file
@@ -0,0 +1,26 @@
|
||||
Horizontal {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
width: 1fr;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
min-height: 25%; /* (1)! */
|
||||
}
|
||||
|
||||
#p2 {
|
||||
min-height: 75%;
|
||||
}
|
||||
|
||||
#p3 {
|
||||
min-height: 30;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
min-height: 40w;
|
||||
}
|
||||
16
docs/examples/styles/min_height.py
Normal file
16
docs/examples/styles/min_height.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Horizontal
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MinHeightApp(App):
|
||||
def compose(self):
|
||||
yield Horizontal(
|
||||
Placeholder("min-height: 25%", id="p1"),
|
||||
Placeholder("min-height: 75%", id="p2"),
|
||||
Placeholder("min-height: 30", id="p3"),
|
||||
Placeholder("min-height: 40w", id="p4"),
|
||||
)
|
||||
|
||||
|
||||
app = MinHeightApp(css_path="min_height.css")
|
||||
26
docs/examples/styles/min_width.css
Normal file
26
docs/examples/styles/min_width.css
Normal file
@@ -0,0 +1,26 @@
|
||||
Vertical {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
height: 1fr;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
min-width: 25%; /* (1)! */
|
||||
}
|
||||
|
||||
#p2 {
|
||||
min-width: 75%;
|
||||
}
|
||||
|
||||
#p3 {
|
||||
min-width: 100;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
min-width: 400h;
|
||||
}
|
||||
16
docs/examples/styles/min_width.py
Normal file
16
docs/examples/styles/min_width.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Vertical
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MinWidthApp(App):
|
||||
def compose(self):
|
||||
yield Vertical(
|
||||
Placeholder("min-width: 25%", id="p1"),
|
||||
Placeholder("min-width: 75%", id="p2"),
|
||||
Placeholder("min-width: 100", id="p3"),
|
||||
Placeholder("min-width: 400h", id="p4"),
|
||||
)
|
||||
|
||||
|
||||
app = MinWidthApp(css_path="min_width.css")
|
||||
@@ -3,7 +3,7 @@ Screen {
|
||||
color: black;
|
||||
layout: horizontal;
|
||||
}
|
||||
Static {
|
||||
Label {
|
||||
width: 20;
|
||||
height: 10;
|
||||
content-align: center middle;
|
||||
@@ -24,7 +24,7 @@ Static {
|
||||
}
|
||||
|
||||
.chani {
|
||||
offset: 0 5;
|
||||
offset: 0 -3;
|
||||
background: blue 20%;
|
||||
border: outer blue;
|
||||
color: blue;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class OffsetApp(App):
|
||||
def compose(self):
|
||||
yield Static("Paul (offset 8 2)", classes="paul")
|
||||
yield Static("Duncan (offset 4 10)", classes="duncan")
|
||||
yield Static("Chani (offset 0 5)", classes="chani")
|
||||
yield Label("Paul (offset 8 2)", classes="paul")
|
||||
yield Label("Duncan (offset 4 10)", classes="duncan")
|
||||
yield Label("Chani (offset 0 -3)", classes="chani")
|
||||
|
||||
|
||||
app = OffsetApp(css_path="offset.css")
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
}
|
||||
|
||||
Screen {
|
||||
background: antiquewhite;
|
||||
background: black;
|
||||
}
|
||||
|
||||
Static {
|
||||
Label {
|
||||
width: 100%;
|
||||
height: 1fr;
|
||||
border: outer dodgerblue;
|
||||
background: lightseagreen;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class OpacityApp(App):
|
||||
def compose(self):
|
||||
yield Static("opacity: 0%", id="zero-opacity")
|
||||
yield Static("opacity: 25%", id="quarter-opacity")
|
||||
yield Static("opacity: 50%", id="half-opacity")
|
||||
yield Static("opacity: 75%", id="three-quarter-opacity")
|
||||
yield Static("opacity: 100%", id="full-opacity")
|
||||
yield Label("opacity: 0%", id="zero-opacity")
|
||||
yield Label("opacity: 25%", id="quarter-opacity")
|
||||
yield Label("opacity: 50%", id="half-opacity")
|
||||
yield Label("opacity: 75%", id="three-quarter-opacity")
|
||||
yield Label("opacity: 100%", id="full-opacity")
|
||||
|
||||
|
||||
app = OpacityApp(css_path="opacity.css")
|
||||
|
||||
@@ -2,8 +2,10 @@ Screen {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
Static {
|
||||
|
||||
Label {
|
||||
margin: 4 8;
|
||||
background: green 20%;
|
||||
outline: wide green;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
TEXT = """I must not fear.
|
||||
@@ -13,7 +13,7 @@ Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
class OutlineApp(App):
|
||||
def compose(self):
|
||||
yield Static(TEXT)
|
||||
yield Label(TEXT)
|
||||
|
||||
|
||||
app = OutlineApp(css_path="outline.css")
|
||||
|
||||
71
docs/examples/styles/outline_all.css
Normal file
71
docs/examples/styles/outline_all.css
Normal file
@@ -0,0 +1,71 @@
|
||||
#ascii {
|
||||
outline: ascii $accent;
|
||||
}
|
||||
|
||||
#blank {
|
||||
outline: blank $accent;
|
||||
}
|
||||
|
||||
#dashed {
|
||||
outline: dashed $accent;
|
||||
}
|
||||
|
||||
#double {
|
||||
outline: double $accent;
|
||||
}
|
||||
|
||||
#heavy {
|
||||
outline: heavy $accent;
|
||||
}
|
||||
|
||||
#hidden {
|
||||
outline: hidden $accent;
|
||||
}
|
||||
|
||||
#hkey {
|
||||
outline: hkey $accent;
|
||||
}
|
||||
|
||||
#inner {
|
||||
outline: inner $accent;
|
||||
}
|
||||
|
||||
#none {
|
||||
outline: none $accent;
|
||||
}
|
||||
|
||||
#outer {
|
||||
outline: outer $accent;
|
||||
}
|
||||
|
||||
#round {
|
||||
outline: round $accent;
|
||||
}
|
||||
|
||||
#solid {
|
||||
outline: solid $accent;
|
||||
}
|
||||
|
||||
#tall {
|
||||
outline: tall $accent;
|
||||
}
|
||||
|
||||
#vkey {
|
||||
outline: vkey $accent;
|
||||
}
|
||||
|
||||
#wide {
|
||||
outline: wide $accent;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 3 5;
|
||||
align: center middle;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Label {
|
||||
width: 20;
|
||||
height: 3;
|
||||
content-align: center middle;
|
||||
}
|
||||
26
docs/examples/styles/outline_all.py
Normal file
26
docs/examples/styles/outline_all.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class AllOutlinesApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Label("ascii", id="ascii"),
|
||||
Label("blank", id="blank"),
|
||||
Label("dashed", id="dashed"),
|
||||
Label("double", id="double"),
|
||||
Label("heavy", id="heavy"),
|
||||
Label("hidden/none", id="hidden"),
|
||||
Label("hkey", id="hkey"),
|
||||
Label("inner", id="inner"),
|
||||
Label("none", id="none"),
|
||||
Label("outer", id="outer"),
|
||||
Label("round", id="round"),
|
||||
Label("solid", id="solid"),
|
||||
Label("tall", id="tall"),
|
||||
Label("vkey", id="vkey"),
|
||||
Label("wide", id="wide"),
|
||||
)
|
||||
|
||||
app = AllOutlinesApp(css_path="outline_all.css")
|
||||
11
docs/examples/styles/outline_vs_border.css
Normal file
11
docs/examples/styles/outline_vs_border.css
Normal file
@@ -0,0 +1,11 @@
|
||||
Label {
|
||||
height: 8;
|
||||
}
|
||||
|
||||
.outline {
|
||||
outline: $error round;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: $success heavy;
|
||||
}
|
||||
21
docs/examples/styles/outline_vs_border.py
Normal file
21
docs/examples/styles/outline_vs_border.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
Fear is the little-death that brings total obliteration.
|
||||
I will face my fear.
|
||||
I will permit it to pass over me and through me.
|
||||
And when it has gone past, I will turn the inner eye to see its path.
|
||||
Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
|
||||
class OutlineBorderApp(App):
|
||||
def compose(self):
|
||||
yield Label(TEXT, classes="outline")
|
||||
yield Label(TEXT, classes="border")
|
||||
yield Label(TEXT, classes="outline border")
|
||||
|
||||
|
||||
app = OutlineBorderApp(css_path="outline_vs_border.css")
|
||||
@@ -3,7 +3,8 @@ Screen {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
Static {
|
||||
Label {
|
||||
padding: 4 8;
|
||||
background: blue 20%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from textual.app import App
|
||||
from textual.widgets import Static
|
||||
from textual.widgets import Label
|
||||
|
||||
TEXT = """I must not fear.
|
||||
Fear is the mind-killer.
|
||||
@@ -12,7 +12,7 @@ Where the fear has gone there will be nothing. Only I will remain."""
|
||||
|
||||
class PaddingApp(App):
|
||||
def compose(self):
|
||||
yield Static(TEXT)
|
||||
yield Label(TEXT)
|
||||
|
||||
|
||||
app = PaddingApp(css_path="padding.css")
|
||||
|
||||
45
docs/examples/styles/padding_all.css
Normal file
45
docs/examples/styles/padding_all.css
Normal file
@@ -0,0 +1,45 @@
|
||||
Screen {
|
||||
background: $background;
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 4;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#p1 {
|
||||
/* default is no padding */
|
||||
}
|
||||
|
||||
#p2 {
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#p3 {
|
||||
padding: 1 5;
|
||||
}
|
||||
|
||||
#p4 {
|
||||
padding: 1 1 2 6;
|
||||
}
|
||||
|
||||
#p5 {
|
||||
padding-top: 4;
|
||||
}
|
||||
|
||||
#p6 {
|
||||
padding-right: 3;
|
||||
}
|
||||
|
||||
#p7 {
|
||||
padding-bottom: 4;
|
||||
}
|
||||
|
||||
#p8 {
|
||||
padding-left: 3;
|
||||
}
|
||||
20
docs/examples/styles/padding_all.py
Normal file
20
docs/examples/styles/padding_all.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Container, Grid
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class PaddingAllApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Placeholder("no padding", id="p1"),
|
||||
Placeholder("padding: 1", id="p2"),
|
||||
Placeholder("padding: 1 5", id="p3"),
|
||||
Placeholder("padding: 1 1 2 6", id="p4"),
|
||||
Placeholder("padding-top: 4", id="p5"),
|
||||
Placeholder("padding-right: 3", id="p6"),
|
||||
Placeholder("padding-bottom: 4", id="p7"),
|
||||
Placeholder("padding-left: 3", id="p8"),
|
||||
)
|
||||
|
||||
|
||||
app = PaddingAllApp(css_path="padding_all.css")
|
||||
30
docs/examples/styles/row_span.css
Normal file
30
docs/examples/styles/row_span.css
Normal file
@@ -0,0 +1,30 @@
|
||||
#p1 {
|
||||
row-span: 4;
|
||||
}
|
||||
#p2 {
|
||||
row-span: 3;
|
||||
}
|
||||
#p3 {
|
||||
row-span: 2;
|
||||
}
|
||||
#p4 {
|
||||
row-span: 1; /* Didn't need to be set explicitly. */
|
||||
}
|
||||
#p5 {
|
||||
row-span: 3;
|
||||
}
|
||||
#p6 {
|
||||
row-span: 2;
|
||||
}
|
||||
#p7 {
|
||||
/* Default value is 1. */
|
||||
}
|
||||
|
||||
Grid {
|
||||
grid-size: 4 4;
|
||||
grid-gutter: 1 2;
|
||||
}
|
||||
|
||||
Placeholder {
|
||||
height: 100%;
|
||||
}
|
||||
19
docs/examples/styles/row_span.py
Normal file
19
docs/examples/styles/row_span.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from textual.app import App
|
||||
from textual.containers import Grid
|
||||
from textual.widgets import Placeholder
|
||||
|
||||
|
||||
class MyApp(App):
|
||||
def compose(self):
|
||||
yield Grid(
|
||||
Placeholder(id="p1"),
|
||||
Placeholder(id="p2"),
|
||||
Placeholder(id="p3"),
|
||||
Placeholder(id="p4"),
|
||||
Placeholder(id="p5"),
|
||||
Placeholder(id="p6"),
|
||||
Placeholder(id="p7"),
|
||||
)
|
||||
|
||||
|
||||
app = MyApp(css_path="row_span.css")
|
||||
4
docs/examples/styles/scrollbar_corner_color.css
Normal file
4
docs/examples/styles/scrollbar_corner_color.css
Normal file
@@ -0,0 +1,4 @@
|
||||
Screen {
|
||||
overflow: auto auto;
|
||||
scrollbar-corner-color: white;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user