Merge pull request #606 from Textualize/render-enhancements

Render enhancements
This commit is contained in:
Will McGugan
2022-07-12 10:44:57 +01:00
committed by GitHub
19 changed files with 146 additions and 232 deletions

35
poetry.lock generated
View File

@@ -52,7 +52,7 @@ python-versions = ">=3.5"
[[package]] [[package]]
name = "atomicwrites" name = "atomicwrites"
version = "1.4.0" version = "1.4.1"
description = "Atomic file writes." description = "Atomic file writes."
category = "dev" category = "dev"
optional = false optional = false
@@ -208,7 +208,7 @@ dev = ["twine", "markdown", "flake8", "wheel"]
[[package]] [[package]]
name = "griffe" name = "griffe"
version = "0.21.0" version = "0.22.0"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
category = "dev" category = "dev"
optional = false optional = false
@@ -345,7 +345,7 @@ mkdocs = ">=1.1"
[[package]] [[package]]
name = "mkdocs-material" name = "mkdocs-material"
version = "8.3.8" version = "8.3.9"
description = "Documentation that simply works" description = "Documentation that simply works"
category = "dev" category = "dev"
optional = false optional = false
@@ -713,7 +713,7 @@ python-versions = ">=3.6"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.2.0" version = "4.3.0"
description = "Backported and Experimental Type Hints for Python 3.7+" description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main" category = "main"
optional = false optional = false
@@ -721,7 +721,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.15.0" version = "20.15.1"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
category = "dev" category = "dev"
optional = false optional = false
@@ -869,10 +869,7 @@ asynctest = [
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
] ]
atomicwrites = [ atomicwrites = []
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [ attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
@@ -1042,10 +1039,7 @@ ghp-import = [
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
] ]
griffe = [ griffe = []
{file = "griffe-0.21.0-py3-none-any.whl", hash = "sha256:e9fb5eeb7c721e1d84804452bdc742bd57b120b13aba663157668ae2d217088a"},
{file = "griffe-0.21.0.tar.gz", hash = "sha256:61ab3bc02b09afeb489f1aef44c646a09f1837d9cdf15943ac6021903a4d3984"},
]
identify = [ identify = [
{file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"}, {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"},
{file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"}, {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"},
@@ -1124,10 +1118,7 @@ mkdocs-autorefs = [
{file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
] ]
mkdocs-material = [ mkdocs-material = []
{file = "mkdocs-material-8.3.8.tar.gz", hash = "sha256:b9cd305c3c29ef758931dae06e4aea0ca9f8bcc8ac6b2d45f10f932a015d6b83"},
{file = "mkdocs_material-8.3.8-py2.py3-none-any.whl", hash = "sha256:949c75fa934d4b9ecc7b519964e58f0c9fc29f2ceb04736c85809cdbc403dfb5"},
]
mkdocs-material-extensions = [ mkdocs-material-extensions = [
{file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"}, {file = "mkdocs-material-extensions-1.0.3.tar.gz", hash = "sha256:bfd24dfdef7b41c312ede42648f9eb83476ea168ec163b613f9abd12bbfddba2"},
{file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"}, {file = "mkdocs_material_extensions-1.0.3-py3-none-any.whl", hash = "sha256:a82b70e533ce060b2a5d9eb2bc2e1be201cf61f901f93704b4acf6e3d5983a44"},
@@ -1468,14 +1459,8 @@ typed-ast = [
{file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"},
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
] ]
typing-extensions = [ typing-extensions = []
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, virtualenv = []
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
]
virtualenv = [
{file = "virtualenv-20.15.0-py2.py3-none-any.whl", hash = "sha256:804cce4de5b8a322f099897e308eecc8f6e2951f1a8e7e2b3598dff865f01336"},
{file = "virtualenv-20.15.0.tar.gz", hash = "sha256:4c44b1d77ca81f8368e2d7414f9b20c428ad16b343ac6d226206c5b84e2b4fcc"},
]
watchdog = [ watchdog = [
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},

BIN
reference/box.monopic Normal file

Binary file not shown.

View File

@@ -1,4 +1,6 @@
Button { Button {
padding-left: 1; box-sizing: border-box;
padding-right: 1; margin: 1;
width: 100%;
} }

View File

@@ -6,14 +6,6 @@
transition: color 300ms linear, background 300ms linear; transition: color 300ms linear, background 300ms linear;
} }
* {
scrollbar-background: $panel-darken-2;
scrollbar-background-hover: $panel-darken-3;
scrollbar-color: $system;
scrollbar-color-active: $accent-darken-1;
scrollbar-size-horizontal: 1;
scrollbar-size-vertical: 2;
}
*:hover { *:hover {
/* tint: 30% red; /* tint: 30% red;
@@ -74,8 +66,8 @@ DataTable {
} }
#header { #header {
color: $text-primary-darken-1; color: $text-primary-background-darken-1;
background: $primary-darken-1; background: $primary-background-darken-1;
height: 3; height: 3;
content-align: center middle; content-align: center middle;
} }
@@ -92,7 +84,7 @@ Tweet {
height:12; height:12;
width: 100%; width: 100%;
margin: 1 3;
background: $panel; background: $panel;
color: $text-panel; color: $text-panel;
layout: vertical; layout: vertical;
@@ -117,7 +109,6 @@ Tweet {
.code { .code {
height: auto; height: auto;
} }
@@ -172,7 +163,7 @@ Tweet.scroll-horizontal TweetBody {
color: $text-accent; color: $text-accent;
background: $accent; background: $accent;
height: 1; height: 1;
border-top: hkey $accent-darken-2;
content-align: center middle; content-align: center middle;
} }
@@ -191,12 +182,12 @@ OptionItem {
OptionItem:hover { OptionItem:hover {
height: 3; height: 3;
color: $accent; color: $secondary;
background: $primary-background-darken-1; background: $primary-background-darken-1;
/* border-top: hkey $accent2-darken-3; /* border-top: hkey $accent2-darken-3;
border-bottom: hkey $accent2-darken-3; */ border-bottom: hkey $accent2-darken-3; */
text-style: bold; text-style: bold;
border-left: outer $accent-darken-2; border-left: outer $secondary-darken-2;
} }
Error { Error {
@@ -204,10 +195,10 @@ Error {
height:3; height:3;
background: $error; background: $error;
color: $text-error; color: $text-error;
border-top: hkey $error-darken-2; border-top: wide $error-darken-1;
border-bottom: hkey $error-darken-2; border-bottom: wide $error-darken-1;
margin: 1 3;
padding: 0;
text-style: bold; text-style: bold;
align-horizontal: center; align-horizontal: center;
} }
@@ -217,9 +208,9 @@ Warning {
height:3; height:3;
background: $warning; background: $warning;
color: $text-warning-fade-1; color: $text-warning-fade-1;
border-top: hkey $warning-darken-2; border-top: wide $warning-darken-1;
border-bottom: hkey $warning-darken-2; border-bottom: wide $warning-darken-1;
margin: 1 2;
text-style: bold; text-style: bold;
align-horizontal: center; align-horizontal: center;
} }
@@ -229,13 +220,14 @@ Success {
width:90%; width:90%;
height:auto; height:auto;
box-sizing: border-box; box-sizing: border-box;
background: $success-lighten-3; background: $success;
color: $text-success-lighten-3-fade-1; color: $text-success;
border-top: hkey $success; border-top: hkey $success-darken-1;
border-bottom: hkey $success; border-bottom: hkey $success-darken-1;
text-style: bold underline;
text-style: bold;
align-horizontal: center; align-horizontal: center;
} }

View File

@@ -88,7 +88,7 @@ class Warning(Widget):
class Success(Widget): class Success(Widget):
def render(self) -> Text: def render(self) -> Text:
return Text("This\nis\na\nsuccess\n message", justify="center") return Text("This is a success message", justify="center")
class BasicApp(App, css_path="basic.css"): class BasicApp(App, css_path="basic.css"):

View File

@@ -225,8 +225,7 @@ class StylesCache:
from_color = Style.from_color from_color = Style.from_color
rich_style = styles.rich_style inner = from_color(bgcolor=(base_background + background).rich_color)
inner = from_color(bgcolor=background.rich_color) + rich_style
outer = from_color(bgcolor=base_background.rich_color) outer = from_color(bgcolor=base_background.rich_color)
def post(segments: Iterable[Segment]) -> list[Segment]: def post(segments: Iterable[Segment]) -> list[Segment]:
@@ -265,9 +264,7 @@ class StylesCache:
elif (pad_top and y < gutter.top) or ( elif (pad_top and y < gutter.top) or (
pad_bottom and y >= height - gutter.bottom pad_bottom and y >= height - gutter.bottom
): ):
background_style = from_color( background_style = from_color(bgcolor=background.rich_color)
color=rich_style.color, bgcolor=background.rich_color
)
left_style = from_color(color=border_left_color.rich_color) left_style = from_color(color=border_left_color.rich_color)
left = get_box(border_left, inner, outer, left_style)[1][0] left = get_box(border_left, inner, outer, left_style)[1][0]
right_style = from_color(color=border_right_color.rich_color) right_style = from_color(color=border_right_color.rich_color)

View File

@@ -85,12 +85,13 @@ DEFAULT_COLORS = ColorSystem(
secondary="#ffa62b", secondary="#ffa62b",
warning="#ffa62b", warning="#ffa62b",
error="#ba3c5b", error="#ba3c5b",
success="#6d9f71", success="#4EBF71",
accent="#ffa62b", accent="#1A75B4",
system="#5a4599", system="#5a4599",
dark_surface="#292929", dark_surface="#292929",
) )
ComposeResult = Iterable[Widget] ComposeResult = Iterable[Widget]

View File

@@ -62,7 +62,7 @@ def get_box_model(
else: else:
# An explicit width # An explicit width
content_width = styles.width.resolve_dimension( content_width = styles.width.resolve_dimension(
sizing_container, viewport, fraction_unit sizing_container - styles.margin.totals, viewport, fraction_unit
) )
if is_border_box: if is_border_box:
content_width -= gutter.width content_width -= gutter.width

View File

@@ -834,10 +834,10 @@ class ColorProperty:
_rich_traceback_omit = True _rich_traceback_omit = True
if color is None: if color is None:
if obj.clear_rule(self.name): if obj.clear_rule(self.name):
obj.refresh() obj.refresh(children=True)
elif isinstance(color, Color): elif isinstance(color, Color):
if obj.set_rule(self.name, color): if obj.set_rule(self.name, color):
obj.refresh() obj.refresh(children=True)
elif isinstance(color, str): elif isinstance(color, str):
try: try:
parsed_color = Color.parse(color) parsed_color = Color.parse(color)
@@ -849,7 +849,7 @@ class ColorProperty:
), ),
) )
if obj.set_rule(self.name, parsed_color): if obj.set_rule(self.name, parsed_color):
obj.refresh() obj.refresh(children=True)
else: else:
raise StyleValueError(f"Invalid color value {color}") raise StyleValueError(f"Invalid color value {color}")

View File

@@ -43,6 +43,7 @@ VALID_STYLE_FLAGS: Final = {
"none", "none",
"not", "not",
"bold", "bold",
"blink",
"italic", "italic",
"underline", "underline",
"overline", "overline",
@@ -50,7 +51,9 @@ VALID_STYLE_FLAGS: Final = {
"b", "b",
"i", "i",
"u", "u",
"uu",
"o", "o",
"reverse",
} }
NULL_SPACING: Final = Spacing.all(0) NULL_SPACING: Final = Spacing.all(0)

View File

@@ -178,6 +178,8 @@ class StylesBase(ABC):
"scrollbar_background_active", "scrollbar_background_active",
} }
node: DOMNode | None = None
display = StringEnumProperty(VALID_DISPLAY, "block", layout=True) display = StringEnumProperty(VALID_DISPLAY, "block", layout=True)
visibility = StringEnumProperty(VALID_VISIBILITY, "visible") visibility = StringEnumProperty(VALID_VISIBILITY, "visible")
layout = LayoutProperty() layout = LayoutProperty()
@@ -325,11 +327,12 @@ class StylesBase(ABC):
""" """
@abstractmethod @abstractmethod
def refresh(self, *, layout: bool = False) -> None: def refresh(self, *, layout: bool = False, children: bool = False) -> None:
"""Mark the styles as requiring a refresh. """Mark the styles as requiring a refresh.
Args: Args:
layout (bool, optional): Also require a layout. Defaults to False. layout (bool, optional): Also require a layout. Defaults to False.
children (bool, opional): Also refresh children. Defaults to False.
""" """
@abstractmethod @abstractmethod
@@ -439,7 +442,6 @@ class StylesBase(ABC):
class Styles(StylesBase): class Styles(StylesBase):
node: DOMNode | None = None node: DOMNode | None = None
_rules: RulesMap = field(default_factory=dict) _rules: RulesMap = field(default_factory=dict)
important: set[str] = field(default_factory=set) important: set[str] = field(default_factory=set)
@@ -486,9 +488,12 @@ class Styles(StylesBase):
def get_rule(self, rule: str, default: object = None) -> object: def get_rule(self, rule: str, default: object = None) -> object:
return self._rules.get(rule, default) return self._rules.get(rule, default)
def refresh(self, *, layout: bool = False) -> None: def refresh(self, *, layout: bool = False, children: bool = False) -> None:
if self.node is not None: if self.node is not None:
self.node.refresh(layout=layout) self.node.refresh(layout=layout)
if children:
for child in self.node.walk_children(with_self=False):
child.refresh(layout=layout)
def reset(self) -> None: def reset(self) -> None:
"""Reset the rules to initial state.""" """Reset the rules to initial state."""
@@ -783,8 +788,8 @@ class RenderStyles(StylesBase):
if self.has_rule(rule_name): if self.has_rule(rule_name):
yield rule_name, getattr(self, rule_name) yield rule_name, getattr(self, rule_name)
def refresh(self, *, layout: bool = False) -> None: def refresh(self, *, layout: bool = False, children: bool = False) -> None:
self._inline_styles.refresh(layout=layout) self._inline_styles.refresh(layout=layout, children=children)
def merge(self, other: Styles) -> None: def merge(self, other: Styles) -> None:
"""Merge values from another Styles. """Merge values from another Styles.

