mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' of github.com:Textualize/textual into delay-transition
This commit is contained in:
24
docs/examples/styles/layout.css
Normal file
24
docs/examples/styles/layout.css
Normal file
@@ -0,0 +1,24 @@
|
||||
#vertical-layout {
|
||||
layout: vertical;
|
||||
background: darkmagenta;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#horizontal-layout {
|
||||
layout: horizontal;
|
||||
background: darkcyan;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#center-layout {
|
||||
layout: center;
|
||||
background: darkslateblue;
|
||||
height: 7;
|
||||
}
|
||||
|
||||
Static {
|
||||
margin: 1;
|
||||
width: 12;
|
||||
color: black;
|
||||
background: yellowgreen;
|
||||
}
|
||||
27
docs/examples/styles/layout.py
Normal file
27
docs/examples/styles/layout.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from textual import layout
|
||||
from textual.app import App
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Static
|
||||
|
||||
|
||||
class LayoutApp(App):
|
||||
def compose(self):
|
||||
yield layout.Container(
|
||||
Static("Layout"),
|
||||
Static("Is"),
|
||||
Static("Vertical"),
|
||||
id="vertical-layout",
|
||||
)
|
||||
yield layout.Container(
|
||||
Static("Layout"),
|
||||
Static("Is"),
|
||||
Static("Horizontal"),
|
||||
id="horizontal-layout",
|
||||
)
|
||||
yield layout.Container(
|
||||
Static("Center"),
|
||||
id="center-layout",
|
||||
)
|
||||
|
||||
|
||||
app = LayoutApp(css_path="layout.css")
|
||||
24
docs/examples/styles/text_align.css
Normal file
24
docs/examples/styles/text_align.css
Normal file
@@ -0,0 +1,24 @@
|
||||
#one {
|
||||
text-align: left;
|
||||
background: lightblue;
|
||||
|
||||
}
|
||||
|
||||
#two {
|
||||
text-align: center;
|
||||
background: indianred;
|
||||
}
|
||||
|
||||
#three {
|
||||
text-align: right;
|
||||
background: palegreen;
|
||||
}
|
||||
|
||||
#four {
|
||||
text-align: justify;
|
||||
background: palevioletred;
|
||||
}
|
||||
|
||||
Static {
|
||||
padding: 1;
|
||||
}
|
||||
28
docs/examples/styles/text_align.py
Normal file
28
docs/examples/styles/text_align.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static
|
||||
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
class TextAlign(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
left = Static("[b]Left aligned[/]\n" + TEXT, id="one")
|
||||
yield left
|
||||
|
||||
right = Static("[b]Center aligned[/]\n" + TEXT, id="two")
|
||||
yield right
|
||||
|
||||
center = Static("[b]Right aligned[/]\n" + TEXT, id="three")
|
||||
yield center
|
||||
|
||||
full = Static("[b]Justified[/]\n" + TEXT, id="four")
|
||||
yield full
|
||||
|
||||
|
||||
app = TextAlign(css_path="text_align.css")
|
||||
@@ -5,7 +5,7 @@ The `background` rule sets the background color of the widget.
|
||||
## Syntax
|
||||
|
||||
```
|
||||
background: COLOR [PERCENTAGE]
|
||||
background: <COLOR> [<PERCENTAGE>];
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
@@ -2,8 +2,22 @@
|
||||
|
||||
The `border` rule enables the drawing of a box around a widget. A border is set with a border value (see below) followed by a color.
|
||||
|
||||
| Border value | Explanation |
|
||||
| ------------ |---------------------------------------------------------|
|
||||
Borders may also be set individually for the four edges of a widget with the `border-top`, `border-right`, `border-bottom` and `border-left` rules.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
border: [<COLOR>] [<BORDER VALUE>];
|
||||
border-top: [<COLOR>] [<BORDER VALUE>];
|
||||
border-right: [<COLOR>] [<BORDER VALUE>];
|
||||
border-bottom: [<COLOR>] [<BORDER VALUE>];
|
||||
border-left: [<COLOR>] [<BORDER VALUE>];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Border value | Description |
|
||||
|--------------|---------------------------------------------------------|
|
||||
| `"ascii"` | A border with plus, hyphen, and vertical bar |
|
||||
| `"blank"` | A blank border (reserves space for a border) |
|
||||
| `"dashed"` | Dashed line border |
|
||||
@@ -20,19 +34,7 @@ The `border` rule enables the drawing of a box around a widget. A border is set
|
||||
| `"vkey"` | Vertical key-line border |
|
||||
| `"wide"` | Solid border with additional space left and right |
|
||||
|
||||
For example `heavy white` would display a heavy white line around a widget.
|
||||
|
||||
Borders may also be set individually for the four edges of a widget with the `border-top`, `border-right`, `border-bottom` and `border-left` rules.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
border: [<COLOR>] [<BORDER VALUE>];
|
||||
border-top: [<COLOR>] [<BORDER VALUE>];
|
||||
border-right: [<COLOR>] [<BORDER VALUE>];
|
||||
border-bottom: [<COLOR>] [<BORDER VALUE>];
|
||||
border-left: [<COLOR>] [<BORDER VALUE>];
|
||||
```
|
||||
For example, `heavy white` would display a heavy white line around a widget.
|
||||
|
||||
## Border command
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
# Box-sizing
|
||||
|
||||
The `box-sizing` rule impacts how `width` and `height` rules are translated in to screen dimensions, when combined with `padding` and `border`.
|
||||
|
||||
The default value is `border-box` which means that padding and border are included in the width and height. This setting means that if you add padding and/or border the widget will not change in size, but you will have less space for content.
|
||||
|
||||
You can set `box-sizing` to `content-box` which tells Textual that padding and border should increase the size of the widget, leaving the content area unaffected.
|
||||
The `box-sizing` property determines how the width and height of a widget are calculated.
|
||||
|
||||
## Syntax
|
||||
|
||||
@@ -12,9 +8,18 @@ You can set `box-sizing` to `content-box` which tells Textual that padding and b
|
||||
box-sizing: [border-box|content-box];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Values | Description |
|
||||
|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `border-box` (default) | Padding and border are included in the width and height. If you add padding and/or border the widget will not change in size, but you will have less space for content. |
|
||||
| `content-box` | Padding and border will increase the size of the widget, leaving the content area unaffected. |
|
||||
|
||||
## Example
|
||||
|
||||
Both widgets in this example have the same height (5). The top widget has `box-sizing: border-box` which means that padding and border reduces the space for content. The bottom widget has `box-sizing: content-box` which increases the size of the widget to compensate for padding and border.
|
||||
Both widgets in this example have the same height (5).
|
||||
The top widget has `box-sizing: border-box` which means that padding and border reduces the space for content.
|
||||
The bottom widget has `box-sizing: content-box` which increases the size of the widget to compensate for padding and border.
|
||||
|
||||
=== "box_sizing.py"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Display
|
||||
|
||||
The `display` property defines if a Widget is displayed or not. The default value is `"block"` which will display the widget as normal. Setting the property to `"none"` will effectively make it invisible.
|
||||
The `display` property defines whether a widget is displayed or not.
|
||||
|
||||
## Syntax
|
||||
|
||||
@@ -8,6 +8,13 @@ The `display` property defines if a Widget is displayed or not. The default valu
|
||||
display: [none|block];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|-------------------|---------------------------------------------------------------------------|
|
||||
| `block` (default) | Display the widget as normal |
|
||||
| `none` | The widget not be displayed, and space will no longer be reserved for it. |
|
||||
|
||||
## Example
|
||||
|
||||
Note that the second widget is hidden by adding the "hidden" class which sets the display style to None.
|
||||
|
||||
50
docs/styles/layout.md
Normal file
50
docs/styles/layout.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Layout
|
||||
|
||||
The `layout` property defines how a widget arranges its children.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
layout: [vertical|horizontal|center];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|----------------------|-------------------------------------------------------------------------------|
|
||||
| `vertical` (default) | Child widgets will be arranged along the vertical axis, from top to bottom. |
|
||||
| `horizontal` | Child widgets will be arranged along the horizontal axis, from left to right. |
|
||||
| `center` | A single child widget will be placed in the center. |
|
||||
|
||||
## Example
|
||||
|
||||
Note how the `layout` property affects the arrangement of widgets in the example below.
|
||||
|
||||
=== "layout.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/styles/layout.py"
|
||||
```
|
||||
|
||||
=== "layout.css"
|
||||
|
||||
```sass
|
||||
--8<-- "docs/examples/styles/layout.css"
|
||||
```
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/styles/layout.py"}
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
```sass
|
||||
layout: horizontal;
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
```python
|
||||
widget.layout = "horizontal"
|
||||
```
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
The `margin` rule adds space around the entire widget. Margin may be specified with 1, 2 or 4 values.
|
||||
|
||||
| example | |
|
||||
| ------------------ | ------------------------------------------------------------------- |
|
||||
| Example | Description |
|
||||
|--------------------|---------------------------------------------------------------------|
|
||||
| `margin: 1;` | A single value sets a margin of 1 around all 4 edges |
|
||||
| `margin: 1 2;` | Two values sets the margin for the top/bottom and left/right edges |
|
||||
| `margin: 1 2 3 4;` | Four values sets top, right, bottom, and left margins independently |
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
# Outline
|
||||
|
||||
The `outline` rule enables the drawing of a box around a widget. Similar to `border`, but unlike border, outline will draw over the content area. This rule can be useful for emphasis if you want to display a outline for a brief time to draw the user's attention to it.
|
||||
The `outline` rule enables the drawing of a box around a widget. Similar to `border`, but unlike border, outline will
|
||||
draw _over_ the content area. This rule can be useful for emphasis if you want to display an outline for a brief time to
|
||||
draw the user's attention to it.
|
||||
|
||||
An outline is set with a border value (see below) followed by a color.
|
||||
An outline is set with a border value (see table below) followed by a color.
|
||||
|
||||
| Border value | Explanation |
|
||||
| ------------ | ------------------------------------------------------- |
|
||||
Outlines may also be set individually with the `outline-top`, `outline-right`, `outline-bottom` and `outline-left`
|
||||
rules.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
outline: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-top: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-right: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-bottom: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-left: [<COLOR>] [<BORDER VALUE>];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Border value | Description |
|
||||
|--------------|---------------------------------------------------------|
|
||||
| `"ascii"` | A border with plus, hyphen, and vertical bar |
|
||||
| `"blank"` | A blank border (reserves space for a border) |
|
||||
| `"dashed"` | Dashed line border |
|
||||
@@ -22,19 +39,7 @@ An outline is set with a border value (see below) followed by a color.
|
||||
| `"vkey"` | Vertical key-line border |
|
||||
| `"wide"` | Solid border with additional space left and right |
|
||||
|
||||
For example `heavy white` would display a heavy white line around a widget.
|
||||
|
||||
Outlines may also be set individually with the `outline-top`, `outline-right`, `outline-bottom` and `outline-left` rules.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
outline: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-top: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-right: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-bottom: [<COLOR>] [<BORDER VALUE>];
|
||||
outline-left: [<COLOR>] [<BORDER VALUE>];
|
||||
```
|
||||
For example, `heavy white` would display a heavy white line around a widget.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -61,10 +66,10 @@ This examples shows a widget with an outline. Note how the outline occludes the
|
||||
|
||||
```sass
|
||||
/* Set a heavy white outline */
|
||||
outline: heavy white;
|
||||
outline:heavy white;
|
||||
|
||||
/* set a red outline on the left */
|
||||
outline-left: outer red;
|
||||
outline-left:outer red;
|
||||
```
|
||||
|
||||
## Python
|
||||
@@ -73,6 +78,6 @@ outline-left: outer red;
|
||||
# Set a heavy white outline
|
||||
widget.outline = ("heavy", "white)
|
||||
|
||||
# Set a red outline on the left
|
||||
widget.outline_left = ("outer", "red)
|
||||
# Set a red outline on the left
|
||||
widget.outline_left = ("outer", "red)
|
||||
```
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
# Overflow
|
||||
|
||||
The `overflow` rule specifies if and when scrollbars should be displayed on the `x` and `y` axis. The rule takes two overflow values; one for the horizontal bar (x axis), followed by the vertical bar (y-axis).
|
||||
|
||||
| Overflow value | Effect |
|
||||
| -------------- | ------------------------------------------------------------------------- |
|
||||
| `"auto"` | Automatically show the scrollbar if the content doesn't fit (the default) |
|
||||
| `"hidden"` | Never show the scrollbar |
|
||||
| `"scroll"` | Always show the scrollbar |
|
||||
The `overflow` rule specifies if and when scrollbars should be displayed on the `x` and `y` axis.
|
||||
The rule takes two overflow values; one for the horizontal bar (x-axis), followed by the vertical bar (y-axis).
|
||||
|
||||
The default value for overflow is `"auto auto"` which will show scrollbars automatically for both scrollbars if content doesn't fit within container.
|
||||
|
||||
@@ -20,11 +15,20 @@ overflow-x: [auto|hidden|scroll];
|
||||
overflow-y: [auto|hidden|scroll];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|------------------|---------------------------------------------------------|
|
||||
| `auto` (default) | Automatically show the scrollbar if content doesn't fit |
|
||||
| `hidden` | Never show the scrollbar |
|
||||
| `scroll` | Always show the scrollbar |
|
||||
|
||||
## Example
|
||||
|
||||
Here we split the screen in to left and right sections, each with three vertically scrolling widgets that do not fit in to the height of the terminal.
|
||||
|
||||
The left side has `overflow-y: auto` (the default) and will automatically show a scrollbar. The right side has `overflow-y: hidden` which will prevent a scrollbar from being shown.
|
||||
The left side has `overflow-y: auto` (the default) and will automatically show a scrollbar.
|
||||
The right side has `overflow-y: hidden` which will prevent a scrollbar from being shown.
|
||||
|
||||
=== "overflow.py"
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Scrollbar colors
|
||||
|
||||
There are a number of rules to set the colors used in Textual scrollbars. You won't typically need to do this, as the default themes have carefully chosen colors, but you can if you want to.
|
||||
There are a number of rules to set the colors used in Textual scrollbars.
|
||||
You won't typically need to do this, as the default themes have carefully chosen colors, but you can if you want to.
|
||||
|
||||
| Rule | Color |
|
||||
| ----------------------------- | ------------------------------------------------------- |
|
||||
|-------------------------------|---------------------------------------------------------|
|
||||
| `scrollbar-color` | Scrollbar "thumb" (movable part) |
|
||||
| `scrollbar-color-hover` | Scrollbar thumb when the mouse is hovering over it |
|
||||
| `scrollbar-color-active` | Scrollbar thumb when it is active (being dragged) |
|
||||
@@ -12,7 +13,7 @@ There are a number of rules to set the colors used in Textual scrollbars. You wo
|
||||
| `scrollbar-background-active` | Scrollbar background when the thumb is being dragged |
|
||||
| `scrollbar-corner-color` | The gap between the horizontal and vertical scrollbars |
|
||||
|
||||
## Example:
|
||||
## Syntax
|
||||
|
||||
```
|
||||
scrollbar-color: <COLOR>;
|
||||
|
||||
57
docs/styles/text_align.md
Normal file
57
docs/styles/text_align.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Text-align
|
||||
|
||||
The `text-align` rule aligns text within a widget.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
text-align: [left|start|center|right|end|justify];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|-----------|----------------------------------|
|
||||
| `left` | Left aligns text in the widget |
|
||||
| `start` | Left aligns text in the widget |
|
||||
| `center` | Center aligns text in the widget |
|
||||
| `right` | Right aligns text in the widget |
|
||||
| `end` | Right aligns text in the widget |
|
||||
| `justify` | Justifies text in the widget |
|
||||
|
||||
## Example
|
||||
|
||||
This example shows, from top to bottom: `left`, `center`, `right`, and `justify` text alignments.
|
||||
|
||||
=== "text_align.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/styles/text_align.py"
|
||||
```
|
||||
|
||||
=== "text_align.css"
|
||||
|
||||
```css
|
||||
--8<-- "docs/examples/styles/text_align.css"
|
||||
```
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/styles/text_align.py"}
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
```sass
|
||||
/* Set text in all Widgets to be right aligned */
|
||||
Widget {
|
||||
text-align: right;
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
```python
|
||||
# Set text in the widget to be right aligned
|
||||
widget.styles.text_align = "right"
|
||||
```
|
||||
@@ -1,23 +1,26 @@
|
||||
# Text-style
|
||||
|
||||
The `text-style` rule enables a number of different ways of displaying text. The value may be set to any of the following:
|
||||
The `text-style` rule enables a number of different ways of displaying text.
|
||||
|
||||
| Style | Effect |
|
||||
| ------------- | -------------------------------------------------------------- |
|
||||
| `"bold"` | **bold text** |
|
||||
| `"italic"` | _italic text_ |
|
||||
| `"reverse"` | reverse video text (foreground and background colors reversed) |
|
||||
| `"underline"` | <u>underline text</u> |
|
||||
| `"strike"` | <s>strikethrough text</s> |
|
||||
|
||||
Text styles may be set in combination. For example "bold underline" or "reverse underline strike".
|
||||
Text styles may be set in combination.
|
||||
For example `bold underline` or `reverse underline strike`.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
text-style: <TEXT STYLE> ...
|
||||
text-style: <TEXT STYLE> ...;
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|-------------|----------------------------------------------------------------|
|
||||
| `bold` | **bold text** |
|
||||
| `italic` | _italic text_ |
|
||||
| `reverse` | reverse video text (foreground and background colors reversed) |
|
||||
| `underline` | <u>underline text</u> |
|
||||
| `strike` | <s>strikethrough text</s> |
|
||||
|
||||
## Example
|
||||
|
||||
Each of the three text panels has a different text style.
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
# Visibility
|
||||
|
||||
The `visibility` rule may be used to make a widget invisible while still reserving spacing for it. The default value is `"visible"` which will cause the Widget to be displayed as normal. Setting the value to `"hidden"` will cause the Widget to become invisible.
|
||||
The `visibility` rule may be used to make a widget invisible while still reserving spacing for it.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
visibility: [hidden|visible];
|
||||
visibility: [visible|hidden];
|
||||
```
|
||||
|
||||
### Values
|
||||
|
||||
| Value | Description |
|
||||
|---------------------|----------------------------------------|
|
||||
| `visible` (default) | The widget will be displayed as normal |
|
||||
| `hidden` | The widget will be invisible |
|
||||
|
||||
## Example
|
||||
|
||||
Note that the second widget is hidden, while leaving a space where it would have been rendered.
|
||||
|
||||
12
mkdocs.yml
12
mkdocs.yml
@@ -6,10 +6,10 @@ nav:
|
||||
- "getting_started.md"
|
||||
- "introduction.md"
|
||||
- Guide:
|
||||
- "guide/devtools.md"
|
||||
- "guide/devtools.md"
|
||||
- "guide/CSS.md"
|
||||
- "guide/events.md"
|
||||
|
||||
|
||||
- "actions.md"
|
||||
- Events:
|
||||
- "events/blur.md"
|
||||
@@ -35,7 +35,7 @@ nav:
|
||||
- "events/resize.md"
|
||||
- "events/screen_resume.md"
|
||||
- "events/screen_suspend.md"
|
||||
- "events/show.md"
|
||||
- "events/show.md"
|
||||
- Styles:
|
||||
- "styles/background.md"
|
||||
- "styles/border.md"
|
||||
@@ -44,6 +44,7 @@ nav:
|
||||
- "styles/content_align.md"
|
||||
- "styles/display.md"
|
||||
- "styles/height.md"
|
||||
- "styles/layout.md"
|
||||
- "styles/margin.md"
|
||||
- "styles/max_height.md"
|
||||
- "styles/max_width.md"
|
||||
@@ -53,9 +54,10 @@ nav:
|
||||
- "styles/outline.md"
|
||||
- "styles/overflow.md"
|
||||
- "styles/padding.md"
|
||||
- "styles/scrollbar.md"
|
||||
- "styles/scrollbar_gutter.md"
|
||||
- "styles/scrollbar_size.md"
|
||||
- "styles/scrollbar.md"
|
||||
- "styles/text_align.md"
|
||||
- "styles/text_style.md"
|
||||
- "styles/tint.md"
|
||||
- "styles/visibility.md"
|
||||
@@ -81,7 +83,7 @@ markdown_extensions:
|
||||
- admonition
|
||||
- def_list
|
||||
- meta
|
||||
|
||||
|
||||
- toc:
|
||||
permalink: true
|
||||
baselevel: 1
|
||||
|
||||
@@ -16,7 +16,14 @@ Screen {
|
||||
offset-x: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
.box1 {
|
||||
background: orangered;
|
||||
height: 12;
|
||||
width: 30;
|
||||
}
|
||||
|
||||
.box2 {
|
||||
background: blueviolet;
|
||||
height: 6;
|
||||
width: 12;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ class JustABox(App):
|
||||
self.bind("s", "toggle_class('#sidebar', '-active')", description="Sidebar")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
self.box = Box()
|
||||
self.box = Box(classes="box1")
|
||||
yield self.box
|
||||
yield Box(classes="box2")
|
||||
yield Widget(id="sidebar")
|
||||
|
||||
def key_a(self):
|
||||
|
||||
33
sandbox/darren/text_align.py
Normal file
33
sandbox/darren/text_align.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Static
|
||||
|
||||
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 TextAlign(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
left = Static("[b]Left aligned[/]\n" + TEXT, id="one")
|
||||
yield left
|
||||
|
||||
right = Static("[b]Center aligned[/]\n" + TEXT, id="two")
|
||||
yield right
|
||||
|
||||
center = Static("[b]Right aligned[/]\n" + TEXT, id="three")
|
||||
yield center
|
||||
|
||||
full = Static("[b]Fully justified[/]\n" + TEXT, id="four")
|
||||
yield full
|
||||
|
||||
|
||||
app = TextAlign(css_path="text_align.scss", watch_css=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
24
sandbox/darren/text_align.scss
Normal file
24
sandbox/darren/text_align.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
#one {
|
||||
text-align: left;
|
||||
background: lightblue;
|
||||
|
||||
}
|
||||
|
||||
#two {
|
||||
text-align: center;
|
||||
background: indianred;
|
||||
}
|
||||
|
||||
#three {
|
||||
text-align: right;
|
||||
background: palegreen;
|
||||
}
|
||||
|
||||
#four {
|
||||
text-align: justify;
|
||||
background: palevioletred;
|
||||
}
|
||||
|
||||
Static {
|
||||
padding: 1;
|
||||
}
|
||||
@@ -9,10 +9,10 @@ from textual.css._help_renderables import Example, Bullet, HelpText
|
||||
from textual.css.constants import (
|
||||
VALID_BORDER,
|
||||
VALID_LAYOUT,
|
||||
VALID_EDGE,
|
||||
VALID_ALIGN_HORIZONTAL,
|
||||
VALID_ALIGN_VERTICAL,
|
||||
VALID_STYLE_FLAGS,
|
||||
VALID_TEXT_ALIGN,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
@@ -648,6 +648,26 @@ def align_help_text() -> HelpText:
|
||||
)
|
||||
|
||||
|
||||
def text_align_help_text() -> HelpText:
|
||||
"""Help text to show when the user supplies an invalid value for the text-align property
|
||||
|
||||
Returns:
|
||||
HelpText: Renderable for displaying the help text for this property.
|
||||
"""
|
||||
return HelpText(
|
||||
summary="Invalid value for the [i]text-align[/] property.",
|
||||
bullets=[
|
||||
Bullet(
|
||||
f"The [i]text-align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}",
|
||||
examples=[
|
||||
Example("text-align: center;"),
|
||||
Example("text-align: right;"),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def offset_single_axis_help_text(property_name: str) -> HelpText:
|
||||
"""Help text to show when the user supplies an invalid value for an offset-* property.
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from ._help_text import (
|
||||
property_invalid_value_help_text,
|
||||
scrollbar_size_property_help_text,
|
||||
scrollbar_size_single_axis_help_text,
|
||||
text_align_help_text,
|
||||
)
|
||||
from .constants import (
|
||||
VALID_ALIGN_HORIZONTAL,
|
||||
@@ -36,6 +37,7 @@ from .constants import (
|
||||
VALID_VISIBILITY,
|
||||
VALID_STYLE_FLAGS,
|
||||
VALID_SCROLLBAR_GUTTER,
|
||||
VALID_TEXT_ALIGN,
|
||||
)
|
||||
from .errors import DeclarationError, StyleValueError
|
||||
from .model import Declaration
|
||||
@@ -618,6 +620,20 @@ class StylesBuilder:
|
||||
style_definition = " ".join(token.value for token in tokens)
|
||||
self.styles.text_style = style_definition
|
||||
|
||||
def process_text_align(self, name: str, tokens: list[Token]) -> None:
|
||||
"""Process a text-align declaration"""
|
||||
if not tokens:
|
||||
return
|
||||
|
||||
if len(tokens) > 1 or tokens[0].value not in VALID_TEXT_ALIGN:
|
||||
self.error(
|
||||
name,
|
||||
tokens[0],
|
||||
text_align_help_text(),
|
||||
)
|
||||
|
||||
self.styles._rules["text_align"] = tokens[0].value
|
||||
|
||||
def process_dock(self, name: str, tokens: list[Token]) -> None:
|
||||
if not tokens:
|
||||
return
|
||||
|
||||
@@ -38,6 +38,7 @@ VALID_BOX_SIZING: Final = {"border-box", "content-box"}
|
||||
VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"}
|
||||
VALID_ALIGN_HORIZONTAL: Final = {"left", "center", "right"}
|
||||
VALID_ALIGN_VERTICAL: Final = {"top", "middle", "bottom"}
|
||||
VALID_TEXT_ALIGN: Final = {"start", "end", "left", "right", "center", "justify"}
|
||||
VALID_SCROLLBAR_GUTTER: Final = {"auto", "stable"}
|
||||
VALID_STYLE_FLAGS: Final = {
|
||||
"none",
|
||||
|
||||
@@ -31,7 +31,6 @@ from ._style_properties import (
|
||||
SpacingProperty,
|
||||
StringEnumProperty,
|
||||
StyleFlagsProperty,
|
||||
StyleProperty,
|
||||
TransitionsProperty,
|
||||
)
|
||||
from .constants import (
|
||||
@@ -42,6 +41,7 @@ from .constants import (
|
||||
VALID_OVERFLOW,
|
||||
VALID_SCROLLBAR_GUTTER,
|
||||
VALID_VISIBILITY,
|
||||
VALID_TEXT_ALIGN,
|
||||
)
|
||||
from .scalar import Scalar, ScalarOffset, Unit
|
||||
from .scalar_animation import ScalarAnimation
|
||||
@@ -57,6 +57,7 @@ from .types import (
|
||||
Specificity3,
|
||||
Specificity6,
|
||||
Visibility,
|
||||
TextAlign,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
@@ -143,6 +144,8 @@ class RulesMap(TypedDict, total=False):
|
||||
content_align_horizontal: AlignHorizontal
|
||||
content_align_vertical: AlignVertical
|
||||
|
||||
text_align: TextAlign
|
||||
|
||||
|
||||
RULE_NAMES = list(RulesMap.__annotations__.keys())
|
||||
RULE_NAMES_SET = frozenset(RULE_NAMES)
|
||||
@@ -250,6 +253,8 @@ class StylesBase(ABC):
|
||||
content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top")
|
||||
content_align = AlignProperty()
|
||||
|
||||
text_align = StringEnumProperty(VALID_TEXT_ALIGN, "start")
|
||||
|
||||
def __eq__(self, styles: object) -> bool:
|
||||
"""Check that Styles contains the same rules."""
|
||||
if not isinstance(styles, StylesBase):
|
||||
@@ -459,7 +464,6 @@ class StylesBase(ABC):
|
||||
@rich.repr.auto
|
||||
@dataclass
|
||||
class Styles(StylesBase):
|
||||
|
||||
node: DOMNode | None = None
|
||||
_rules: RulesMap = field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -45,10 +45,6 @@ class TokenError(Exception):
|
||||
def _get_snippet(self) -> Panel:
|
||||
"""Get a short snippet of code around a given line number.
|
||||
|
||||
Args:
|
||||
code (str): The code.
|
||||
line_no (int): Line number.
|
||||
|
||||
Returns:
|
||||
Panel: A renderable.
|
||||
"""
|
||||
|
||||
@@ -39,6 +39,7 @@ ScrollbarGutter = Literal["auto", "stable"]
|
||||
BoxSizing = Literal["border-box", "content-box"]
|
||||
Overflow = Literal["scroll", "hidden", "auto"]
|
||||
EdgeStyle = Tuple[EdgeType, Color]
|
||||
TextAlign = Literal["left", "start", "center", "right", "end", "justify"]
|
||||
|
||||
Specificity3 = Tuple[int, int, int]
|
||||
Specificity4 = Tuple[int, int, int, int]
|
||||
|
||||
@@ -619,7 +619,7 @@ class Region(NamedTuple):
|
||||
"""Move the offset of the Region.
|
||||
|
||||
Args:
|
||||
translate (tuple[int, int]): Offset to add to region.
|
||||
offset (tuple[int, int]): Offset to add to region.
|
||||
|
||||
Returns:
|
||||
Region: A new region shifted by (x, y)
|
||||
|
||||
@@ -242,7 +242,7 @@ class MessagePump(metaclass=MessagePumpMeta):
|
||||
name: str | None = None,
|
||||
repeat: int = 0,
|
||||
pause: bool = False,
|
||||
):
|
||||
) -> Timer:
|
||||
"""Call a function at periodic intervals.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import Lock
|
||||
from itertools import islice
|
||||
from fractions import Fraction
|
||||
from itertools import islice
|
||||
from operator import attrgetter
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Awaitable,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Collection,
|
||||
Iterable,
|
||||
@@ -16,8 +13,7 @@ from typing import (
|
||||
)
|
||||
|
||||
import rich.repr
|
||||
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.console import Console, RenderableType, JustifyMethod
|
||||
from rich.measure import Measurement
|
||||
from rich.segment import Segment
|
||||
from rich.style import Style
|
||||
@@ -33,13 +29,13 @@ from ._segment_tools import align_lines
|
||||
from ._styles_cache import StylesCache
|
||||
from ._types import Lines
|
||||
from .box_model import BoxModel, get_box_model
|
||||
from .css.constants import VALID_TEXT_ALIGN
|
||||
from .dom import DOMNode
|
||||
from .dom import NoScreen
|
||||
from .geometry import Offset, Region, Size, Spacing, clamp
|
||||
from .layouts.vertical import VerticalLayout
|
||||
from .message import Message
|
||||
from .reactive import Reactive
|
||||
from .dom import NoScreen
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .app import App, ComposeResult
|
||||
@@ -1215,11 +1211,15 @@ class Widget(DOMNode):
|
||||
"""
|
||||
|
||||
if isinstance(renderable, str):
|
||||
renderable = Text.from_markup(renderable)
|
||||
justify = _get_rich_justify(self.styles.text_align)
|
||||
renderable = Text.from_markup(renderable, justify=justify)
|
||||
|
||||
rich_style = self.rich_style
|
||||
if isinstance(renderable, Text):
|
||||
renderable.stylize(rich_style)
|
||||
if not renderable.justify:
|
||||
justify = _get_rich_justify(self.styles.text_align)
|
||||
renderable.justify = justify
|
||||
else:
|
||||
renderable = Styled(renderable, rich_style)
|
||||
|
||||
@@ -1380,9 +1380,6 @@ class Widget(DOMNode):
|
||||
def render(self) -> RenderableType:
|
||||
"""Get renderable for widget.
|
||||
|
||||
Args:
|
||||
style (Styles): The Styles object for this Widget.
|
||||
|
||||
Returns:
|
||||
RenderableType: Any renderable
|
||||
"""
|
||||
@@ -1580,3 +1577,22 @@ class Widget(DOMNode):
|
||||
self.scroll_page_up()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_rich_justify(css_align: str) -> JustifyMethod:
|
||||
"""Given the value for CSS text-align, return the analogous argument
|
||||
for the Rich text `justify` parameter.
|
||||
|
||||
Args:
|
||||
css_align: The value of text-align CSS property.
|
||||
|
||||
Returns:
|
||||
JustifyMethod: The Rich JustifyMethod that corresponds to the text-align
|
||||
value
|
||||
"""
|
||||
assert css_align in VALID_TEXT_ALIGN
|
||||
return {
|
||||
"start": "left",
|
||||
"end": "right",
|
||||
"justify": "full",
|
||||
}.get(css_align, css_align)
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from textual.color import Color
|
||||
from textual.css.errors import UnresolvedVariableError
|
||||
from textual.css.parse import substitute_references
|
||||
@@ -1132,3 +1131,27 @@ class TestParsePadding:
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet.add_source(css)
|
||||
assert stylesheet.rules[0].styles.padding == Spacing(2, 3, -1, 1)
|
||||
|
||||
|
||||
class TestParseTextAlign:
|
||||
@pytest.mark.parametrize("valid_align", ["left", "start", "center", "right", "end", "justify"])
|
||||
def test_text_align(self, valid_align):
|
||||
css = f"#foo {{ text-align: {valid_align} }}"
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet.add_source(css)
|
||||
assert stylesheet.rules[0].styles.text_align == valid_align
|
||||
|
||||
def test_text_align_invalid(self):
|
||||
css = "#foo { text-align: invalid-value; }"
|
||||
stylesheet = Stylesheet()
|
||||
with pytest.raises(StylesheetParseError):
|
||||
stylesheet.add_source(css)
|
||||
stylesheet.parse()
|
||||
rules = stylesheet._parse_rules(css, "foo")
|
||||
assert rules[0].errors
|
||||
|
||||
def test_text_align_empty_uses_default(self):
|
||||
css = "#foo { text-align: ; }"
|
||||
stylesheet = Stylesheet()
|
||||
stylesheet.add_source(css)
|
||||
assert stylesheet.rules[0].styles.text_align == "start"
|
||||
|
||||
@@ -25,4 +25,4 @@ def test_auto_refresh():
|
||||
elapsed = app.run(quit_after=1, headless=True)
|
||||
assert elapsed is not None
|
||||
# CI can run slower, so we need to give this a bit of margin
|
||||
assert elapsed >= 0.3 and elapsed < 0.6
|
||||
assert 0.2 <= elapsed < 0.8
|
||||
|
||||
Reference in New Issue
Block a user