mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
|
||||
## [0.6.0] - Unreleased
|
||||
## [0.6.0] - 2022-12-11
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
106
docs/blog/posts/release0-6-0.md
Normal file
106
docs/blog/posts/release0-6-0.md
Normal file
@@ -0,0 +1,106 @@
|
||||
---
|
||||
draft: false
|
||||
date: 2022-12-11
|
||||
categories:
|
||||
- Release
|
||||
title: "version-060"
|
||||
authors:
|
||||
- willmcgugan
|
||||
---
|
||||
|
||||
# Textual 0.6.0 adds a *tree*mendous new widget
|
||||
|
||||
A new release of Textual lands 3 weeks after the previous release -- and it's a big one.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
!!! information
|
||||
|
||||
If you're new here, [Textual](https://github.com/Textualize/textual) is TUI framework for Python.
|
||||
|
||||
## Tree Control
|
||||
|
||||
The headline feature of version 0.6.0 is a new tree control built from the ground-up. The previous Tree control suffered from an overly complex API and wasn't scalable (scrolling slowed down with 1000s of nodes).
|
||||
|
||||
This new version has a simpler API and is highly scalable (no slowdown with larger trees). There are also a number of visual enhancements in this version.
|
||||
|
||||
Here's a very simple example:
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/tree.py"}
|
||||
```
|
||||
|
||||
=== "tree.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/tree.py"
|
||||
```
|
||||
|
||||
Here's the tree control being used to navigate some JSON ([json_tree.py](https://github.com/Textualize/textual/blob/main/examples/json_tree.py) in the examples directory).
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="auto" src="https://www.youtube.com/embed/Fy9fPL37P6o" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
I'm biased of course, but I think this terminal based tree control is more usable (and even prettier) than just about anything I've seen on the web or desktop. So much of computing tends to organize itself in to a tree that I think this widget will find a lot of uses.
|
||||
|
||||
The Tree control forms the foundation of the [DirectoryTree](../../widgets/directory_tree.md) widget, which has also been updated. Here it is used in the [code_browser.py](https://github.com/Textualize/textual/blob/main/examples/code_browser.py) example:
|
||||
|
||||
<div class="video-wrapper">
|
||||
<iframe width="auto" src="https://www.youtube.com/embed/ZrYWyZXuYRY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
## List View
|
||||
|
||||
We have a new [ListView](../../widgets/list_view.md) control to navigate and select items in a list. Items can be widgets themselves, which makes this a great platform for building more sophisticated controls.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/list_view.py"}
|
||||
```
|
||||
|
||||
=== "list_view.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/list_view.py"
|
||||
```
|
||||
|
||||
=== "list_view.css"
|
||||
|
||||
```sass
|
||||
--8<-- "docs/examples/widgets/list_view.css"
|
||||
```
|
||||
|
||||
## Placeholder
|
||||
|
||||
The [Placeholder](../../widgets/placeholder.md) widget was broken since the big CSS update. We've brought it back and given it a bit of a polish.
|
||||
|
||||
Use this widget in place of custom widgets you have yet to build when designing your UI. The colors are automatically cycled to differentiate one placeholder rom the next. You can click a placeholder to cycle between its ID, size, and lorem ipsum text.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/widgets/placeholder.py" columns="100" lines="45"}
|
||||
```
|
||||
|
||||
=== "placeholder.py"
|
||||
|
||||
```python
|
||||
--8<-- "docs/examples/widgets/placeholder.py"
|
||||
```
|
||||
|
||||
=== "placeholder.css"
|
||||
|
||||
```sass
|
||||
--8<-- "docs/examples/widgets/placeholder.css"
|
||||
```
|
||||
|
||||
|
||||
## Fixes
|
||||
|
||||
As always, there are a number of fixes in this release. Mostly related to layout. See [CHANGELOG.md](https://github.com/Textualize/textual/blob/main/CHANGELOG.md) for the details.
|
||||
|
||||
## What's next?
|
||||
|
||||
The next release will focus on *pain points* we discovered while in a dog-fooding phase (see the [DevLog](https://textual.textualize.io/blog/category/devlog/) for details on what Textual devs have been building).
|
||||
|
||||
@@ -3,6 +3,9 @@ from textual.widgets import ListView, ListItem, Label, Footer
|
||||
|
||||
|
||||
class ListViewExample(App):
|
||||
|
||||
CSS_PATH = "list_view.css"
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield ListView(
|
||||
ListItem(Label("One")),
|
||||
@@ -12,6 +15,6 @@ class ListViewExample(App):
|
||||
yield Footer()
|
||||
|
||||
|
||||
app = ListViewExample(css_path="list_view.css")
|
||||
if __name__ == "__main__":
|
||||
app = ListViewExample()
|
||||
app.run()
|
||||
|
||||
@@ -22,7 +22,7 @@ class PlaceholderApp(App):
|
||||
Placeholder(variant="size", id="col3"),
|
||||
id="c1",
|
||||
),
|
||||
id="bot"
|
||||
id="bot",
|
||||
),
|
||||
Container(
|
||||
Placeholder(variant="text", id="left"),
|
||||
|
||||
@@ -4,12 +4,12 @@ from textual.widgets import Tree
|
||||
|
||||
class TreeApp(App):
|
||||
def compose(self) -> ComposeResult:
|
||||
tree: Tree = Tree("Dune")
|
||||
tree: Tree[dict] = Tree("Dune")
|
||||
tree.root.expand()
|
||||
characters = tree.root.add("Characters", expand=True)
|
||||
characters.add_leaf("Paul")
|
||||
characters.add_leaf("Jessica")
|
||||
characters.add_leaf("Channi")
|
||||
characters.add_leaf("Chani")
|
||||
yield tree
|
||||
|
||||
|
||||
|
||||
@@ -39,5 +39,5 @@ The `DirectoryTree.FileSelected` message is sent when the user selects a file in
|
||||
|
||||
## See Also
|
||||
|
||||
* [Tree][textual.widgets.DirectoryTree] code reference
|
||||
* [DirectoryTree][textual.widgets.DirectoryTree] code reference
|
||||
* [Tree][textual.widgets.Tree] code reference
|
||||
|
||||
@@ -21,10 +21,16 @@ The example below shows an app with a simple `ListView`.
|
||||
--8<-- "docs/examples/widgets/list_view.py"
|
||||
```
|
||||
|
||||
=== "list_view.css"
|
||||
|
||||
```sass
|
||||
--8<-- "docs/examples/widgets/list_view.css"
|
||||
```
|
||||
|
||||
## Reactive Attributes
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------|-------|---------|---------------------------------|
|
||||
| ------- | ----- | ------- | ------------------------------- |
|
||||
| `index` | `int` | `0` | The currently highlighted index |
|
||||
|
||||
## Messages
|
||||
@@ -40,7 +46,7 @@ click on a list item.
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
|-----------|------------|--------------------------------|
|
||||
| --------- | ---------- | ------------------------------ |
|
||||
| `item` | `ListItem` | The item that was highlighted. |
|
||||
|
||||
### Selected
|
||||
@@ -54,7 +60,7 @@ or by clicking on it.
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
|-----------|------------|-----------------------------|
|
||||
| --------- | ---------- | --------------------------- |
|
||||
| `item` | `ListItem` | The item that was selected. |
|
||||
|
||||
|
||||
@@ -68,7 +74,7 @@ are changed (e.g. a child is added, or the list is cleared).
|
||||
#### Attributes
|
||||
|
||||
| attribute | type | purpose |
|
||||
|------------|------------------|---------------------------|
|
||||
| ---------- | ---------------- | ------------------------- |
|
||||
| `children` | `list[ListItem]` | The new ListView children |
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,13 @@ class TreeApp(App):
|
||||
highlighter = ReprHighlighter()
|
||||
|
||||
def add_node(name: str, node: TreeNode, data: object) -> None:
|
||||
"""Adds a node to the tree.
|
||||
|
||||
Args:
|
||||
name (str): Name of the node.
|
||||
node (TreeNode): Parent node.
|
||||
data (object): Data associated with the node.
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
node._label = Text(f"{{}} {name}")
|
||||
for key, value in data.items():
|
||||
@@ -56,20 +63,24 @@ class TreeApp(App):
|
||||
add_node("JSON", node, json_data)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Load some JSON when the app starts."""
|
||||
with open("food.json") as data_file:
|
||||
self.json_data = json.load(data_file)
|
||||
|
||||
def action_add(self) -> None:
|
||||
"""Add a node to the tree."""
|
||||
tree = self.query_one(Tree)
|
||||
json_node = tree.root.add("JSON")
|
||||
self.add_json(json_node, self.json_data)
|
||||
tree.root.expand()
|
||||
|
||||
def action_clear(self) -> None:
|
||||
"""Clear the tree (remove all nodes)."""
|
||||
tree = self.query_one(Tree)
|
||||
tree.clear()
|
||||
|
||||
def action_toggle_root(self) -> None:
|
||||
"""Toggle the root node."""
|
||||
tree = self.query_one(Tree)
|
||||
tree.show_root = not tree.show_root
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "textual"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
homepage = "https://github.com/Textualize/textual"
|
||||
description = "Modern Text User Interface framework"
|
||||
authors = ["Will McGugan <will@textualize.io>"]
|
||||
|
||||
@@ -27,8 +27,8 @@ from . import errors
|
||||
from ._cells import cell_len
|
||||
from ._loop import loop_last
|
||||
from ._types import Lines
|
||||
from .geometry import Offset, Region, Size
|
||||
from ._typing import TypeAlias
|
||||
from .geometry import NULL_OFFSET, Offset, Region, Size
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .widget import Widget
|
||||
@@ -334,7 +334,7 @@ class Compositor:
|
||||
tuple[CompositorMap, set[Widget]]: Compositor map and set of widgets.
|
||||
"""
|
||||
|
||||
ORIGIN = Offset(0, 0)
|
||||
ORIGIN = NULL_OFFSET
|
||||
|
||||
map: CompositorMap = {}
|
||||
widgets: set[Widget] = set()
|
||||
|
||||
@@ -9,46 +9,46 @@ when setting and getting.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Generic, Iterable, NamedTuple, TypeVar, TYPE_CHECKING, cast
|
||||
from operator import attrgetter
|
||||
from typing import TYPE_CHECKING, Generic, Iterable, NamedTuple, TypeVar, cast
|
||||
|
||||
import rich.repr
|
||||
from rich.style import Style
|
||||
|
||||
from ._help_text import (
|
||||
border_property_help_text,
|
||||
layout_property_help_text,
|
||||
fractional_property_help_text,
|
||||
offset_property_help_text,
|
||||
style_flags_property_help_text,
|
||||
)
|
||||
from ._help_text import (
|
||||
spacing_wrong_number_of_values_help_text,
|
||||
scalar_help_text,
|
||||
string_enum_help_text,
|
||||
color_property_help_text,
|
||||
)
|
||||
from .._border import normalize_border_value
|
||||
from ..color import Color, ColorParseError
|
||||
from ..geometry import Spacing, SpacingDimensions, clamp
|
||||
from ._error_tools import friendly_list
|
||||
from ._help_text import (
|
||||
border_property_help_text,
|
||||
color_property_help_text,
|
||||
fractional_property_help_text,
|
||||
layout_property_help_text,
|
||||
offset_property_help_text,
|
||||
scalar_help_text,
|
||||
spacing_wrong_number_of_values_help_text,
|
||||
string_enum_help_text,
|
||||
style_flags_property_help_text,
|
||||
)
|
||||
from .constants import NULL_SPACING, VALID_STYLE_FLAGS
|
||||
from .errors import StyleTypeError, StyleValueError
|
||||
from .scalar import (
|
||||
get_symbols,
|
||||
NULL_SCALAR,
|
||||
UNIT_SYMBOL,
|
||||
Unit,
|
||||
Scalar,
|
||||
ScalarOffset,
|
||||
ScalarParseError,
|
||||
Unit,
|
||||
get_symbols,
|
||||
percentage_string_to_float,
|
||||
)
|
||||
from .transition import Transition
|
||||
from ..geometry import Spacing, SpacingDimensions, clamp
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .._layout import Layout
|
||||
from .styles import Styles, StylesBase
|
||||
|
||||
from .types import DockEdge, EdgeType, AlignHorizontal, AlignVertical
|
||||
from .types import AlignHorizontal, AlignVertical, DockEdge, EdgeType
|
||||
|
||||
BorderDefinition = (
|
||||
"Sequence[tuple[EdgeType, str | Color] | None] | tuple[EdgeType, str | Color]"
|
||||
@@ -138,8 +138,7 @@ class ScalarProperty:
|
||||
Returns:
|
||||
The Scalar object or ``None`` if it's not set.
|
||||
"""
|
||||
value = obj.get_rule(self.name)
|
||||
return value
|
||||
return obj.get_rule(self.name)
|
||||
|
||||
def __set__(
|
||||
self, obj: StylesBase, value: float | int | Scalar | str | None
|
||||
@@ -208,8 +207,7 @@ class ScalarListProperty:
|
||||
def __get__(
|
||||
self, obj: StylesBase, objtype: type[StylesBase] | None = None
|
||||
) -> tuple[Scalar, ...] | None:
|
||||
value = obj.get_rule(self.name)
|
||||
return value
|
||||
return obj.get_rule(self.name)
|
||||
|
||||
def __set__(
|
||||
self, obj: StylesBase, value: str | Iterable[str | float] | None
|
||||
@@ -265,10 +263,7 @@ class BoxProperty:
|
||||
A ``tuple[EdgeType, Style]`` containing the string type of the box and
|
||||
it's style. Example types are "rounded", "solid", and "dashed".
|
||||
"""
|
||||
box_type, color = obj.get_rule(self.name) or ("", self._default_color)
|
||||
if box_type in {"none", "hidden"}:
|
||||
box_type = ""
|
||||
return (box_type, color)
|
||||
return obj.get_rule(self.name) or ("", self._default_color)
|
||||
|
||||
def __set__(self, obj: Styles, border: tuple[EdgeType, str | Color] | None):
|
||||
"""Set the box property
|
||||
@@ -289,6 +284,8 @@ class BoxProperty:
|
||||
obj.refresh(layout=True)
|
||||
else:
|
||||
_type, color = border
|
||||
if _type in ("none", "hidden"):
|
||||
_type = ""
|
||||
new_value = border
|
||||
if isinstance(color, str):
|
||||
try:
|
||||
@@ -370,6 +367,7 @@ class BorderProperty:
|
||||
f"{name}_bottom",
|
||||
f"{name}_left",
|
||||
)
|
||||
self._get_properties = attrgetter(*self._properties)
|
||||
|
||||
def __get__(
|
||||
self, obj: StylesBase, objtype: type[StylesBase] | None = None
|
||||
@@ -383,15 +381,8 @@ class BorderProperty:
|
||||
Returns:
|
||||
An ``Edges`` object describing the type and style of each edge.
|
||||
"""
|
||||
top, right, bottom, left = self._properties
|
||||
|
||||
border = Edges(
|
||||
getattr(obj, top),
|
||||
getattr(obj, right),
|
||||
getattr(obj, bottom),
|
||||
getattr(obj, left),
|
||||
)
|
||||
return border
|
||||
return Edges(*self._get_properties(obj))
|
||||
|
||||
def __set__(
|
||||
self,
|
||||
@@ -416,27 +407,14 @@ class BorderProperty:
|
||||
_rich_traceback_omit = True
|
||||
top, right, bottom, left = self._properties
|
||||
|
||||
border_spacing = Edges(
|
||||
getattr(obj, top),
|
||||
getattr(obj, right),
|
||||
getattr(obj, bottom),
|
||||
getattr(obj, left),
|
||||
).spacing
|
||||
border_spacing = Edges(*self._get_properties(obj)).spacing
|
||||
|
||||
def check_refresh() -> None:
|
||||
"""Check if an update requires a layout"""
|
||||
if not self._layout:
|
||||
obj.refresh()
|
||||
else:
|
||||
layout = (
|
||||
Edges(
|
||||
getattr(obj, top),
|
||||
getattr(obj, right),
|
||||
getattr(obj, bottom),
|
||||
getattr(obj, left),
|
||||
).spacing
|
||||
!= border_spacing
|
||||
)
|
||||
layout = Edges(*self._get_properties(obj)).spacing != border_spacing
|
||||
obj.refresh(layout=layout)
|
||||
|
||||
if border is None:
|
||||
@@ -601,10 +579,10 @@ class LayoutProperty:
|
||||
"""
|
||||
|
||||
from ..layouts.factory import (
|
||||
get_layout,
|
||||
Layout,
|
||||
Layout, # Prevents circular import
|
||||
MissingLayout,
|
||||
) # Prevents circular import
|
||||
get_layout,
|
||||
)
|
||||
|
||||
_rich_traceback_omit = True
|
||||
if layout is None:
|
||||
@@ -647,7 +625,7 @@ class OffsetProperty:
|
||||
ScalarOffset: The ``ScalarOffset`` indicating the adjustment that
|
||||
will be made to widget position prior to it being rendered.
|
||||
"""
|
||||
return obj.get_rule(self.name, ScalarOffset.null())
|
||||
return obj.get_rule(self.name, NULL_SCALAR)
|
||||
|
||||
def __set__(
|
||||
self, obj: StylesBase, offset: tuple[int | str, int | str] | ScalarOffset | None
|
||||
|
||||
@@ -16,12 +16,10 @@ a method which evaluates the query, such as first() and last().
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast, Generic, TYPE_CHECKING, Iterator, TypeVar, overload
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Generic, Iterator, TypeVar, cast, overload
|
||||
|
||||
import rich.repr
|
||||
|
||||
from .. import events
|
||||
from .._context import active_app
|
||||
from ..await_remove import AwaitRemove
|
||||
from .errors import DeclarationError, TokenError
|
||||
|
||||
@@ -351,8 +351,7 @@ class StylesBase(ABC):
|
||||
Returns:
|
||||
Spacing: Space around widget content.
|
||||
"""
|
||||
spacing = self.padding + self.border.spacing
|
||||
return spacing
|
||||
return self.padding + self.border.spacing
|
||||
|
||||
@property
|
||||
def auto_dimensions(self) -> bool:
|
||||
@@ -993,9 +992,10 @@ class RenderStyles(StylesBase):
|
||||
Returns:
|
||||
Spacing: Space around widget content.
|
||||
"""
|
||||
# This is (surprisingly) a bit of a bottleneck
|
||||
if self._gutter is not None:
|
||||
cache_key, gutter = self._gutter
|
||||
if cache_key == self._updates:
|
||||
if cache_key == self._cache_key:
|
||||
return gutter
|
||||
gutter = self.padding + self.border.spacing
|
||||
self._gutter = (self._cache_key, gutter)
|
||||
|
||||
@@ -322,7 +322,7 @@ class Region(NamedTuple):
|
||||
|
||||
if region in window_region:
|
||||
# Region is already inside the window, so no need to move it.
|
||||
return Offset(0, 0)
|
||||
return NULL_OFFSET
|
||||
|
||||
window_left, window_top, window_right, window_bottom = window_region.corners
|
||||
region = region.crop_size(window_region.size)
|
||||
@@ -691,7 +691,8 @@ class Region(NamedTuple):
|
||||
Returns:
|
||||
Region: New region.
|
||||
"""
|
||||
|
||||
if not any(margin):
|
||||
return self
|
||||
top, right, bottom, left = margin
|
||||
x, y, width, height = self
|
||||
return Region(
|
||||
|
||||
@@ -4,7 +4,7 @@ from math import ceil
|
||||
|
||||
import rich.repr
|
||||
from rich.color import Color
|
||||
from rich.console import ConsoleOptions, RenderableType, RenderResult
|
||||
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
||||
from rich.segment import Segment, Segments
|
||||
from rich.style import Style, StyleType
|
||||
|
||||
|
||||
@@ -973,10 +973,9 @@ class Widget(DOMNode):
|
||||
Returns:
|
||||
Spacing: Scrollbar gutter spacing.
|
||||
"""
|
||||
gutter = Spacing(
|
||||
return Spacing(
|
||||
0, self.scrollbar_size_vertical, self.scrollbar_size_horizontal, 0
|
||||
)
|
||||
return gutter
|
||||
|
||||
@property
|
||||
def gutter(self) -> Spacing:
|
||||
|
||||
@@ -22,6 +22,14 @@ class DirEntry:
|
||||
|
||||
|
||||
class DirectoryTree(Tree[DirEntry]):
|
||||
"""A Tree widget that presents files and directories.
|
||||
|
||||
Args:
|
||||
path (str): Path to directory.
|
||||
name (str | None, optional): The name of the widget, or None for no name. Defaults to None.
|
||||
id (str | None, optional): The ID of the widget in the DOM, or None for no ID. Defaults to None.
|
||||
classes (str | None, optional): A space-separated list of classes, or None for no classes. Defaults to None.
|
||||
"""
|
||||
|
||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||
"tree--label",
|
||||
@@ -68,6 +76,7 @@ class DirectoryTree(Tree[DirEntry]):
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
) -> None:
|
||||
|
||||
self.path = path
|
||||
super().__init__(
|
||||
path,
|
||||
|
||||
@@ -7,7 +7,6 @@ from ..containers import Container
|
||||
from ..css._error_tools import friendly_list
|
||||
from ..reactive import Reactive, reactive
|
||||
from ..widget import Widget, RenderResult
|
||||
from ..widgets import Label
|
||||
from .._typing import Literal
|
||||
|
||||
PlaceholderVariant = Literal["default", "size", "text"]
|
||||
@@ -65,6 +64,7 @@ class Placeholder(Container):
|
||||
DEFAULT_CSS = """
|
||||
Placeholder {
|
||||
align: center middle;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
Placeholder.-text {
|
||||
@@ -73,6 +73,7 @@ class Placeholder(Container):
|
||||
|
||||
_PlaceholderLabel {
|
||||
height: auto;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
Placeholder > _PlaceholderLabel {
|
||||
@@ -137,7 +138,7 @@ class Placeholder(Container):
|
||||
"-size",
|
||||
)
|
||||
self._text_label = _PlaceholderLabel(
|
||||
_LOREM_IPSUM_PLACEHOLDER_TEXT,
|
||||
"\n\n".join(_LOREM_IPSUM_PLACEHOLDER_TEXT for _ in range(5)),
|
||||
"-text",
|
||||
)
|
||||
super().__init__(
|
||||
@@ -149,7 +150,7 @@ class Placeholder(Container):
|
||||
classes=classes,
|
||||
)
|
||||
|
||||
self.styles.background = f"{next(Placeholder._COLORS)} 70%"
|
||||
self.styles.background = f"{next(Placeholder._COLORS)} 50%"
|
||||
|
||||
self.variant = self.validate_variant(variant)
|
||||
# Set a cycle through the variants with the correct starting point.
|
||||
|
||||
@@ -78,7 +78,7 @@ class TextLog(ScrollView, can_focus=True):
|
||||
else:
|
||||
renderable = Text(content)
|
||||
if self.highlight:
|
||||
renderable = self.highlighter(content)
|
||||
renderable = self.highlighter(renderable)
|
||||
else:
|
||||
renderable = cast(RenderableType, content)
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
9
tests/test_style_properties.py
Normal file
9
tests/test_style_properties.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from textual.color import Color
|
||||
from textual.css.styles import Styles
|
||||
|
||||
|
||||
def test_box_normalization():
|
||||
"""Check that none or hidden is normalized to empty string."""
|
||||
styles = Styles()
|
||||
styles.border_left = ("none", "red")
|
||||
assert styles.border_left == ("", Color.parse("red"))
|
||||
Reference in New Issue
Block a user