View File

@@ -143,7 +143,7 @@ class Token(NamedTuple):
yield "name", self.name yield "name", self.name
yield "value", self.value yield "value", self.value
yield "path", self.path yield "path", self.path
yield "code", self.code yield "code", self.code if len(self.code) < 40 else self.code[:40] + "..."
yield "location", self.location yield "location", self.location
yield "referenced_by", self.referenced_by, None yield "referenced_by", self.referenced_by, None

View File

@@ -7,6 +7,7 @@ class Vertical(Widget):
CSS = """ CSS = """
Vertical { Vertical {
layout: vertical; layout: vertical;
overflow: auto;
} }
""" """
@@ -17,5 +18,6 @@ class Horizontal(Widget):
CSS = """ CSS = """
Horizontal { Horizontal {
layout: horizontal; layout: horizontal;
overflow: auto;
} }
""" """

View File

@@ -212,18 +212,16 @@ class ScrollBar(Widget):
def render(self) -> RenderableType: def render(self) -> RenderableType:
styles = self.parent.styles styles = self.parent.styles
scrollbar_style = Style( background = (
bgcolor=( styles.scrollbar_background_hover
styles.scrollbar_background_hover.rich_color if self.mouse_over
if self.mouse_over else styles.scrollbar_background
else styles.scrollbar_background.rich_color
),
color=(
styles.scrollbar_color_active.rich_color
if self.grabbed
else styles.scrollbar_color.rich_color
),
) )
color = (
styles.scrollbar_color_active if self.grabbed else styles.scrollbar_color
)
color = background + color
scrollbar_style = Style.from_color(color.rich_color, background.rich_color)
return ScrollBarRender( return ScrollBarRender(
virtual_size=self.window_virtual_size, virtual_size=self.window_virtual_size,
window_size=self.window_size, window_size=self.window_size,

View File

@@ -16,9 +16,11 @@ import rich.repr
from rich.align import Align from rich.align import Align
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.measure import Measurement from rich.measure import Measurement
from rich.padding import Padding
from rich.segment import Segment from rich.segment import Segment
from rich.style import Style from rich.style import Style
from rich.styled import Styled
from rich.text import Text
from . import errors, events, messages from . import errors, events, messages
from ._animator import BoundAnimator from ._animator import BoundAnimator
@@ -72,10 +74,9 @@ class Widget(DOMNode):
scrollbar-background: $panel-darken-2; scrollbar-background: $panel-darken-2;
scrollbar-background-hover: $panel-darken-3; scrollbar-background-hover: $panel-darken-3;
scrollbar-color: $system; scrollbar-color: $system;
scrollbar-color-active: $secondary-darken-1; scrollbar-color-active: $warning-darken-1;
scrollbar-size-vertical: 2; scrollbar-size-vertical: 2;
scrollbar-size-horizontal: 1; scrollbar-size-horizontal: 1;
} }
""" """
@@ -879,54 +880,7 @@ class Widget(DOMNode):
def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None: def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None:
watch(self, attribute_name, callback) watch(self, attribute_name, callback)
def _style_renderable(self, renderable: RenderableType) -> RenderableType: def _render_styled(self) -> RenderableType:
"""Applies CSS styles to a renderable by wrapping it in another renderable.
Args:
renderable (RenderableType): Renderable to apply styles to.
Returns:
RenderableType: An updated renderable.
"""
(base_background, base_color), (background, color) = self.colors
styles = self.styles
content_align = (styles.content_align_horizontal, styles.content_align_vertical)
if content_align != ("left", "top"):
horizontal, vertical = content_align
renderable = Align(renderable, horizontal, vertical=vertical)
renderable = Padding(
renderable,
styles.padding,
style=Style.from_color(color.rich_color, background.rich_color),
)
if styles.border:
renderable = Border(
renderable,
styles.border,
inner_color=background,
outer_color=base_background,
)
if styles.outline:
renderable = Border(
renderable,
styles.outline,
inner_color=styles.background,
outer_color=base_background,
outline=True,
)
if styles.tint.a != 0:
renderable = Tint(renderable, styles.tint)
if styles.opacity != 1.0:
renderable = Opacity(renderable, opacity=styles.opacity)
return renderable
def render_styled(self) -> RenderableType:
"""Applies style attributes to the default renderable. """Applies style attributes to the default renderable.
Returns: Returns:
@@ -934,8 +888,21 @@ class Widget(DOMNode):
""" """
renderable = self.render() renderable = self.render()
if isinstance(renderable, str):
renderable = Text.from_markup(renderable)
rich_style = self.rich_style
if isinstance(renderable, Text):
renderable.stylize(rich_style)
else:
renderable = Styled(renderable, rich_style)
styles = self.styles styles = self.styles
content_align = (styles.content_align_horizontal, styles.content_align_vertical) content_align = (
styles.content_align_horizontal,
styles.content_align_vertical,
)
if content_align != ("left", "top"): if content_align != ("left", "top"):
horizontal, vertical = content_align horizontal, vertical = content_align
renderable = Align(renderable, horizontal, vertical=vertical) renderable = Align(renderable, horizontal, vertical=vertical)
@@ -977,11 +944,11 @@ class Widget(DOMNode):
def _render_content(self) -> None: def _render_content(self) -> None:
"""Render all lines.""" """Render all lines."""
width, height = self.size width, height = self.size
renderable = self.render_styled() renderable = self._render_styled()
options = self.console.options.update_dimensions(width, height).update( options = self.console.options.update_dimensions(width, height).update(
highlight=False highlight=False
) )
lines = self.console.render_lines(renderable, options, style=self.rich_style) lines = self.console.render_lines(renderable, options)
self._render_cache = RenderCache(self.size, lines) self._render_cache = RenderCache(self.size, lines)
self._dirty_regions.clear() self._dirty_regions.clear()

