docs examples and diagrams

This commit is contained in:
Will McGugan
2023-02-03 11:23:14 +01:00
parent 806c80b8fe
commit 2ff278874b
33 changed files with 1381 additions and 1190 deletions

1
docs/api/strip.md Normal file
View File

@@ -0,0 +1 @@
::: textual.strip.Strip

View File

@@ -0,0 +1,43 @@
from rich.segment import Segment
from rich.style import Style
from textual.app import App, ComposeResult
from textual.strip import Strip
from textual.widget import Widget
class CheckerBoard(Widget):
"""Render an 8x8 checkerboard."""
def render_line(self, y: int) -> Strip:
"""Render a line of the widget. y is relative to the top of the widget."""
row_index = y // 4 # A checkerboard square consists of 4 rows
if row_index >= 8: # Generate blank lines when we reach the end
return Strip.blank(self.size.width)
is_odd = row_index % 2 # Used to alternate the starting square on each row
white = Style.parse("on white") # Get a style object for a white background
black = Style.parse("on black") # Get a style object for a black background
# Generate a list of segments with alternating black and white space characters
segments = [
Segment(" " * 8, black if (column + is_odd) % 2 else white)
for column in range(8)
]
strip = Strip(segments, 8 * 8)
return strip
class BoardApp(App):
"""A simple app to show our widget."""
def compose(self) -> ComposeResult:
yield CheckerBoard()
if __name__ == "__main__":
app = BoardApp()
app.run()

View File

@@ -0,0 +1,63 @@
from rich.segment import Segment
from rich.style import Style
from textual.app import App, ComposeResult
from textual.geometry import Size
from textual.strip import Strip
from textual.widget import Widget
class CheckerBoard(Widget):
"""Render an 8x8 checkerboard."""
COMPONENT_CLASSES = {
"checkerboard--white-square",
"checkerboard--black-square",
}
DEFAULT_CSS = """
CheckerBoard .checkerboard--white-square {
background: #A5BAC9;
}
CheckerBoard .checkerboard--black-square {
background: #004578;
}
"""
def get_content_width(self, container: Size, viewport: Size) -> int:
return 64
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
return 32
def render_line(self, y: int) -> Strip:
"""Render a line of the widget. y is relative to the top of the widget."""
row_index = y // 4 # four lines per row
if row_index >= 8:
return Strip.blank(self.size.width)
is_odd = row_index % 2
white = self.get_component_rich_style("checkerboard--white-square")
black = self.get_component_rich_style("checkerboard--black-square")
segments = [
Segment(" " * 8, black if (column + is_odd) % 2 else white)
for column in range(8)
]
strip = Strip(segments, 8 * 8)
return strip
class BoardApp(App):
"""A simple app to show our widget."""
def compose(self) -> ComposeResult:
yield CheckerBoard()
if __name__ == "__main__":
app = BoardApp()
app.run()

View File

@@ -15,17 +15,20 @@ class CheckerBoard(ScrollView):
}
DEFAULT_CSS = """
CheckerBoard {
background: $primary;
}
CheckerBoard .checkerboard--white-square {
background: $foreground 70%;
background: #A5BAC9;
}
CheckerBoard .checkerboard--black-square {
background: $primary;
background: #004578;
}
"""
def get_content_width(self, container: Size, viewport: Size) -> int:
return 64
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
return 32
def on_mount(self) -> None:
self.virtual_size = Size(64, 32)

View File

@@ -200,4 +200,78 @@ TODO: Explanation of compound widgets
## Line API
TODO: Explanation of line API
Working with Rich renderables allows you to build sophisticated widgets with minimal effort, but there is a downside to widgets that return renderables.
When you resize a widget or update its state, Textual has to refresh the widget's content in its entirety, which may be expensive.
You are unlikely to notice this if the widget fits within the screen but large widgets that scroll may slow down your application.
Textual offers an alternative API which reduces the amount of work Textual needs to do to refresh a widget, and makes it possible to update portions of a widget (as small as a single character). This is known as the *line API*.
!!! info
The [DataTable](./../widgets/data_table.md) widget uses the Line API, which can support thousands or even millions of rows without a reduction in render times.
### Render Line method
To build an widget with the line API, implement a `render_line` method rather than a `render` method. The `render_line` method takes a single integer argument `y` which is an offset from the top of the widget, and should return a [Strip][textual.strip.Strip] object which contains that line's content.
Textual will call this method as required to to get the content for every line.
<div class="excalidraw">
--8<-- "docs/images/render_line.excalidraw.svg"
</div>
Let's look at an example before we go in to the details. The following Textual app implements a widget with the line API that renders a checkerboard pattern. This might form the basis of a chess / checkers app. Here's the code:
=== "checker01.py"
```python title="checker01.py" hl_lines="12-30"
--8<-- "docs/examples/guide/widgets/checker01.py"
```
=== "Output"
```{.textual path="docs/examples/guide/widgets/checker01.py"}
```
The `render_line` method above calculates a `Strip` for every row of characters in the widget. Each strip contains alternating black and white space characters which form the squares in the checkerboard. You may have noticed that the checkerboard widget makes use of some objects we haven't covered before. Let's explore those.
#### Segment and Style
A [Segment](https://rich.readthedocs.io/en/latest/protocol.html#low-level-render) is a class borrowed from the [Rich](https://github.com/Textualize/rich) project. It is small object (actually a named tuple) which bundles text and a [Style](https://rich.readthedocs.io/en/latest/style.html) which tells Textual how the text should be displayed.
Lets look at a simple segment which would produce the text "Hello, World!" in bold.
```python
greeting = Segment("Hello, World!", Style(bold=True))
```
This would create the following object:
<div class="excalidraw">
--8<-- "docs/images/segment.excalidraw.svg"
</div>
Both Rich and Textual work with segments to generate content. A Textual app is the result of processing hundreds, or perhaps thousands of segments.
#### Strips
A [Strip][textual.strip.Strip] is a container for a number of segments which define the content for a single *line* (or row) in the Widget. A Strip only requires a single segment, but will likely contain many more.
You construct a strip with a list of segments. Here's now you might construct a strip that ultimately displays the text "Hello, World!", but with the second word in bold:
```python
segments = [
Segment("Hello, "),
Segment("World", Style(bold=Trip)),
Segment("!")
]
strip = Strip(segments)
```
The `Strip` constructor has a second optional constructor, which should be the length of the strip. In the code above, the length of the strip is 13, so we could have constructed it like this:
```python
strip = Strip(segments, 13)
```
Note that the length parameter is _not_ the total number of characters in the string. It is the number of terminal "cells". Some characters (such as Asian language characters and certain emoji) take up the space of two Western alphabet characters. If you don't know in advance the number of cells your segments will occupy, it is best to leave the length parameter blank.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB