more docs

This commit is contained in:
Will McGugan
2022-07-30 17:01:51 +01:00
parent 7ba36c1146
commit 7040d00c7b
22 changed files with 534 additions and 31 deletions

View File

@@ -0,0 +1,29 @@
from textual.app import App
from textual.widgets import Static
class BackgroundApp(App):
CSS = """
Static {
height:1fr;
content-align: center middle;
color: white;
}
#static1 {
background: red;
}
#static2 {
background: rgb(0, 255, 0);
}
#static3 {
background: hsl(240, 100%, 50%);
}
"""
def compose(self):
yield Static("Hello, World!", id="static1")
yield Static("Hello, World!", id="static2")
yield Static("Hello, World!", id="static3")
app = BackgroundApp()

View File

@@ -0,0 +1,34 @@
from textual.app import App
from textual.widgets import Static
class BorderApp(App):
CSS = """
Screen > Static {
height:5;
content-align: center middle;
color: white;
margin: 1;
box-sizing: border-box;
}
#static1 {
background: red 20%;
border: solid red;
}
#static2 {
background: green 20%;
border: dashed green;
}
#static3 {
background: blue 20%;
border: tall blue;
}
"""
def compose(self):
yield Static("Hello, World!", id="static1")
yield Static("Hello, World!", id="static2")
yield Static("Hello, World!", id="static3")
app = BorderApp()

View File

@@ -0,0 +1,28 @@
from textual.app import App
from textual.widgets import Static
class ColorApp(App):
CSS = """
Static {
height:1fr;
content-align: center middle;
}
#static1 {
color: red;
}
#static2 {
color: rgb(0, 255, 0);
}
#static3 {
color: hsl(240, 100%, 50%)
}
"""
def compose(self):
yield Static("Hello, World!", id="static1")
yield Static("Hello, World!", id="static2")
yield Static("Hello, World!", id="static3")
app = ColorApp()

View File

@@ -1,24 +1,27 @@
from textual.app import App from textual.app import App
from textual.widget import Widget from textual.widgets import Static
class WidthApp(App): class DisplayApp(App):
CSS = """ CSS = """
Screen > Widget { Screen {
height: 5; background: green;
background: blue;
color: white;
border: heavy white;
} }
Widget.hidden { Static {
height: 5;
background: white;
color: blue;
border: heavy blue;
}
Static.remove {
display: none; display: none;
} }
""" """
def compose(self): def compose(self):
yield Widget(id="widget1") yield Static("Widget 1")
yield Widget(id="widget2", classes="hidden") yield Static("widget 2", classes="remove")
yield Widget(id="widget3") yield Static("widget 3")
app = WidthApp() app = DisplayApp()

View File

@@ -2,7 +2,7 @@ from textual.app import App
from textual.widget import Widget from textual.widget import Widget
class WidthApp(App): class HeightApp(App):
CSS = """ CSS = """
Screen > Widget { Screen > Widget {
background: green; background: green;
@@ -15,4 +15,4 @@ class WidthApp(App):
yield Widget() yield Widget()
app = WidthApp() app = HeightApp()

View File

@@ -0,0 +1,27 @@
from textual.app import App
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 MarginApp(App):
CSS = """
Static {
margin: 4 8;
background: blue 20%;
}
"""
def compose(self):
yield Static(TEXT)
app = MarginApp()

View File

@@ -0,0 +1,27 @@
from textual.app import App
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 PaddingApp(App):
CSS = """
Static {
padding: 4 8;
background: blue 20%;
}
"""
def compose(self):
yield Static(TEXT)
app = PaddingApp()

View File

@@ -0,0 +1,41 @@
from textual.app import App
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 TextStyleApp(App):
CSS = """
Screen {
layout: horizontal;
}
Static {
width:1fr;
}
#static1 {
background: red 30%;
text-style: bold;
}
#static2 {
background: green 30%;
text-style: italic;
}
#static3 {
background: blue 30%;
text-style: reverse;
}
"""
def compose(self):
yield Static(TEXT, id="static1")
yield Static(TEXT, id="static2")
yield Static(TEXT, id="static3")
app = TextStyleApp()

View File

@@ -0,0 +1,27 @@
from textual.app import App
from textual.widgets import Static
class VisibilityApp(App):
CSS = """
Screen {
background: green;
}
Static {
height: 5;
background: white;
color: blue;
border: heavy blue;
}
Static.invisible {
visibility: hidden;
}
"""
def compose(self):
yield Static("Widget 1")
yield Static("widget 2", classes="invisible")
yield Static("widget 3")
app = VisibilityApp()

41
docs/styles/background.md Normal file
View File

@@ -0,0 +1,41 @@
# Background
The `background` rule sets the background color of the widget.
=== "background.py"
```python
--8<-- "docs/examples/styles/background.py"
```
=== "Output"
```{.textual path="docs/examples/styles/background.py"}
```
## CSS
```sass
/* Blue background */
background: blue;
/* 20% red backround */
background: red 20%;
/* RGB color */
background: rgb(100,120,200);
```
## Python
You can use the same syntax as CSS, or explicitly set a Color object.
```python
# Set blue background
widget.styles.background = "blue"
from textual.color import Color
# Set with a color object
widget.styles.background = Color.parse("pink")
```

41
docs/styles/color.md Normal file
View File

@@ -0,0 +1,41 @@
# Color
The `color` rule sets the text color of a Widget.
=== "color.py"
```python
--8<-- "docs/examples/styles/color.py"
```
=== "Output"
```{.textual path="docs/examples/styles/color.py"}
```
## CSS
```sass
/* Blue background */
color: blue;
/* 20% red backround */
color: red 20%;
/* RGB color */
color: rgb(100,120,200);
```
## Python
You can use the same syntax as CSS, or explicitly set a Color object.
```python
# Set blue background
widget.styles.color = "blue"
from textual.color import Color
# Set with a color object
widget.styles.color = Color.parse("pink")
```

View File

@@ -1,6 +1,6 @@
# Height # Height
The `height` property sets a widget's height. By default, it sets the width of the content area, but if `box-sizing` is set to `border-box` it sets the width of the border area. The `height` style sets a widget's height. By default, it sets the width of the content area, but if `box-sizing` is set to `border-box` it sets the width of the border area.
## Example ## Example

34
docs/styles/margin.md Normal file
View File

@@ -0,0 +1,34 @@
# Margin
The `margin` rule adds space around the entire widget.
- `1` Sets a margin of 1 around all 4 edges
- `1 2` Sets a margin of 1 on the top and bottom edges, and a margin of 2 on the left and right edges
- `1 2 3 4` Sets a margin of one on the top edge, 2 on the right, 3 on the bottom, and 4 on the left.
## Example
=== "margin.py"
```python
--8<-- "docs/examples/styles/margin.py"
```
=== "Output"
```{.textual path="docs/examples/styles/margin.py"}
```
## CSS
```sass
/* Set margin of 2 on the top and bottom edges, and 4 on the left and right */
margin: 2 4;
```
## Python
```python
# In Python you can set the margin as a tuple of integers
widget.styles.margin = (2, 3)
```

34
docs/styles/padding.md Normal file
View File

@@ -0,0 +1,34 @@
# Padding
The padding rule adds space around the content of a widget. You can specify padding with 1, 2 or 4 numbers.
- `1` Sets a padding of 1 around all 4 edges
- `1 2` Sets a padding of 1 on the top and bottom edges, and a padding of two on the left and right edges
- `1 2 3 4` Sets a padding of one on the top edge, 2 on the right, 3 on the bottom, and 4 on the left.
## Example
=== "padding.py"
```python
--8<-- "docs/examples/styles/padding.py"
```
=== "Output"
```{.textual path="docs/examples/styles/padding.py"}
```
## CSS
```sass
/* Set padding of 2 on the top and bottom edges, and 4 on the left and right */
padding: 2 4;
```
## Python
```python
# In Python you can set the padding as a tuple of integers
widget.styles.padding = (2, 3)
```

36
docs/styles/text_style.md Normal file
View File

@@ -0,0 +1,36 @@
# 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:
- `"bold"` Sets **bold text**
- `"italic"` Sets _italic text_
- `"reverse"` Sets reverse video text (foreground and background colors reversed)
- `"underline"` Sets <u>underline text</u>
- `"strike"` Sets <s>strikethrough text</s>
Text styles may be set in combination. For example "bold underline" or "reverse underline strike".
## Example
=== "text_style.py"
```python
--8<-- "docs/examples/styles/text_style.py"
```
=== "Output"
```{.textual path="docs/examples/styles/text_style.py"}
```
## CSS
```sass
text-style: italic;
```
## Python
```python
widget.styles.text_style = "italic"
```

48
docs/styles/visibility.md Normal file
View File

@@ -0,0 +1,48 @@
# 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 be removed from the screen.
## Example
Note that the second widget is hidden, while leaving a space where it would have been rendered.
=== "visibility.py"
```python
--8<-- "docs/examples/styles/visibility.py"
```
=== "Output"
```{.textual path="docs/examples/styles/visibility.py"}
```
## CSS
```sass
/* Widget is on screen */
visibility: visible;
/* Widget is not on the screen */
visibility: hidden;
```
## Python
```python
# Widget is invisible
self.styles.visibility = "hidden"
# Widget is visible
self.styles.visibility = "visible"
```
There is also a shortcut to set a Widget's visibility. The `visible` property on `Widget` may be set to `True` or `False`.
```python
# Make a widget invisible
widget.visible = False
# Make the widget visible again
widget.visible = True
```

View File

@@ -1,6 +1,6 @@
# Width # Width
The `width` property sets a widget's width. By default, it sets the width of the content area, but if `box-sizing` is set to `border-box` it sets the width of the border area. The `width` style sets a widget's width. By default, it sets the width of the content area, but if `box-sizing` is set to `border-box` it sets the width of the border area.
## Example ## Example

29
examples/borders.py Normal file
View File

@@ -0,0 +1,29 @@
from itertools import cycle
from textual.app import App
from textual.color import Color
from textual.constants import BORDERS
from textual.widgets import Static
class BorderApp(App):
"""Displays a pride flag."""
COLORS = ["red", "orange", "yellow", "green", "blue", "purple"]
def compose(self):
self.dark = True
for border, color in zip(BORDERS, cycle(self.COLORS)):
static = Static(f"border: {border} {color};")
static.styles.height = 7
static.styles.background = Color.parse(color).with_alpha(0.2)
static.styles.margin = (1, 2)
static.styles.border = (border, color)
static.styles.content_align = ("center", "middle")
yield static
app = BorderApp()
if __name__ == "__main__":
app.run()

View File

@@ -11,9 +11,15 @@ nav:
- "events/mount.md" - "events/mount.md"
- "events/resize.md" - "events/resize.md"
- Styles: - Styles:
- "styles/background.md"
- "styles/color.md"
- "styles/display.md" - "styles/display.md"
- "styles/width.md"
- "styles/height.md" - "styles/height.md"
- "styles/margin.md"
- "styles/padding.md"
- "styles/text_style.md"
- "styles/visibility.md"
- "styles/width.md"
- Widgets: "/widgets/" - Widgets: "/widgets/"
- Reference: - Reference:
- "reference/app.md" - "reference/app.md"

11
src/textual/constants.py Normal file
View File

@@ -0,0 +1,11 @@
"""
Constants that we might want to expose via the public API.
"""
from ._border import BORDER_CHARS
__all__ = ["BORDERS"]
BORDERS = list(BORDER_CHARS)

View File

@@ -29,7 +29,7 @@ class HorizontalLayout(Layout):
total_fraction = sum( total_fraction = sum(
[int(style.width.value) for style in styles if style.width.is_fraction] [int(style.width.value) for style in styles if style.width.is_fraction]
) )
fraction_unit = Fraction(size.height, total_fraction or 1) fraction_unit = Fraction(size.width, total_fraction or 1)
box_models = [ box_models = [
widget.get_box_model(size, parent_size, fraction_unit) widget.get_box_model(size, parent_size, fraction_unit)

View File

@@ -115,22 +115,28 @@ def test_color_parse(text, expected):
assert Color.parse(text) == expected assert Color.parse(text) == expected
@pytest.mark.parametrize("input,output", [ @pytest.mark.parametrize(
("rgb( 300, 300 , 300 )", Color(255, 255, 255)), "input,output",
("rgba( 2 , 3 , 4, 1.0 )", Color(2, 3, 4, 1.0)), [
("hsl( 45, 25% , 25% )", Color(80, 72, 48)), ("rgb( 300, 300 , 300 )", Color(255, 255, 255)),
("hsla( 45, 25% , 25%, 0.35 )", Color(80, 72, 48, 0.35)), ("rgba( 2 , 3 , 4, 1.0 )", Color(2, 3, 4, 1.0)),
]) ("hsl( 45, 25% , 25% )", Color(80, 72, 48)),
("hsla( 45, 25% , 25%, 0.35 )", Color(80, 72, 48, 0.35)),
],
)
def test_color_parse_input_has_spaces(input, output): def test_color_parse_input_has_spaces(input, output):
assert Color.parse(input) == output assert Color.parse(input) == output
@pytest.mark.parametrize("input,output", [ @pytest.mark.parametrize(
("rgb(300, 300, 300)", Color(255, 255, 255)), "input,output",
("rgba(300, 300, 300, 300)", Color(255, 255, 255, 1.0)), [
("hsl(400, 200%, 250%)", Color(255, 255, 255, 1.0)), ("rgb(300, 300, 300)", Color(255, 255, 255)),
("hsla(400, 200%, 250%, 1.9)", Color(255, 255, 255, 1.0)), ("rgba(300, 300, 300, 300)", Color(255, 255, 255, 1.0)),
]) ("hsl(400, 200%, 250%)", Color(255, 255, 255, 1.0)),
("hsla(400, 200%, 250%, 1.9)", Color(255, 255, 255, 1.0)),
],
)
def test_color_parse_clamp(input, output): def test_color_parse_clamp(input, output):
assert Color.parse(input) == output assert Color.parse(input) == output
@@ -141,7 +147,8 @@ def test_color_parse_hsl_negative_degrees():
def test_color_parse_hsla_negative_degrees(): def test_color_parse_hsla_negative_degrees():
assert Color.parse("hsla(-45, 50%, 50%, 0.2)") == Color.parse( assert Color.parse("hsla(-45, 50%, 50%, 0.2)") == Color.parse(
"hsla(315, 50%, 50%, 0.2)") "hsla(315, 50%, 50%, 0.2)"
)
def test_color_parse_color(): def test_color_parse_color():