View File

@@ -33,145 +33,96 @@ class Button(Widget, can_focus=True):
Button { Button {
width: auto; width: auto;
height: 3; height: 3;
background: $primary; background: $primary;
color: $text-primary; color: $text-primary;
border: tall $primary-lighten-3; border: none;
border-top: tall $primary-lighten-2;
border-bottom: tall $primary-darken-3;
content-align: center middle;
text-style: bold;
}
content-align: center middle; Button:focus {
margin: 1 0; text-style: bold underline;
align: center middle;
text-style: bold;
transition: background 0.1;/* for "active" effect */
} }
Button:hover { Button:hover {
border-top: tall $primary-lighten-1;
background: $primary-darken-2; background: $primary-darken-2;
color: $text-primary-darken-2; color: $text-primary-darken-2;
border: tall $primary-lighten-1;
} }
Button.-active { Button.-active {
background: $primary-lighten-1; background: $primary;
border-bottom: tall $primary-lighten-2;
border-top: tall $primary-darken-2;
} }
.-dark-mode Button {
background: $background;
color: $primary-lighten-2;
border: tall white $primary-lighten-2;
}
.-dark-mode Button:hover {
background: $surface;
}
.-dark-mode Button.-active {
background: $background-lighten-3;
}
/* Success variant */ /* Success variant */
Button.-success { Button.-success {
background: $success; background: $success;
color: $text-success; color: $text-success;
border: tall $success-lighten-3; border-top: tall $success-lighten-2;
border-bottom: tall $success-darken-3;
} }
Button.-success:hover { Button.-success:hover {
background: $success-darken-1; background: $success-darken-2;
color: $text-success-darken-1; color: $text-success-darken-2;
border: tall $success-lighten-2;
} }
Button.-success.-active { Button.-success.-active {
background: $success-lighten-1;
}
.-dark-mode Button.-success {
background: $success; background: $success;
color: $text-success; border-bottom: tall $success-lighten-2;
border: tall $success-lighten-3; border-top: tall $success-darken-2;
}
.-dark-mode Button.-success:hover {
background: $success-darken-1;
color: $text-success-darken-1;
border: tall $success-lighten-3;
}
.-dark-mode Button.-success.-active {
background: $success-lighten-1;
} }
/* Warning variant */ /* Warning variant */
Button.-warning { Button.-warning {
background: $warning; background: $warning;
color: $text-warning; color: $text-warning;
border: tall $warning-lighten-3; border-top: tall $warning-lighten-2;
border-bottom: tall $warning-darken-3;
} }
Button.-warning:hover { Button.-warning:hover {
background: $warning-darken-1; background: $warning-darken-2;
color: $text-warning-darken-1; color: $text-warning-darken-1;
border: tall $warning-lighten-3;
} }
Button.-warning.-active { Button.-warning.-active {
background: $warning; background: $warning;
border-bottom: tall $warning-lighten-2;
border-top: tall $warning-darken-2;
} }
.-dark-mode Button.-warning {
background: $warning;
color: $text-warning;
border: tall $warning-lighten-3;
}
.-dark-mode Button.-warning:hover {
background: $warning-darken-1;
color: $text-warning-darken-1;
border: tall $warning-lighten-3;
}
.-dark-mode Button.-warning.-active {
background: $warning-lighten-1;
}
/* Error variant */ /* Error variant */
Button.-error { Button.-error {
background: $error; background: $error;
color: $text-error; color: $text-error;
border: tall $error-lighten-3; border-top: tall $error-lighten-2;
border-bottom: tall $error-darken-3;
} }
Button.-error:hover { Button.-error:hover {
background: $error-darken-1; background: $error-darken-1;
color: $text-error-darken-1; color: $text-error-darken-2;
border: tall $error-lighten-3;
} }
Button.-error.-active { Button.-error.-active {
background: $error; background: $error;
border-bottom: tall $error-lighten-2;
border-top: tall $error-darken-2;
} }
.-dark-mode Button.-error {
background: $error;
color: $text-error;
border: tall $error-lighten-3;
}
.-dark-mode Button.-error:hover {
background: $error-darken-1;
color: $text-error-darken-1;
border: tall $error-lighten-3;
}
.-dark-mode Button.-error.-active {
background: $error;
}
App.-show-focus Button:focus {
tint: $accent 20%;
}
""" """
ACTIVE_EFFECT_DURATION = 0.3 ACTIVE_EFFECT_DURATION = 0.3

