more docs and diagrams

This commit is contained in:
Will McGugan
2022-08-13 21:39:31 +01:00
parent 8ad91cc4c8
commit a1c0b173bd
30 changed files with 385 additions and 188 deletions

View File

@@ -16,21 +16,40 @@ App > Screen {
background: $surface;
color: $text-surface;
layers: sidebar;
layers: base sidebar;
color: $text-background;
background: $background;
layout: vertical;
overflow: hidden;
}
#tree-container {
overflow-y: auto;
height: 20;
margin: 1 2;
background: $panel;
padding: 1 2;
}
DirectoryTree {
padding: 0 1;
height: auto;
}
DataTable {
/*border:heavy red;*/
/* tint: 10% green; */
/* opacity: 50%; */
padding: 1;
margin: 1 2;
height: 12;
height: 24;
}
#sidebar {
@@ -38,6 +57,7 @@ DataTable {
background: $panel;
dock: left;
width: 30;
margin-bottom: 1;
offset-x: -100%;
transition: offset 500ms in_out_cubic;
@@ -71,14 +91,7 @@ DataTable {
content-align: center middle;
}
#header {
color: $text-secondary-background;
background: $secondary-background;
height: 1;
content-align: center middle;
dock: top;
}
Tweet {
@@ -103,7 +116,7 @@ Tweet {
overflow-x: auto;
overflow-y: scroll;
margin: 1 2;
height: 20;
height: 24;
align-horizontal: center;
layout: vertical;
}

View File

@@ -6,7 +6,8 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.reactive import Reactive
from textual.widget import Widget
from textual.widgets import Static, DataTable
from textual.widgets import Static, DataTable, DirectoryTree, Header, Footer
from textual.layout import Container
CODE = '''
from __future__ import annotations
@@ -103,24 +104,23 @@ class Success(Widget):
return Text("This is a success message", justify="center")
class BasicApp(App, css_path="demo.css"):
class BasicApp(App, css_path="basic.css"):
"""A basic app demonstrating CSS"""
def on_load(self):
"""Bind keys here."""
self.bind("s", "toggle_class('#sidebar', '-active')")
self.bind("s", "toggle_class('#sidebar', '-active')", description="Sidebar")
self.bind("d", "toggle_dark", description="Dark mode")
self.bind("q", "quit", description="Quit")
self.bind("f", "query_test", description="Query test")
def compose(self):
yield Header()
def compose(self) -> ComposeResult:
table = DataTable()
self.scroll_to_target = Tweet(TweetBody())
yield Static(
Text.from_markup(
"[b]This is a [u]Textual[/u] app, running in the terminal"
),
id="header",
)
yield from (
yield Container(
Tweet(TweetBody()),
Widget(
Static(
@@ -130,6 +130,7 @@ class BasicApp(App, css_path="demo.css"):
classes="scrollable",
),
table,
Widget(DirectoryTree("~/projects/textual"), id="tree-container"),
Error(),
Tweet(TweetBody(), classes="scrollbar-size-custom"),
Warning(),
@@ -141,7 +142,6 @@ class BasicApp(App, css_path="demo.css"):
Tweet(TweetBody(), classes="scroll-horizontal"),
Tweet(TweetBody(), classes="scroll-horizontal"),
)
yield Widget(id="footer")
yield Widget(
Widget(classes="title"),
Widget(classes="user"),
@@ -151,6 +151,7 @@ class BasicApp(App, css_path="demo.css"):
Widget(classes="content"),
id="sidebar",
)
yield Footer()
table.add_column("Foo", width=20)
table.add_column("Bar", width=20)
@@ -162,12 +163,32 @@ class BasicApp(App, css_path="demo.css"):
for n in range(100):
table.add_row(*[f"Cell ([b]{n}[/b], {col})" for col in range(6)])
def on_mount(self):
self.sub_title = "Widget demo"
async def on_key(self, event) -> None:
await self.dispatch_key(event)
def key_d(self):
def action_toggle_dark(self):
self.dark = not self.dark
def action_query_test(self):
query = self.query("Tweet")
self.log(query)
self.log(query.nodes)
self.log(query)
self.log(query.nodes)
query.set_styles("outline: outer red;")
query = query.exclude(".scroll-horizontal")
self.log(query)
self.log(query.nodes)
# query = query.filter(".rubbish")
# self.log(query)
# self.log(query.first())
async def key_q(self):
await self.shutdown()

View File

@@ -0,0 +1,8 @@
from textual.app import App
class ExampleApp(App):
pass
app = ExampleApp()

View File

@@ -0,0 +1,11 @@
from textual.app import App, ComposeResult
from textual.widgets import Header, Footer
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield Header()
yield Footer()
app = ExampleApp()

View File

@@ -0,0 +1,17 @@
from textual.app import App, ComposeResult
from textual.layout import Container
from textual.widgets import Header, Footer, Static
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield Header()
yield Footer()
yield Container(
Static(id="widget1"),
Static(id="widget2"),
Static(id="widget3"),
)
app = ExampleApp()

View File

View File

@@ -0,0 +1,18 @@
from textual.app import App, ComposeResult
from textual.layout import Container
from textual.widget import Widget
from textual.widgets import Header, Footer
class ExampleApp(App):
def compose(self) -> ComposeResult:
yield Header()
yield Footer()
yield Container(
Widget(id="widget1"),
Widget(id="widget2"),
Widget(id="widget3"),
)
app = ExampleApp()

View File

@@ -0,0 +1,14 @@
Static {
height: 1fr;
content-align: center middle;
color: white;
}
#static1 {
background: red;
}
#static2 {
background: rgb(0, 255, 0);
}
#static3 {
background: hsl(240, 100%, 50%);
}

View File

@@ -3,27 +3,10 @@ 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("Widget 1", id="static1")
yield Static("Widget 2", id="static2")
yield Static("Widget 3", id="static3")
app = BackgroundApp()
app = BackgroundApp(css_path="background.css")

View File

@@ -0,0 +1,25 @@
Screen {
background: white;
}
Screen > Static {
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;
}

View File

@@ -3,38 +3,10 @@ from textual.widgets import Static
class BorderApp(App):
CSS = """
Screen {
background: white;
}
Screen > Static {
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;
}
"""
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")
app = BorderApp()
app = BorderApp(css_path="border.css")

View File

@@ -0,0 +1,17 @@
Screen {
background: white;
color: black;
}
App Static {
background: blue 20%;
height: 5;
margin: 2;
padding: 1;
border: wide black;
}
#static1 {
box-sizing: border-box;
}
#static2 {
box-sizing: content-box;
}

View File

@@ -3,30 +3,9 @@ from textual.widgets import Static
class BoxSizingApp(App):
CSS = """
Screen {
background: white;
color: black;
}
Static {
background: blue 20%;
height: 5;
margin: 2;
padding: 1;
border: wide black;
}
#static1 {
box-sizing: border-box;
}
#static2 {
box-sizing: content-box;
}
"""
def compose(self):
yield Static("I'm using border-box!", id="static1")
yield Static("I'm using content-box!", id="static2")
app = BoxSizingApp()
app = BoxSizingApp(css_path="box_sizing.css")

View File

@@ -0,0 +1,13 @@
Static {
height:1fr;
content-align: center middle;
}
#static1 {
color: red;
}
#static2 {
color: rgb(0, 255, 0);
}
#static3 {
color: hsl(240, 100%, 50%)
}

View File

@@ -3,26 +3,10 @@ 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("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")
app = ColorApp()
app = ColorApp(css_path="color.css")

View File

@@ -0,0 +1,12 @@
Screen {
background: green;
}
Static {
height: 5;
background: white;
color: blue;
border: heavy blue;
}
Static.remove {
display: none;
}

View File

@@ -3,25 +3,10 @@ from textual.widgets import Static
class DisplayApp(App):
CSS = """
Screen {
background: green;
}
Static {
height: 5;
background: white;
color: blue;
border: heavy blue;
}
Static.remove {
display: none;
}
"""
def compose(self):
yield Static("Widget 1")
yield Static("Widget 2", classes="remove")
yield Static("Widget 3")
app = DisplayApp()
app = DisplayApp(css_path="display.css")

View File

@@ -0,0 +1,5 @@
Screen > Widget {
background: green;
height: 50%;
color: white;
}

View File

@@ -3,16 +3,8 @@ from textual.widget import Widget
class HeightApp(App):
CSS = """
Screen > Widget {
background: green;
height: 50%;
color: white;
}
"""
def compose(self):
yield Widget()
app = HeightApp()
app = HeightApp(css_path="height.css")

View File

@@ -0,0 +1,10 @@
Screen {
background: white;
color: black;
}
Static {
margin: 4 8;
background: blue 20%;
border: blue wide;
}

View File

@@ -11,23 +11,8 @@ Where the fear has gone there will be nothing. Only I will remain."""
class MarginApp(App):
CSS = """
Screen {
background: white;
color: black;
}
Static {
margin: 4 8;
background: blue 20%;
border: blue wide;
}
"""
def compose(self):
yield Static(TEXT)
app = MarginApp()
app = MarginApp(css_path="margin.css")

View File

@@ -0,0 +1,31 @@
Screen {
background: white;
color: black;
layout: horizontal;
}
Static {
width: 20;
height: 10;
content-align: center middle;
}
.paul {
offset: 8 2;
background: red 20%;
border: outer red;
color: red;
}
.duncan {
offset: 4 10;
background: green 20%;
border: outer green;
color: green;
}
.chani {
offset: 0 5;
background: blue 20%;
border: outer blue;
color: blue;
}

View File

@@ -3,44 +3,10 @@ from textual.widgets import Static
class OffsetApp(App):
CSS = """
Screen {
background: white;
color: black;
layout: horizontal;
}
Static {
width: 20;
height: 10;
content-align: center middle;
}
.paul {
offset: 8 2;
background: red 20%;
border: outer red;
color: red;
}
.duncan {
offset: 4 10;
background: green 20%;
border: outer green;
color: green;
}
.chani {
offset: 0 5;
background: blue 20%;
border: outer blue;
color: blue;
}
"""
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")
app = OffsetApp()
app = OffsetApp(css_path="offset.css")

View File

@@ -1,5 +1,82 @@
# Textual CSS
Textual uses a web-technology known as CSS (Cascading Style Sheets) to apply styles to widgets. If you are already familiar with CSS you will be right at home, but don't worry if you aren't; Textual CSS is far simpler than its web counterpart.
Textual uses CSS to apply style to widgets. If you have any exposure to web development you will have encountered CSS, bit don't worry if you haven't: this section will get you up to speed.
## CSS Basics
CSS stands for _Cascading Stylesheets_. A stylesheet is a list of styles and the parts of a webpage to apply them to. In the case of Textual, the stylesheets apply styles to widgets.
## The DOM
The DOM, or _Document Object Model_, is a term borrowed from the web world. Textual doesn't use documents, but the term has stuck. The DOM is essentially an arrangement of widgets in to a tree.
Let's look at a super trivial Textual app.
=== "dom1.py"
```python
--8<-- "docs/examples/guide/dom1.py"
```
=== "Output"
```{.textual path="docs/examples/guide/dom1.py"}
```
When you run this app, you will have an instance of an app (ExampleAPP) in memory. The app class will also create a Screen object. In DOM terms, the Screen is a _child_ of the app.
With the above example, the DOM will look like the following:
<div class="excalidraw">
--8<-- "docs/images/dom1.excalidraw.svg"
</div>
The above doesn't look much like a tree. Adding more widgets will create more _branches_ in the tree:
=== "dom2.py"
```python
--8<-- "docs/examples/guide/dom2.py"
```
=== "Output"
```{.textual path="docs/examples/guide/dom2.py"}
```
This examples adds a header and a footer widget, which makes our DOM look the following:
<div class="excalidraw">
--8<-- "docs/images/dom2.excalidraw.svg"
</div>
!!! note
We've simplified the above example somewhat. Both the Header and Footer widgets contain children of their own. When building an app with pre-built widgets you rarely need to know how they are constructed unless you plan on changing the styles for the individual components.
Both Header and Footer are children of the Screen objects. If you were to print `app.screen.children` you would see something like `[Header(), Footer()]`.
To further explore the DOM, lets add a few more levels. We are going to add a `textual.layout.Container` widget which (as the name suggests) is a container for other widgets. To that container we are going to add three `textual.widget.Widget` widgets. The `Widget` class is the base class for all widgets. Normally you would extend the `Widget` class to build a functional widget, but for our experiment that the base class will do.
=== "dom3.py"
```python
--8<-- "docs/examples/guide/dom3.py"
```
=== "Output"
```{.textual path="docs/examples/guide/dom3.py"}
```
You may notice that there is a scrollbar in the output now, and we see the text "Widget#widget1". We will explain why that is later.
Here's the DOM created by the above code:
<div class="excalidraw">
--8<-- "docs/images/dom3.excalidraw.svg"
</div>
You'll notice that we defined the children of our container differently. The `Container` class, and most other widgets, will accept their children as positional arguments. These children are added to the DOM at the same time as their parents.
```python hl_lines="10 11 12 13 14"
--8<-- "docs/examples/guide/dom3.py"
```

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -8,6 +8,12 @@ The `background` rule sets the background color of the widget.
--8<-- "docs/examples/styles/background.py"
```
=== "background.css"
```sass
--8<-- "docs/examples/styles/background.css"
```
=== "Output"
```{.textual path="docs/examples/styles/background.py"}

View File

@@ -20,3 +20,7 @@ body[data-md-color-primary="black"] .excalidraw svg {
body[data-md-color-primary="black"] .excalidraw svg rect {
fill: transparent;
}
.excalidraw {
text-align: center;
}

View File

@@ -6,6 +6,7 @@ nav:
- "introduction.md"
- Guide:
- "guide/guide.md"
- "guide/CSS.md"
- "guide/events.md"
- "guide/devtools.md"
- "actions.md"