From 7040d00c7bf7005c9a8486b6ac36b2e19c86994a Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sat, 30 Jul 2022 17:01:51 +0100 Subject: [PATCH] more docs --- docs/examples/styles/background.py | 29 ++++++++++++++++++ docs/examples/styles/border.py | 34 +++++++++++++++++++++ docs/examples/styles/color.py | 28 +++++++++++++++++ docs/examples/styles/display.py | 27 +++++++++-------- docs/examples/styles/height.py | 4 +-- docs/examples/styles/margin.py | 27 +++++++++++++++++ docs/examples/styles/padding.py | 27 +++++++++++++++++ docs/examples/styles/text_style.py | 41 +++++++++++++++++++++++++ docs/examples/styles/visibility.py | 27 +++++++++++++++++ docs/styles/background.md | 41 +++++++++++++++++++++++++ docs/styles/color.md | 41 +++++++++++++++++++++++++ docs/styles/height.md | 2 +- docs/styles/margin.md | 34 +++++++++++++++++++++ docs/styles/padding.md | 34 +++++++++++++++++++++ docs/styles/text_style.md | 36 ++++++++++++++++++++++ docs/styles/visibility.md | 48 ++++++++++++++++++++++++++++++ docs/styles/width.md | 2 +- examples/borders.py | 29 ++++++++++++++++++ mkdocs.yml | 8 ++++- src/textual/constants.py | 11 +++++++ src/textual/layouts/horizontal.py | 2 +- tests/test_color.py | 33 ++++++++++++-------- 22 files changed, 534 insertions(+), 31 deletions(-) create mode 100644 docs/examples/styles/background.py create mode 100644 docs/examples/styles/border.py create mode 100644 docs/examples/styles/color.py create mode 100644 docs/examples/styles/margin.py create mode 100644 docs/examples/styles/padding.py create mode 100644 docs/examples/styles/text_style.py create mode 100644 docs/examples/styles/visibility.py create mode 100644 docs/styles/background.md create mode 100644 docs/styles/color.md create mode 100644 docs/styles/margin.md create mode 100644 docs/styles/padding.md create mode 100644 docs/styles/text_style.md create mode 100644 docs/styles/visibility.md create mode 100644 examples/borders.py create mode 100644 src/textual/constants.py diff --git a/docs/examples/styles/background.py b/docs/examples/styles/background.py new file mode 100644 index 000000000..792cf4c85 --- /dev/null +++ b/docs/examples/styles/background.py @@ -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() diff --git a/docs/examples/styles/border.py b/docs/examples/styles/border.py new file mode 100644 index 000000000..958976939 --- /dev/null +++ b/docs/examples/styles/border.py @@ -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() diff --git a/docs/examples/styles/color.py b/docs/examples/styles/color.py new file mode 100644 index 000000000..6d29d9a30 --- /dev/null +++ b/docs/examples/styles/color.py @@ -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() diff --git a/docs/examples/styles/display.py b/docs/examples/styles/display.py index 5528b5b2f..cc5bd0021 100644 --- a/docs/examples/styles/display.py +++ b/docs/examples/styles/display.py @@ -1,24 +1,27 @@ from textual.app import App -from textual.widget import Widget +from textual.widgets import Static -class WidthApp(App): +class DisplayApp(App): CSS = """ - Screen > Widget { - height: 5; - background: blue; - color: white; - border: heavy white; + Screen { + background: green; } - Widget.hidden { + Static { + height: 5; + background: white; + color: blue; + border: heavy blue; + } + Static.remove { display: none; } """ def compose(self): - yield Widget(id="widget1") - yield Widget(id="widget2", classes="hidden") - yield Widget(id="widget3") + yield Static("Widget 1") + yield Static("widget 2", classes="remove") + yield Static("widget 3") -app = WidthApp() +app = DisplayApp() diff --git a/docs/examples/styles/height.py b/docs/examples/styles/height.py index c1863d5b2..f94baeeeb 100644 --- a/docs/examples/styles/height.py +++ b/docs/examples/styles/height.py @@ -2,7 +2,7 @@ from textual.app import App from textual.widget import Widget -class WidthApp(App): +class HeightApp(App): CSS = """ Screen > Widget { background: green; @@ -15,4 +15,4 @@ class WidthApp(App): yield Widget() -app = WidthApp() +app = HeightApp() diff --git a/docs/examples/styles/margin.py b/docs/examples/styles/margin.py new file mode 100644 index 000000000..c7aec1dfe --- /dev/null +++ b/docs/examples/styles/margin.py @@ -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() diff --git a/docs/examples/styles/padding.py b/docs/examples/styles/padding.py new file mode 100644 index 000000000..8b345514b --- /dev/null +++ b/docs/examples/styles/padding.py @@ -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() diff --git a/docs/examples/styles/text_style.py b/docs/examples/styles/text_style.py new file mode 100644 index 000000000..42f9710d2 --- /dev/null +++ b/docs/examples/styles/text_style.py @@ -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() diff --git a/docs/examples/styles/visibility.py b/docs/examples/styles/visibility.py new file mode 100644 index 000000000..aeec9d228 --- /dev/null +++ b/docs/examples/styles/visibility.py @@ -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() diff --git a/docs/styles/background.md b/docs/styles/background.md new file mode 100644 index 000000000..3530e0d80 --- /dev/null +++ b/docs/styles/background.md @@ -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") + +``` diff --git a/docs/styles/color.md b/docs/styles/color.md new file mode 100644 index 000000000..51806d857 --- /dev/null +++ b/docs/styles/color.md @@ -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") + +``` diff --git a/docs/styles/height.md b/docs/styles/height.md index 4a877e131..12aefac09 100644 --- a/docs/styles/height.md +++ b/docs/styles/height.md @@ -1,6 +1,6 @@ # 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 diff --git a/docs/styles/margin.md b/docs/styles/margin.md new file mode 100644 index 000000000..a0cc4d443 --- /dev/null +++ b/docs/styles/margin.md @@ -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) +``` diff --git a/docs/styles/padding.md b/docs/styles/padding.md new file mode 100644 index 000000000..0c8272fbc --- /dev/null +++ b/docs/styles/padding.md @@ -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) +``` diff --git a/docs/styles/text_style.md b/docs/styles/text_style.md new file mode 100644 index 000000000..d5869f9bf --- /dev/null +++ b/docs/styles/text_style.md @@ -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 underline text +- `"strike"` Sets strikethrough text + +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" +``` diff --git a/docs/styles/visibility.md b/docs/styles/visibility.md new file mode 100644 index 000000000..9db7ba10b --- /dev/null +++ b/docs/styles/visibility.md @@ -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 +``` diff --git a/docs/styles/width.md b/docs/styles/width.md index 18cc7b57e..933f9257d 100644 --- a/docs/styles/width.md +++ b/docs/styles/width.md @@ -1,6 +1,6 @@ # 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 diff --git a/examples/borders.py b/examples/borders.py new file mode 100644 index 000000000..b3f8ece6d --- /dev/null +++ b/examples/borders.py @@ -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() diff --git a/mkdocs.yml b/mkdocs.yml index f7ddbf67e..a5298665c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,9 +11,15 @@ nav: - "events/mount.md" - "events/resize.md" - Styles: + - "styles/background.md" + - "styles/color.md" - "styles/display.md" - - "styles/width.md" - "styles/height.md" + - "styles/margin.md" + - "styles/padding.md" + - "styles/text_style.md" + - "styles/visibility.md" + - "styles/width.md" - Widgets: "/widgets/" - Reference: - "reference/app.md" diff --git a/src/textual/constants.py b/src/textual/constants.py new file mode 100644 index 000000000..6429da790 --- /dev/null +++ b/src/textual/constants.py @@ -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) diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py index 251e93838..9660b9aec 100644 --- a/src/textual/layouts/horizontal.py +++ b/src/textual/layouts/horizontal.py @@ -29,7 +29,7 @@ class HorizontalLayout(Layout): total_fraction = sum( [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 = [ widget.get_box_model(size, parent_size, fraction_unit) diff --git a/tests/test_color.py b/tests/test_color.py index 3f9719665..0e1e51ebb 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -115,22 +115,28 @@ def test_color_parse(text, expected): assert Color.parse(text) == expected -@pytest.mark.parametrize("input,output", [ - ("rgb( 300, 300 , 300 )", Color(255, 255, 255)), - ("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)), -]) +@pytest.mark.parametrize( + "input,output", + [ + ("rgb( 300, 300 , 300 )", Color(255, 255, 255)), + ("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): assert Color.parse(input) == output -@pytest.mark.parametrize("input,output", [ - ("rgb(300, 300, 300)", Color(255, 255, 255)), - ("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)), -]) +@pytest.mark.parametrize( + "input,output", + [ + ("rgb(300, 300, 300)", Color(255, 255, 255)), + ("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): assert Color.parse(input) == output @@ -141,7 +147,8 @@ def test_color_parse_hsl_negative_degrees(): def test_color_parse_hsla_negative_degrees(): 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():