mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
docs examples and diagrams
This commit is contained in:
1
docs/api/strip.md
Normal file
1
docs/api/strip.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
::: textual.strip.Strip
|
||||||
43
docs/examples/guide/widgets/checker01.py
Normal file
43
docs/examples/guide/widgets/checker01.py
Normal 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()
|
||||||
63
docs/examples/guide/widgets/checker02.py
Normal file
63
docs/examples/guide/widgets/checker02.py
Normal 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()
|
||||||
@@ -15,17 +15,20 @@ class CheckerBoard(ScrollView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
CheckerBoard {
|
|
||||||
background: $primary;
|
|
||||||
}
|
|
||||||
CheckerBoard .checkerboard--white-square {
|
CheckerBoard .checkerboard--white-square {
|
||||||
background: $foreground 70%;
|
background: #A5BAC9;
|
||||||
}
|
}
|
||||||
CheckerBoard .checkerboard--black-square {
|
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:
|
def on_mount(self) -> None:
|
||||||
self.virtual_size = Size(64, 32)
|
self.virtual_size = Size(64, 32)
|
||||||
|
|
||||||
@@ -200,4 +200,78 @@ TODO: Explanation of compound widgets
|
|||||||
|
|
||||||
## Line API
|
## 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.
|
||||||
|
|||||||
16
docs/images/render_line.excalidraw.svg
Normal file
16
docs/images/render_line.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 26 KiB |
16
docs/images/segment.excalidraw.svg
Normal file
16
docs/images/segment.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -168,6 +168,7 @@ nav:
|
|||||||
- "api/reactive.md"
|
- "api/reactive.md"
|
||||||
- "api/screen.md"
|
- "api/screen.md"
|
||||||
- "api/static.md"
|
- "api/static.md"
|
||||||
|
- "api/strip.md"
|
||||||
- "api/text_log.md"
|
- "api/text_log.md"
|
||||||
- "api/timer.md"
|
- "api/timer.md"
|
||||||
- "api/tree.md"
|
- "api/tree.md"
|
||||||
|
|||||||
2307
poetry.lock
generated
2307
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,7 @@ dev = ["aiohttp", "click", "msgpack"]
|
|||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest = "^7.1.3"
|
pytest = "^7.1.3"
|
||||||
black = "^22.3.0,<22.10.0" # macos wheel issue on 22.10
|
black = "^23.1.0"
|
||||||
mypy = "^0.990"
|
mypy = "^0.990"
|
||||||
pytest-cov = "^2.12.1"
|
pytest-cov = "^2.12.1"
|
||||||
mkdocs = "^1.3.0"
|
mkdocs = "^1.3.0"
|
||||||
|
|||||||
@@ -322,7 +322,6 @@ class Animator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if animation is None:
|
if animation is None:
|
||||||
|
|
||||||
if not isinstance(value, (int, float)) and not isinstance(
|
if not isinstance(value, (int, float)) and not isinstance(
|
||||||
value, Animatable
|
value, Animatable
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ class Parser(Generic[T]):
|
|||||||
self._awaiting = next(self._gen)
|
self._awaiting = next(self._gen)
|
||||||
|
|
||||||
def feed(self, data: str) -> Iterable[T]:
|
def feed(self, data: str) -> Iterable[T]:
|
||||||
|
|
||||||
if self._eof:
|
if self._eof:
|
||||||
raise ParseError("end of file reached") from None
|
raise ParseError("end of file reached") from None
|
||||||
if not data:
|
if not data:
|
||||||
@@ -104,7 +103,6 @@ class Parser(Generic[T]):
|
|||||||
yield popleft()
|
yield popleft()
|
||||||
|
|
||||||
while pos < data_size or isinstance(self._awaiting, _PeekBuffer):
|
while pos < data_size or isinstance(self._awaiting, _PeekBuffer):
|
||||||
|
|
||||||
_awaiting = self._awaiting
|
_awaiting = self._awaiting
|
||||||
if isinstance(_awaiting, _Read1):
|
if isinstance(_awaiting, _Read1):
|
||||||
self._awaiting = self._gen.send(data[pos : pos + 1])
|
self._awaiting = self._gen.send(data[pos : pos + 1])
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ class Sleeper(Thread):
|
|||||||
|
|
||||||
|
|
||||||
async def check_sleeps() -> None:
|
async def check_sleeps() -> None:
|
||||||
|
|
||||||
sleeper = Sleeper()
|
sleeper = Sleeper()
|
||||||
sleeper.start()
|
sleeper.start()
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class XTermParser(Parser[events.Event]):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]:
|
def parse(self, on_token: TokenCallback) -> Generator[Awaitable, str, None]:
|
||||||
|
|
||||||
ESC = "\x1b"
|
ESC = "\x1b"
|
||||||
read1 = self.read1
|
read1 = self.read1
|
||||||
sequence_to_key_events = self._sequence_to_key_events
|
sequence_to_key_events = self._sequence_to_key_events
|
||||||
@@ -161,7 +160,6 @@ class XTermParser(Parser[events.Event]):
|
|||||||
|
|
||||||
# Look ahead through the suspected escape sequence for a match
|
# Look ahead through the suspected escape sequence for a match
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
# If we run into another ESC at this point, then we've failed
|
# If we run into another ESC at this point, then we've failed
|
||||||
# to find a match, and should issue everything we've seen within
|
# to find a match, and should issue everything we've seen within
|
||||||
# the suspected sequence as Key events instead.
|
# the suspected sequence as Key events instead.
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class Content(Vertical):
|
|||||||
|
|
||||||
class ColorsView(Vertical):
|
class ColorsView(Vertical):
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
|
||||||
LEVELS = [
|
LEVELS = [
|
||||||
"darken-3",
|
"darken-3",
|
||||||
"darken-2",
|
"darken-2",
|
||||||
@@ -42,7 +41,6 @@ class ColorsView(Vertical):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for color_name in ColorSystem.COLOR_NAMES:
|
for color_name in ColorSystem.COLOR_NAMES:
|
||||||
|
|
||||||
items: list[Widget] = [Label(f'"{color_name}"')]
|
items: list[Widget] = [Label(f'"{color_name}"')]
|
||||||
for level in LEVELS:
|
for level in LEVELS:
|
||||||
color = f"{color_name}-{level}" if level else color_name
|
color = f"{color_name}-{level}" if level else color_name
|
||||||
|
|||||||
@@ -434,7 +434,6 @@ class StylesBuilder:
|
|||||||
process_padding_left = _process_space_partial
|
process_padding_left = _process_space_partial
|
||||||
|
|
||||||
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
|
def _parse_border(self, name: str, tokens: list[Token]) -> BorderValue:
|
||||||
|
|
||||||
border_type: EdgeType = "solid"
|
border_type: EdgeType = "solid"
|
||||||
border_color = Color(0, 255, 0)
|
border_color = Color(0, 255, 0)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ SELECTOR_MAP: dict[str, tuple[SelectorType, Specificity3]] = {
|
|||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
|
def parse_selectors(css_selectors: str) -> tuple[SelectorSet, ...]:
|
||||||
|
|
||||||
if not css_selectors.strip():
|
if not css_selectors.strip():
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ class DOMQuery(Generic[QueryType]):
|
|||||||
exclude: str | None = None,
|
exclude: str | None = None,
|
||||||
parent: DOMQuery | None = None,
|
parent: DOMQuery | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self._node = node
|
self._node = node
|
||||||
self._nodes: list[QueryType] | None = None
|
self._nodes: list[QueryType] | None = None
|
||||||
self._filters: list[tuple[SelectorSet, ...]] = (
|
self._filters: list[tuple[SelectorSet, ...]] = (
|
||||||
|
|||||||
@@ -327,7 +327,6 @@ class StylesBase(ABC):
|
|||||||
|
|
||||||
# Check we are animating a Scalar or Scalar offset
|
# Check we are animating a Scalar or Scalar offset
|
||||||
if isinstance(start_value, (Scalar, ScalarOffset)):
|
if isinstance(start_value, (Scalar, ScalarOffset)):
|
||||||
|
|
||||||
# If destination is a number, we can convert that to a scalar
|
# If destination is a number, we can convert that to a scalar
|
||||||
if isinstance(value, (int, float)):
|
if isinstance(value, (int, float)):
|
||||||
value = Scalar(value, Unit.CELLS, Unit.CELLS)
|
value = Scalar(value, Unit.CELLS, Unit.CELLS)
|
||||||
|
|||||||
@@ -236,7 +236,6 @@ class ClientHandler:
|
|||||||
message = cast(WSMessage, message)
|
message = cast(WSMessage, message)
|
||||||
|
|
||||||
if message.type in (WSMsgType.TEXT, WSMsgType.BINARY):
|
if message.type in (WSMsgType.TEXT, WSMsgType.BINARY):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if isinstance(message.data, bytes):
|
if isinstance(message.data, bytes):
|
||||||
message = msgpack.unpackb(message.data)
|
message = msgpack.unpackb(message.data)
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class LinuxDriver(Driver):
|
|||||||
self.console.file.flush()
|
self.console.file.flush()
|
||||||
|
|
||||||
def start_application_mode(self):
|
def start_application_mode(self):
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
def send_size_event():
|
def send_size_event():
|
||||||
@@ -123,7 +122,6 @@ class LinuxDriver(Driver):
|
|||||||
except termios.error:
|
except termios.error:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
|
||||||
newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
|
newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG])
|
||||||
newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])
|
newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG])
|
||||||
|
|
||||||
@@ -208,7 +206,6 @@ class LinuxDriver(Driver):
|
|||||||
pass # TODO: log
|
pass # TODO: log
|
||||||
|
|
||||||
def _run_input_thread(self, loop) -> None:
|
def _run_input_thread(self, loop) -> None:
|
||||||
|
|
||||||
selector = selectors.DefaultSelector()
|
selector = selectors.DefaultSelector()
|
||||||
selector.register(self.fileno, selectors.EVENT_READ)
|
selector.register(self.fileno, selectors.EVENT_READ)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ class WindowsDriver(Driver):
|
|||||||
self.console.file.write("\x1b[?2004l")
|
self.console.file.write("\x1b[?2004l")
|
||||||
|
|
||||||
def start_application_mode(self) -> None:
|
def start_application_mode(self) -> None:
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
self._restore_console = win32.enable_application_mode()
|
self._restore_console = win32.enable_application_mode()
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ class HorizontalLayout(Layout):
|
|||||||
def arrange(
|
def arrange(
|
||||||
self, parent: Widget, children: list[Widget], size: Size
|
self, parent: Widget, children: list[Widget], size: Size
|
||||||
) -> ArrangeResult:
|
) -> ArrangeResult:
|
||||||
|
|
||||||
placements: list[WidgetPlacement] = []
|
placements: list[WidgetPlacement] = []
|
||||||
add_placement = placements.append
|
add_placement = placements.append
|
||||||
x = max_height = Fraction(0)
|
x = max_height = Fraction(0)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class VerticalLayout(Layout):
|
|||||||
def arrange(
|
def arrange(
|
||||||
self, parent: Widget, children: list[Widget], size: Size
|
self, parent: Widget, children: list[Widget], size: Size
|
||||||
) -> ArrangeResult:
|
) -> ArrangeResult:
|
||||||
|
|
||||||
placements: list[WidgetPlacement] = []
|
placements: list[WidgetPlacement] = []
|
||||||
add_placement = placements.append
|
add_placement = placements.append
|
||||||
parent_size = parent.outer_size
|
parent_size = parent.outer_size
|
||||||
|
|||||||
@@ -398,7 +398,6 @@ class MessagePump(metaclass=MessagePumpMeta):
|
|||||||
self.app._handle_exception(error)
|
self.app._handle_exception(error)
|
||||||
break
|
break
|
||||||
finally:
|
finally:
|
||||||
|
|
||||||
self._message_queue.task_done()
|
self._message_queue.task_done()
|
||||||
|
|
||||||
current_time = time()
|
current_time = time()
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ class Reactive(Generic[ReactiveType]):
|
|||||||
getattr(obj, "__computes", []).clear()
|
getattr(obj, "__computes", []).clear()
|
||||||
|
|
||||||
def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
|
def __set_name__(self, owner: Type[MessageTarget], name: str) -> None:
|
||||||
|
|
||||||
# Check for compute method
|
# Check for compute method
|
||||||
if hasattr(owner, f"compute_{name}"):
|
if hasattr(owner, f"compute_{name}"):
|
||||||
# Compute methods are stored in a list called `__computes`
|
# Compute methods are stored in a list called `__computes`
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ class ScrollBarRender:
|
|||||||
back_color: Color = Color.parse("#555555"),
|
back_color: Color = Color.parse("#555555"),
|
||||||
bar_color: Color = Color.parse("bright_magenta"),
|
bar_color: Color = Color.parse("bright_magenta"),
|
||||||
) -> Segments:
|
) -> Segments:
|
||||||
|
|
||||||
if vertical:
|
if vertical:
|
||||||
bars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "]
|
bars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", " "]
|
||||||
else:
|
else:
|
||||||
@@ -190,7 +189,6 @@ class ScrollBarRender:
|
|||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class ScrollBar(Widget):
|
class ScrollBar(Widget):
|
||||||
|
|
||||||
renderer: ClassVar[Type[ScrollBarRender]] = ScrollBarRender
|
renderer: ClassVar[Type[ScrollBarRender]] = ScrollBarRender
|
||||||
"""The class used for rendering scrollbars.
|
"""The class used for rendering scrollbars.
|
||||||
This can be overriden and set to a ScrollBarRender-derived class
|
This can be overriden and set to a ScrollBarRender-derived class
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ class DirectoryTree(Tree[DirEntry]):
|
|||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
super().__init__(
|
super().__init__(
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ class _InputRenderable:
|
|||||||
def __rich_console__(
|
def __rich_console__(
|
||||||
self, console: "Console", options: "ConsoleOptions"
|
self, console: "Console", options: "ConsoleOptions"
|
||||||
) -> "RenderResult":
|
) -> "RenderResult":
|
||||||
|
|
||||||
input = self.input
|
input = self.input
|
||||||
result = input._value
|
result = input._value
|
||||||
if input._cursor_at_end:
|
if input._cursor_at_end:
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ class Static(Widget, inherit_bindings=False):
|
|||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
super().__init__(name=name, id=id, classes=classes)
|
super().__init__(name=name, id=id, classes=classes)
|
||||||
self.expand = expand
|
self.expand = expand
|
||||||
self.shrink = shrink
|
self.shrink = shrink
|
||||||
|
|||||||
@@ -159,7 +159,6 @@ class TextLog(ScrollView, can_focus=True):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
def _render_line(self, y: int, scroll_x: int, width: int) -> Strip:
|
def _render_line(self, y: int, scroll_x: int, width: int) -> Strip:
|
||||||
|
|
||||||
if y >= len(self.lines):
|
if y >= len(self.lines):
|
||||||
return Strip.blank(width, self.rich_style)
|
return Strip.blank(width, self.rich_style)
|
||||||
|
|
||||||
|
|||||||
@@ -281,7 +281,6 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
|
|
||||||
|
|
||||||
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
||||||
|
|
||||||
BINDINGS: ClassVar[list[BindingType]] = [
|
BINDINGS: ClassVar[list[BindingType]] = [
|
||||||
Binding("enter", "select_cursor", "Select", show=False),
|
Binding("enter", "select_cursor", "Select", show=False),
|
||||||
Binding("space", "toggle_node", "Toggle", show=False),
|
Binding("space", "toggle_node", "Toggle", show=False),
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ Where the fear has gone there will be nothing. Only I will remain."
|
|||||||
|
|
||||||
|
|
||||||
class Welcome(Static):
|
class Welcome(Static):
|
||||||
|
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
Welcome {
|
Welcome {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: $surface;
|
background: $surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ class Welcome(Static):
|
|||||||
|
|
||||||
Welcome #close {
|
Welcome #close {
|
||||||
dock: bottom;
|
dock: bottom;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user