diff --git a/examples/basic.css b/examples/basic.css index 911fb9a88..efafe797e 100644 --- a/examples/basic.css +++ b/examples/basic.css @@ -2,6 +2,7 @@ App > View { docks: side=left/1; + text: on #20639b; } Widget:hover { @@ -28,6 +29,10 @@ Widget:hover { border: hkey; } +#header.-visible { + visibility: hidden; +} + #content { text: white on #20639b; border-bottom: hkey #0f2b41; diff --git a/examples/basic.py b/examples/basic.py index 72743ab93..d06ac0765 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -8,6 +8,7 @@ class BasicApp(App): def on_load(self): """Bind keys here.""" self.bind("tab", "toggle_class('#sidebar', '-active')") + self.bind("a", "toggle_class('#header', '-visible')") def on_mount(self): """Build layout here.""" diff --git a/src/textual/blank.py b/src/textual/blank.py new file mode 100644 index 000000000..db7fb49b2 --- /dev/null +++ b/src/textual/blank.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from rich.console import Console, ConsoleOptions +from rich.segment import Segment +from rich.style import StyleType + + +class Blank: + """ + Render an empty rectangle. + + Args: + style (StyleType): Style to apply to the box. + width (int, optional): Width of the box in number of cells. Will expand to fit parent if ``None``. + height (int, optional): Height of the box in number of cells. Will expand to fit parent if ``None``. + """ + + def __init__( + self, style: StyleType, width: int | None = None, height: int | None = None + ): + self.style = style + self.width = width + self.height = height + + def __rich_console__(self, console: Console, console_options: ConsoleOptions): + render_width = self.width or console_options.max_width + render_height = ( + self.height or console_options.height or console_options.max_height + ) + style = console.get_style(self.style) + for _ in range(render_height): + yield Segment(" " * render_width + "\n", style) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 4e48fa95b..533167347 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -235,6 +235,13 @@ class Styles: else: return None + def reset(self) -> None: + """ + Reset internal style rules to ``None``, reverting to default styles. + """ + for rule_name in INTERNAL_RULE_NAMES: + setattr(self, rule_name, None) + def extract_rules( self, specificity: Specificity3 ) -> list[tuple[str, Specificity4, Any]]: diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 77e4ab28a..81a4ff14b 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -113,8 +113,14 @@ class Stylesheet: rule_attributes = defaultdict(list) _check_rule = self._check_rule + + node.styles.reset() + + # Get the default node CSS rules for key, default_specificity, value in node._default_rules: rule_attributes[key].append((default_specificity, value)) + + # Apply styles on top of the default node CSS rules for rule in self.rules: for specificity in _check_rule(rule, node): for key, rule_specificity, value in rule.styles.extract_rules( @@ -123,13 +129,6 @@ class Stylesheet: rule_attributes[key].append((rule_specificity, value)) get_first_item = itemgetter(0) - - log("") - log(node) - for key, attributes in rule_attributes.items(): - log(key, key in node.styles.important) - log("\t", attributes) - node_rules = [ (name, max(specificity_rules, key=get_first_item)[1]) for name, specificity_rules in rule_attributes.items() diff --git a/src/textual/widget.py b/src/textual/widget.py index e02118239..51c7e97ab 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -14,11 +14,13 @@ from typing import ( import rich.repr from rich import box from rich.align import Align -from rich.console import Console, RenderableType +from rich.console import Console, RenderableType, ConsoleOptions +from rich.measure import Measurement from rich.panel import Panel from rich.padding import Padding from rich.pretty import Pretty -from rich.style import Style +from rich.segment import Segment +from rich.style import Style, StyleType from rich.styled import Styled from rich.text import Text, TextType @@ -27,6 +29,7 @@ from . import errors from ._animator import BoundAnimator from ._border import Border, BORDER_STYLES from ._callback import invoke +from .blank import Blank from .dom import DOMNode from ._context import active_app from .geometry import Size, Spacing, SpacingDimensions @@ -158,28 +161,36 @@ class Widget(DOMNode): renderable = self.render() styles = self.styles - parent_text_style = self.parent.text_style - text_style = styles.text - renderable_text_style = parent_text_style + text_style - if renderable_text_style: - renderable = Styled(renderable, renderable_text_style) - if styles.has_padding: - renderable = Padding( - renderable, styles.padding, style=renderable_text_style - ) + if styles.visibility == "hidden": + renderable = Blank(parent_text_style) + else: + text_style = styles.text + renderable_text_style = parent_text_style + text_style + if renderable_text_style: + renderable = Styled(renderable, renderable_text_style) - if styles.has_border: - renderable = Border(renderable, styles.border, style=renderable_text_style) + if styles.has_padding: + renderable = Padding( + renderable, styles.padding, style=renderable_text_style + ) - if styles.has_margin: - renderable = Padding(renderable, styles.margin, style=parent_text_style) + if styles.has_border: + renderable = Border( + renderable, styles.border, style=renderable_text_style + ) - if styles.has_outline: - renderable = Border( - renderable, styles.outline, outline=True, style=renderable_text_style - ) + if styles.has_margin: + renderable = Padding(renderable, styles.margin, style=parent_text_style) + + if styles.has_outline: + renderable = Border( + renderable, + styles.outline, + outline=True, + style=renderable_text_style, + ) return renderable diff --git a/tests/test_styles.py b/tests/test_styles.py new file mode 100644 index 000000000..074ef06ad --- /dev/null +++ b/tests/test_styles.py @@ -0,0 +1,11 @@ +from rich.style import Style + +from textual.css.styles import Styles + + +def test_styles_reset(): + styles = Styles() + styles.text_style = "not bold" + assert styles.text_style == Style(bold=False) + styles.reset() + assert styles.text_style is Style.null()