View File

@@ -113,8 +113,8 @@ class DataTable(ScrollView, Generic[CellType]):
} }
DataTable > .datatable--header { DataTable > .datatable--header {
text-style: bold; text-style: bold;
background: $primary; background: $primary-darken-1;
color: $text-primary; color: $text-primary-darken-1;
} }
DataTable > .datatable--fixed { DataTable > .datatable--fixed {
text-style: bold; text-style: bold;

View File

@@ -49,7 +49,7 @@ def test_width():
def get_auto_width(container: Size, parent: Size) -> int: def get_auto_width(container: Size, parent: Size) -> int:
return 10 return 10
def get_auto_height(container: Size, parent: Size) -> int: def get_auto_height(container: Size, parent: Size, width: int) -> int:
return 10 return 10
box_model = get_box_model( box_model = get_box_model(
@@ -88,7 +88,7 @@ def test_width():
box_model = get_box_model( box_model = get_box_model(
styles, Size(60, 20), Size(80, 24), one, get_auto_width, get_auto_height styles, Size(60, 20), Size(80, 24), one, get_auto_width, get_auto_height
) )
assert box_model == BoxModel(Fraction(60), Fraction(16), Spacing(1, 2, 3, 4)) assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4))
styles.width = "100vw" styles.width = "100vw"
styles.max_width = "50%" styles.max_width = "50%"
@@ -107,7 +107,7 @@ def test_height():
def get_auto_width(container: Size, parent: Size) -> int: def get_auto_width(container: Size, parent: Size) -> int:
return 10 return 10
def get_auto_height(container: Size, parent: Size) -> int: def get_auto_height(container: Size, parent: Size, width: int) -> int:
return 10 return 10
box_model = get_box_model( box_model = get_box_model(
@@ -139,6 +139,15 @@ def test_height():
) )
assert box_model == BoxModel(Fraction(54), Fraction(20), Spacing(1, 2, 3, 4)) assert box_model == BoxModel(Fraction(54), Fraction(20), Spacing(1, 2, 3, 4))
styles.height = "auto"
styles.margin = 2
box_model = get_box_model(
styles, Size(60, 20), Size(80, 24), one, get_auto_width, get_auto_height
)
assert box_model == BoxModel(Fraction(56), Fraction(10), Spacing(2, 2, 2, 2))
styles.margin = 1, 2, 3, 4
styles.height = "100vh" styles.height = "100vh"
styles.max_height = "50%" styles.max_height = "50%"

View File

@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from rich.segment import Segment from rich.segment import Segment
from rich.style import Style
from textual.color import Color from textual.color import Color
from textual.geometry import Region, Size from textual.geometry import Region, Size
@@ -41,10 +42,11 @@ def test_no_styles():
content.__getitem__, content.__getitem__,
content_size=Size(3, 3), content_size=Size(3, 3),
) )
style = Style.from_color(bgcolor=Color.parse("green").rich_color)
expected = [ expected = [
[Segment("foo", styles.rich_style)], [Segment("foo", style)],
[Segment("bar", styles.rich_style)], [Segment("bar", style)],
[Segment("baz", styles.rich_style)], [Segment("baz", style)],
] ]
assert lines == expected assert lines == expected