From dce81254bf6f658ae13e36935c0680be2a6e6c25 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 19 Aug 2022 14:44:25 +0100 Subject: [PATCH 01/23] Add docs for layout property --- docs/examples/styles/layout.py | 52 ++++++++++++++++++++++++++++++++++ docs/styles/layout.md | 44 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 docs/examples/styles/layout.py create mode 100644 docs/styles/layout.md diff --git a/docs/examples/styles/layout.py b/docs/examples/styles/layout.py new file mode 100644 index 000000000..6885a361c --- /dev/null +++ b/docs/examples/styles/layout.py @@ -0,0 +1,52 @@ +from textual import layout +from textual.app import App +from textual.widget import Widget +from textual.widgets import Static + + +class LayoutApp(App): + CSS = """ + #vertical-layout { + layout: vertical; + background: $panel; + height: auto; + } + #horizontal-layout { + layout: horizontal; + background: $panel-darken-1; + height: auto; + } + #center-layout { + layout: center; + background: $panel-darken-2; + height: 7; + } + Screen Static { + margin: 1; + width: 12; + color: $text-primary; + background: $primary; + } + """ + + def compose(self): + yield layout.Container( + Static("Layout"), + Static("Is"), + Static("Vertical"), + id="vertical-layout", + ) + yield layout.Container( + Static("Layout"), + Static("Is"), + Static("Horizontal"), + id="horizontal-layout", + ) + yield layout.Container( + Static("Center"), + id="center-layout", + ) + + +app = LayoutApp() +app.run() diff --git a/docs/styles/layout.md b/docs/styles/layout.md new file mode 100644 index 000000000..3907b304c --- /dev/null +++ b/docs/styles/layout.md @@ -0,0 +1,44 @@ +# Layout + +The `layout` property allows you to define how a widget arranges its children. + +## Syntax + +``` +layout: [vertical|horizontal|center]; +``` + +### Values + +| Value | Description | +|----------------------|-------------------------------------------------------------------------------| +| `vertical` (default) | Child widgets will be arranged along the vertical axis, from top to bottom. | +| `horizontal` | Child widgets will be arranged along the horizontal axis, from left to right. | +| `center` | A single child widget will be placed in the center. | + +## Example + +Note how the `layout` property affects the arrangement of widgets in the example below. + +=== "layout.py" + + ```python + --8<-- "docs/examples/styles/layout.py" + ``` + +=== "Output" + + ```{.textual path="docs/examples/styles/layout.py"} + ``` + +## CSS + +```sass +layout: horizontal; +``` + +## Python + +```python +widget.layout = "horizontal" +``` From 721e58ab0857feec8e7b337c3adcc171171c52fa Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 19 Aug 2022 14:46:05 +0100 Subject: [PATCH 02/23] Consistency --- docs/styles/scrollbar_gutter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/styles/scrollbar_gutter.md b/docs/styles/scrollbar_gutter.md index d7be5f89f..0a28bb7d0 100644 --- a/docs/styles/scrollbar_gutter.md +++ b/docs/styles/scrollbar_gutter.md @@ -1,4 +1,4 @@ -# Scrollbar gutter +# Scrollbar-gutter The `scrollbar-gutter` rule allows authors to reserve space for the vertical scrollbar. From 430321684c0dc56f9711535aff3a798722166fa2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 19 Aug 2022 14:51:45 +0100 Subject: [PATCH 03/23] Add layout.md to docs nav index --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 1c85c5ba0..33d55fa19 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ nav: - "styles/min_width.md" - "styles/max_width.md" - "styles/height.md" + - "styles/layout.md" - "styles/margin.md" - "styles/offset.md" - "styles/outline.md" From a26cc3939d5aefbb52dc2640f7f47cc84c70d9fc Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 19 Aug 2022 14:52:22 +0100 Subject: [PATCH 04/23] Simplify layout docs wording --- docs/styles/layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/styles/layout.md b/docs/styles/layout.md index 3907b304c..e4c4abb0f 100644 --- a/docs/styles/layout.md +++ b/docs/styles/layout.md @@ -1,6 +1,6 @@ # Layout -The `layout` property allows you to define how a widget arranges its children. +The `layout` property defines how a widget arranges its children. ## Syntax From 4cb57bb48e64b6c359a8dd522e69bf0091ab255a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 19 Aug 2022 15:25:27 +0100 Subject: [PATCH 05/23] Split layout into separate Python and CSS files for docs example --- docs/examples/styles/layout.css | 24 ++++++++++++++++++++++++ docs/examples/styles/layout.py | 27 +-------------------------- docs/styles/layout.md | 6 ++++++ 3 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 docs/examples/styles/layout.css diff --git a/docs/examples/styles/layout.css b/docs/examples/styles/layout.css new file mode 100644 index 000000000..1b8cacd13 --- /dev/null +++ b/docs/examples/styles/layout.css @@ -0,0 +1,24 @@ +#vertical-layout { + layout: vertical; + background: darkmagenta; + height: auto; +} + +#horizontal-layout { + layout: horizontal; + background: darkcyan; + height: auto; +} + +#center-layout { + layout: center; + background: darkslateblue; + height: 7; +} + +Static { + margin: 1; + width: 12; + color: black; + background: yellowgreen; +} diff --git a/docs/examples/styles/layout.py b/docs/examples/styles/layout.py index 6885a361c..be91681f6 100644 --- a/docs/examples/styles/layout.py +++ b/docs/examples/styles/layout.py @@ -5,30 +5,6 @@ from textual.widgets import Static class LayoutApp(App): - CSS = """ - #vertical-layout { - layout: vertical; - background: $panel; - height: auto; - } - #horizontal-layout { - layout: horizontal; - background: $panel-darken-1; - height: auto; - } - #center-layout { - layout: center; - background: $panel-darken-2; - height: 7; - } - Screen Static { - margin: 1; - width: 12; - color: $text-primary; - background: $primary; - } - """ - def compose(self): yield layout.Container( Static("Layout"), @@ -48,5 +24,4 @@ class LayoutApp(App): ) -app = LayoutApp() -app.run() +app = LayoutApp(css_path="layout.css") diff --git a/docs/styles/layout.md b/docs/styles/layout.md index e4c4abb0f..65fdf7998 100644 --- a/docs/styles/layout.md +++ b/docs/styles/layout.md @@ -26,6 +26,12 @@ Note how the `layout` property affects the arrangement of widgets in the example --8<-- "docs/examples/styles/layout.py" ``` +=== "layout.css" + + ```sass + --8<-- "docs/examples/styles/layout.css" + ``` + === "Output" ```{.textual path="docs/examples/styles/layout.py"} From 0db5fe47e0735e9d66d36f3f6187fa3860a5c61e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 25 Aug 2022 10:53:44 +0100 Subject: [PATCH 06/23] Text justify initial work, help text --- sandbox/darren/text_justify.py | 43 ++++++++++++++++++++++++++++++ sandbox/darren/text_justify.scss | 0 src/textual/css/_help_text.py | 34 +++++++++++++++++++++++ src/textual/css/_styles_builder.py | 15 +++++++++++ src/textual/css/constants.py | 1 + src/textual/css/styles.py | 7 ++++- src/textual/css/types.py | 1 + src/textual/widget.py | 3 ++- 8 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 sandbox/darren/text_justify.py create mode 100644 sandbox/darren/text_justify.scss diff --git a/sandbox/darren/text_justify.py b/sandbox/darren/text_justify.py new file mode 100644 index 000000000..aa3419631 --- /dev/null +++ b/sandbox/darren/text_justify.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from rich.text import Text + +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = """I must not fear. +Fear is the mind-killer. +Fear is the little-death that brings total obliteration. +I will face my fear. +I will permit it to pass over me and through me. +And when it has gone past, I will turn the inner eye to see its path. +Where the fear has gone there will be nothing. Only I will remain.""" + + +class TextJustify(App): + def compose(self) -> ComposeResult: + left = Static(Text(TEXT)) + left.styles.text_justify = "left" + yield left + + right = Static(TEXT) + right.styles.text_justify = "right" + yield right + + center = Static(TEXT) + center.styles.text_justify = "center" + yield center + + full = Static(TEXT) + full.styles.text_justify = "full" + yield full + + +app = TextJustify(css_path="text_justify.scss", watch_css=True) + +if __name__ == "__main__": + from rich.console import Console + + console = Console() + text = Text(TEXT) + console.print(TEXT, justify="full") diff --git a/sandbox/darren/text_justify.scss b/sandbox/darren/text_justify.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/textual/css/_help_text.py b/src/textual/css/_help_text.py index d24835e93..e607741cb 100644 --- a/src/textual/css/_help_text.py +++ b/src/textual/css/_help_text.py @@ -13,6 +13,7 @@ from textual.css.constants import ( VALID_ALIGN_HORIZONTAL, VALID_ALIGN_VERTICAL, VALID_STYLE_FLAGS, + VALID_JUSTIFY, ) if sys.version_info >= (3, 8): @@ -648,6 +649,39 @@ def align_help_text() -> HelpText: ) +def text_justify_help_text(context: str) -> HelpText: + """Help text to show when the user supplies an invalid value for the text-justify property + + Returns: + HelpText: Renderable for displaying the help text for this property. + """ + return HelpText( + summary="Invalid value for the [i]text-justify[/] property.", + bullets=[ + *ContextSpecificBullets( + css=[ + Bullet( + f"The [i]text-justify[/] property must be one of {friendly_list(VALID_JUSTIFY)}", + examples=[ + Example("text-justify: center;"), + Example("text-justify: right;"), + ], + ) + ], + inline=[ + Bullet( + f"The [i]text_justify[/] property must be one of {friendly_list(VALID_JUSTIFY)}", + examples=[ + Example("widget.styles.text_justify = 'center'"), + Example("widget.styles.text_justify = 'right'"), + ], + ) + ], + ).get_by_context(context) + ], + ) + + def offset_single_axis_help_text(property_name: str) -> HelpText: """Help text to show when the user supplies an invalid value for an offset-* property. diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 4b1184127..806a84f48 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -24,6 +24,7 @@ from ._help_text import ( property_invalid_value_help_text, scrollbar_size_property_help_text, scrollbar_size_single_axis_help_text, + text_justify_help_text, ) from .constants import ( VALID_ALIGN_HORIZONTAL, @@ -36,6 +37,7 @@ from .constants import ( VALID_VISIBILITY, VALID_STYLE_FLAGS, VALID_SCROLLBAR_GUTTER, + VALID_JUSTIFY, ) from .errors import DeclarationError, StyleValueError from .model import Declaration @@ -618,6 +620,19 @@ class StylesBuilder: style_definition = " ".join(token.value for token in tokens) self.styles.text_style = style_definition + def process_text_justify(self, name: str, tokens: list[Token]) -> None: + if not tokens: + return + + if len(tokens) > 1 or tokens[0].value not in VALID_JUSTIFY: + self.error( + name, + tokens[0], + text_justify_help_text("css"), + ) + + self.styles._rules["text_justify"] = tokens[0].value + def process_dock(self, name: str, tokens: list[Token]) -> None: if not tokens: return diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index cf096d98a..0f9b25e54 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -38,6 +38,7 @@ VALID_BOX_SIZING: Final = {"border-box", "content-box"} VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"} VALID_ALIGN_HORIZONTAL: Final = {"left", "center", "right"} VALID_ALIGN_VERTICAL: Final = {"top", "middle", "bottom"} +VALID_JUSTIFY: Final = {"left", "center", "right", "full"} VALID_SCROLLBAR_GUTTER: Final = {"auto", "stable"} VALID_STYLE_FLAGS: Final = { "none", diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index dd0b53e6f..7b49c45ed 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -41,6 +41,7 @@ from .constants import ( VALID_OVERFLOW, VALID_SCROLLBAR_GUTTER, VALID_VISIBILITY, + VALID_JUSTIFY, ) from .scalar import Scalar, ScalarOffset, Unit from .scalar_animation import ScalarAnimation @@ -56,6 +57,7 @@ from .types import ( Specificity3, Specificity6, Visibility, + TextJustify, ) if sys.version_info >= (3, 8): @@ -142,6 +144,8 @@ class RulesMap(TypedDict, total=False): content_align_horizontal: AlignHorizontal content_align_vertical: AlignVertical + text_justify: TextJustify + RULE_NAMES = list(RulesMap.__annotations__.keys()) RULE_NAMES_SET = frozenset(RULE_NAMES) @@ -249,6 +253,8 @@ class StylesBase(ABC): content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align = AlignProperty() + text_justify = StringEnumProperty(VALID_JUSTIFY, "left") + def __eq__(self, styles: object) -> bool: """Check that Styles contains the same rules.""" if not isinstance(styles, StylesBase): @@ -458,7 +464,6 @@ class StylesBase(ABC): @rich.repr.auto @dataclass class Styles(StylesBase): - node: DOMNode | None = None _rules: RulesMap = field(default_factory=dict) diff --git a/src/textual/css/types.py b/src/textual/css/types.py index d969397f0..7b06e7561 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -39,6 +39,7 @@ ScrollbarGutter = Literal["auto", "stable"] BoxSizing = Literal["border-box", "content-box"] Overflow = Literal["scroll", "hidden", "auto"] EdgeStyle = Tuple[EdgeType, Color] +TextJustify = Literal["left", "center", "right", "full"] Specificity3 = Tuple[int, int, int] Specificity4 = Tuple[int, int, int, int] diff --git a/src/textual/widget.py b/src/textual/widget.py index 92ef7018b..a8f582260 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -982,11 +982,12 @@ class Widget(DOMNode): """ if isinstance(renderable, str): - renderable = Text.from_markup(renderable) + renderable = Text.from_markup(renderable, justify=self.styles.text_justify) rich_style = self.rich_style if isinstance(renderable, Text): renderable.stylize(rich_style) + renderable.justify = self.styles.text_justify else: renderable = Styled(renderable, rich_style) From 27c07dc7f031f9caad5c88898c419021849a90ee Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 25 Aug 2022 17:13:49 +0100 Subject: [PATCH 07/23] Only apply justify CSS if Text object has no justify set --- src/textual/widget.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/textual/widget.py b/src/textual/widget.py index a8f582260..61bf2cc7e 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -987,7 +987,8 @@ class Widget(DOMNode): rich_style = self.rich_style if isinstance(renderable, Text): renderable.stylize(rich_style) - renderable.justify = self.styles.text_justify + if not renderable.justify: + renderable.justify = self.styles.text_justify else: renderable = Styled(renderable, rich_style) From 9eb5a17df7e6d3552ef2320be03dbe8448a02db2 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 25 Aug 2022 17:16:44 +0100 Subject: [PATCH 08/23] Update sandbox example to use text-justify CSS property --- sandbox/darren/text_justify.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sandbox/darren/text_justify.py b/sandbox/darren/text_justify.py index aa3419631..e76c4ab0e 100644 --- a/sandbox/darren/text_justify.py +++ b/sandbox/darren/text_justify.py @@ -36,8 +36,4 @@ class TextJustify(App): app = TextJustify(css_path="text_justify.scss", watch_css=True) if __name__ == "__main__": - from rich.console import Console - - console = Console() - text = Text(TEXT) - console.print(TEXT, justify="full") + app.run() From d64466c6ceeff9e472edabf8191ae9262033c7b1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 10:10:46 +0100 Subject: [PATCH 09/23] Add a docstring --- src/textual/css/_styles_builder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 806a84f48..c6b276528 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -621,6 +621,7 @@ class StylesBuilder: self.styles.text_style = style_definition def process_text_justify(self, name: str, tokens: list[Token]) -> None: + """Process a text-justify declaration""" if not tokens: return From a45bf58f7e81ed7361d4a4b34f356877da5a2ad1 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 11:34:50 +0100 Subject: [PATCH 10/23] Adding tests for text-justify CSS property --- tests/css/test_parse.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 5b11472cf..70beb4392 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -2,7 +2,6 @@ from __future__ import annotations import pytest - from textual.color import Color from textual.css.errors import UnresolvedVariableError from textual.css.parse import substitute_references @@ -1131,3 +1130,21 @@ class TestParsePadding: stylesheet = Stylesheet() stylesheet.add_source(css) assert stylesheet.rules[0].styles.padding == Spacing(2, 3, -1, 1) + + +class TestParseTextJustify: + @pytest.mark.parametrize("valid_justify", ["left", "center", "full", "right"]) + def test_text_justify(self, valid_justify): + css = f"#foo {{ text-justify: {valid_justify} }}" + stylesheet = Stylesheet() + stylesheet.add_source(css) + assert stylesheet.rules[0].styles.text_justify == valid_justify + + def test_text_justify_invalid(self): + css = "#foo { text-justify: invalid-value }" + stylesheet = Stylesheet() + with pytest.raises(StylesheetParseError): + stylesheet.add_source(css) + stylesheet.parse() + rules = stylesheet._parse_rules(css, "foo") + assert rules[0].errors From 57620fee8c1101822552ac7dba7d14abf48bc1ec Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 12:28:08 +0100 Subject: [PATCH 11/23] Test that text-justify property with empty value uses default --- tests/css/test_parse.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 70beb4392..0d340e1b7 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -1141,10 +1141,16 @@ class TestParseTextJustify: assert stylesheet.rules[0].styles.text_justify == valid_justify def test_text_justify_invalid(self): - css = "#foo { text-justify: invalid-value }" + css = "#foo { text-justify: invalid-value; }" stylesheet = Stylesheet() with pytest.raises(StylesheetParseError): stylesheet.add_source(css) stylesheet.parse() rules = stylesheet._parse_rules(css, "foo") assert rules[0].errors + + def test_text_justify_empty_uses_default(self): + css = "#foo { text-justify: ; }" + stylesheet = Stylesheet() + stylesheet.add_source(css) + assert stylesheet.rules[0].styles.text_justify == "left" From acd56c6f7f470b337420d510822fb33f27284841 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 12:38:53 +0100 Subject: [PATCH 12/23] Initial docs for text-justify CSS property --- docs/styles/text_justify.md | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 docs/styles/text_justify.md diff --git a/docs/styles/text_justify.md b/docs/styles/text_justify.md new file mode 100644 index 000000000..b0cfcdf25 --- /dev/null +++ b/docs/styles/text_justify.md @@ -0,0 +1,40 @@ +# Text-justify + +The `text-justify` rule justifies text within a widget. + +## Syntax + +``` +text-justify: [left|center|right|full]; +``` + +### Values + +| Value | Description | +|----------|-------------------------------------| +| `left` | Left justifies text in the widget | +| `center` | Center justifies text in the widget | +| `right` | Right justifies text in the widget | +| `full` | Fully justifies text in the widget | + +## Example + +In this example, we can see, from top to bottom, + `left`, `center`, `right`, and `full` justified text respectively. + +=== "text_justify.py" + + ```python + --8<-- "docs/examples/styles/text_justify.py" + ``` + +=== "text_justify.css" + + ```css + --8<-- "docs/examples/styles/text_justify.css" + ``` + +=== "Output" + + ```{.textual path="docs/examples/styles/text_justify.py"} + ``` From 7bfdd11527713cac51dbb1904ee3685c1f89eabf Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 12:41:02 +0100 Subject: [PATCH 13/23] Expand auto-refresh margin in test --- tests/test_auto_refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_auto_refresh.py b/tests/test_auto_refresh.py index 5d211224c..4314f0729 100644 --- a/tests/test_auto_refresh.py +++ b/tests/test_auto_refresh.py @@ -25,4 +25,4 @@ def test_auto_refresh(): elapsed = app.run(quit_after=1, headless=True) assert elapsed is not None # CI can run slower, so we need to give this a bit of margin - assert elapsed >= 0.3 and elapsed < 0.6 + assert 0.3 <= elapsed < 0.8 From 3b155216f0bb167de47bc0b4e5cd6ee0224c60ec Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 14:51:27 +0100 Subject: [PATCH 14/23] Docs for text-justify --- docs/examples/styles/text_justify.css | 24 +++++++++++++++++++++++ docs/examples/styles/text_justify.py | 28 +++++++++++++++++++++++++++ docs/styles/scrollbar.md | 2 +- docs/styles/text_justify.md | 21 ++++++++++++++++++-- mkdocs.yml | 11 ++++++----- sandbox/darren/text_justify.py | 28 +++++++++++---------------- sandbox/darren/text_justify.scss | 24 +++++++++++++++++++++++ src/textual/geometry.py | 2 +- src/textual/message_pump.py | 2 +- src/textual/widget.py | 3 --- 10 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 docs/examples/styles/text_justify.css create mode 100644 docs/examples/styles/text_justify.py diff --git a/docs/examples/styles/text_justify.css b/docs/examples/styles/text_justify.css new file mode 100644 index 000000000..589f7bfd6 --- /dev/null +++ b/docs/examples/styles/text_justify.css @@ -0,0 +1,24 @@ +#one { + text-justify: left; + background: lightblue; + +} + +#two { + text-justify: center; + background: indianred; +} + +#three { + text-justify: right; + background: palegreen; +} + +#four { + text-justify: full; + background: palevioletred; +} + +Static { + padding: 1; +} diff --git a/docs/examples/styles/text_justify.py b/docs/examples/styles/text_justify.py new file mode 100644 index 000000000..cc86529f8 --- /dev/null +++ b/docs/examples/styles/text_justify.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from textual.app import App, ComposeResult +from textual.widgets import Static + +TEXT = ( + "I must not fear. Fear is the mind-killer. Fear is the little-death that " + "brings total obliteration. I will face my fear. I will permit it to pass over " + "me and through me." +) + + +class TextJustify(App): + def compose(self) -> ComposeResult: + left = Static("[b]Left justified[/]\n" + TEXT, id="one") + yield left + + right = Static("[b]Center justified[/]\n" + TEXT, id="two") + yield right + + center = Static("[b]Right justified[/]\n" + TEXT, id="three") + yield center + + full = Static("[b]Full justified[/]\n" + TEXT, id="four") + yield full + + +app = TextJustify(css_path="text_justify.css") diff --git a/docs/styles/scrollbar.md b/docs/styles/scrollbar.md index 57b4346d7..85003779d 100644 --- a/docs/styles/scrollbar.md +++ b/docs/styles/scrollbar.md @@ -12,7 +12,7 @@ There are a number of rules to set the colors used in Textual scrollbars. You wo | `scrollbar-background-active` | Scrollbar background when the thumb is being dragged | | `scrollbar-corner-color` | The gap between the horizontal and vertical scrollbars | -## Example: +## Syntax ``` scrollbar-color: ; diff --git a/docs/styles/text_justify.md b/docs/styles/text_justify.md index b0cfcdf25..ab829a75f 100644 --- a/docs/styles/text_justify.md +++ b/docs/styles/text_justify.md @@ -2,6 +2,8 @@ The `text-justify` rule justifies text within a widget. +This property is not the same as the `text-justify` property in browser CSS. + ## Syntax ``` @@ -19,8 +21,7 @@ text-justify: [left|center|right|full]; ## Example -In this example, we can see, from top to bottom, - `left`, `center`, `right`, and `full` justified text respectively. +This example shows, from top to bottom: `left`, `center`, `right`, and `full` justified text. === "text_justify.py" @@ -38,3 +39,19 @@ In this example, we can see, from top to bottom, ```{.textual path="docs/examples/styles/text_justify.py"} ``` + +## CSS + +```sass +/* Set text in all Widgets to be right justified */ +Widget { + text-justify: right; +} +``` + +## Python + +```python +# Set text in the widget to be right justified +widget.styles.text_justify = "right" +``` diff --git a/mkdocs.yml b/mkdocs.yml index 5d17dd154..5735b0ab5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,10 +6,10 @@ nav: - "getting_started.md" - "introduction.md" - Guide: - - "guide/devtools.md" + - "guide/devtools.md" - "guide/CSS.md" - "guide/events.md" - + - "actions.md" - Events: - "events/blur.md" @@ -35,7 +35,7 @@ nav: - "events/resize.md" - "events/screen_resume.md" - "events/screen_suspend.md" - - "events/show.md" + - "events/show.md" - Styles: - "styles/background.md" - "styles/border.md" @@ -53,9 +53,10 @@ nav: - "styles/outline.md" - "styles/overflow.md" - "styles/padding.md" + - "styles/scrollbar.md" - "styles/scrollbar_gutter.md" - "styles/scrollbar_size.md" - - "styles/scrollbar.md" + - "styles/text_justify.md" - "styles/text_style.md" - "styles/tint.md" - "styles/visibility.md" @@ -81,7 +82,7 @@ markdown_extensions: - admonition - def_list - meta - + - toc: permalink: true baselevel: 1 diff --git a/sandbox/darren/text_justify.py b/sandbox/darren/text_justify.py index e76c4ab0e..74f8afdfe 100644 --- a/sandbox/darren/text_justify.py +++ b/sandbox/darren/text_justify.py @@ -1,35 +1,29 @@ from __future__ import annotations -from rich.text import Text - from textual.app import App, ComposeResult from textual.widgets import Static -TEXT = """I must not fear. -Fear is the mind-killer. -Fear is the little-death that brings total obliteration. -I will face my fear. -I will permit it to pass over me and through me. -And when it has gone past, I will turn the inner eye to see its path. -Where the fear has gone there will be nothing. Only I will remain.""" +TEXT = ( + "I must not fear. Fear is the mind-killer. Fear is the little-death that " + "brings total obliteration. I will face my fear. I will permit it to pass over " + "me and through me. And when it has gone past, I will turn the inner eye to " + "see its path. Where the fear has gone there will be nothing. Only I will " + "remain. " +) class TextJustify(App): def compose(self) -> ComposeResult: - left = Static(Text(TEXT)) - left.styles.text_justify = "left" + left = Static("[b]Left justified[/]\n" + TEXT, id="one") yield left - right = Static(TEXT) - right.styles.text_justify = "right" + right = Static("[b]Center justified[/]\n" + TEXT, id="two") yield right - center = Static(TEXT) - center.styles.text_justify = "center" + center = Static("[b]Right justified[/]\n" + TEXT, id="three") yield center - full = Static(TEXT) - full.styles.text_justify = "full" + full = Static("[b]Full justified[/]\n" + TEXT, id="four") yield full diff --git a/sandbox/darren/text_justify.scss b/sandbox/darren/text_justify.scss index e69de29bb..589f7bfd6 100644 --- a/sandbox/darren/text_justify.scss +++ b/sandbox/darren/text_justify.scss @@ -0,0 +1,24 @@ +#one { + text-justify: left; + background: lightblue; + +} + +#two { + text-justify: center; + background: indianred; +} + +#three { + text-justify: right; + background: palegreen; +} + +#four { + text-justify: full; + background: palevioletred; +} + +Static { + padding: 1; +} diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 537932cfc..3dfd634c6 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -619,7 +619,7 @@ class Region(NamedTuple): """Move the offset of the Region. Args: - translate (tuple[int, int]): Offset to add to region. + offset (tuple[int, int]): Offset to add to region. Returns: Region: A new region shifted by (x, y) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 7f8d0bb91..e7eeb4c18 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -242,7 +242,7 @@ class MessagePump(metaclass=MessagePumpMeta): name: str | None = None, repeat: int = 0, pause: bool = False, - ): + ) -> Timer: """Call a function at periodic intervals. Args: diff --git a/src/textual/widget.py b/src/textual/widget.py index 806f89cf1..d1c31761f 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1382,9 +1382,6 @@ class Widget(DOMNode): def render(self) -> RenderableType: """Get renderable for widget. - Args: - style (Styles): The Styles object for this Widget. - Returns: RenderableType: Any renderable """ From ad803a7753d7ad9748bef2ae72700e36740dec59 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 15:33:42 +0100 Subject: [PATCH 15/23] Text justify has become text align --- .../{text_justify.css => text_align.css} | 8 +-- .../styles/{text_justify.py => text_align.py} | 12 ++-- docs/styles/text_align.md | 57 +++++++++++++++++++ docs/styles/text_justify.md | 57 ------------------- mkdocs.yml | 2 +- .../darren/{text_justify.py => text_align.py} | 12 ++-- .../{text_justify.scss => text_align.scss} | 8 +-- src/textual/css/_help_text.py | 20 +++---- src/textual/css/_styles_builder.py | 14 ++--- src/textual/css/constants.py | 2 +- src/textual/css/styles.py | 7 +-- src/textual/widget.py | 37 ++++++++---- tests/css/test_parse.py | 16 +++--- 13 files changed, 134 insertions(+), 118 deletions(-) rename docs/examples/styles/{text_justify.css => text_align.css} (65%) rename docs/examples/styles/{text_justify.py => text_align.py} (56%) create mode 100644 docs/styles/text_align.md delete mode 100644 docs/styles/text_justify.md rename sandbox/darren/{text_justify.py => text_align.py} (64%) rename sandbox/darren/{text_justify.scss => text_align.scss} (65%) diff --git a/docs/examples/styles/text_justify.css b/docs/examples/styles/text_align.css similarity index 65% rename from docs/examples/styles/text_justify.css rename to docs/examples/styles/text_align.css index 589f7bfd6..c594254d6 100644 --- a/docs/examples/styles/text_justify.css +++ b/docs/examples/styles/text_align.css @@ -1,21 +1,21 @@ #one { - text-justify: left; + text-align: left; background: lightblue; } #two { - text-justify: center; + text-align: center; background: indianred; } #three { - text-justify: right; + text-align: right; background: palegreen; } #four { - text-justify: full; + text-align: justify; background: palevioletred; } diff --git a/docs/examples/styles/text_justify.py b/docs/examples/styles/text_align.py similarity index 56% rename from docs/examples/styles/text_justify.py rename to docs/examples/styles/text_align.py index cc86529f8..27e2892fa 100644 --- a/docs/examples/styles/text_justify.py +++ b/docs/examples/styles/text_align.py @@ -10,19 +10,19 @@ TEXT = ( ) -class TextJustify(App): +class TextAlign(App): def compose(self) -> ComposeResult: - left = Static("[b]Left justified[/]\n" + TEXT, id="one") + left = Static("[b]Left aligned[/]\n" + TEXT, id="one") yield left - right = Static("[b]Center justified[/]\n" + TEXT, id="two") + right = Static("[b]Center aligned[/]\n" + TEXT, id="two") yield right - center = Static("[b]Right justified[/]\n" + TEXT, id="three") + center = Static("[b]Right aligned[/]\n" + TEXT, id="three") yield center - full = Static("[b]Full justified[/]\n" + TEXT, id="four") + full = Static("[b]Justified[/]\n" + TEXT, id="four") yield full -app = TextJustify(css_path="text_justify.css") +app = TextAlign(css_path="text_align.css") diff --git a/docs/styles/text_align.md b/docs/styles/text_align.md new file mode 100644 index 000000000..1fc9b515c --- /dev/null +++ b/docs/styles/text_align.md @@ -0,0 +1,57 @@ +# Text-align + +The `text-align` rule aligns text within a widget. + +## Syntax + +``` +text-align: [left|start|center|right|end|justify]; +``` + +### Values + +| Value | Description | +|-----------|----------------------------------| +| `left` | Left aligns text in the widget | +| `start` | Left aligns text in the widget | +| `center` | Center aligns text in the widget | +| `right` | Right aligns text in the widget | +| `end` | Right aligns text in the widget | +| `justify` | Justifies text in the widget | + +## Example + +This example shows, from top to bottom: `left`, `center`, `right`, and `justify` text alignments. + +=== "text_align.py" + + ```python + --8<-- "docs/examples/styles/text_align.py" + ``` + +=== "text_align.css" + + ```css + --8<-- "docs/examples/styles/text_align.css" + ``` + +=== "Output" + + ```{.textual path="docs/examples/styles/text_align.py"} + ``` + +## CSS + +```sass +/* Set text in all Widgets to be right aligned */ +Widget { + text-align: right; +} +``` + +## Python + +```python +# Set text in the widget to be right aligned +widget.styles.text_align = "right" +``` diff --git a/docs/styles/text_justify.md b/docs/styles/text_justify.md deleted file mode 100644 index ab829a75f..000000000 --- a/docs/styles/text_justify.md +++ /dev/null @@ -1,57 +0,0 @@ -# Text-justify - -The `text-justify` rule justifies text within a widget. - -This property is not the same as the `text-justify` property in browser CSS. - -## Syntax - -``` -text-justify: [left|center|right|full]; -``` - -### Values - -| Value | Description | -|----------|-------------------------------------| -| `left` | Left justifies text in the widget | -| `center` | Center justifies text in the widget | -| `right` | Right justifies text in the widget | -| `full` | Fully justifies text in the widget | - -## Example - -This example shows, from top to bottom: `left`, `center`, `right`, and `full` justified text. - -=== "text_justify.py" - - ```python - --8<-- "docs/examples/styles/text_justify.py" - ``` - -=== "text_justify.css" - - ```css - --8<-- "docs/examples/styles/text_justify.css" - ``` - -=== "Output" - - ```{.textual path="docs/examples/styles/text_justify.py"} - ``` - -## CSS - -```sass -/* Set text in all Widgets to be right justified */ -Widget { - text-justify: right; -} -``` - -## Python - -```python -# Set text in the widget to be right justified -widget.styles.text_justify = "right" -``` diff --git a/mkdocs.yml b/mkdocs.yml index 5735b0ab5..070d93a3c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -56,7 +56,7 @@ nav: - "styles/scrollbar.md" - "styles/scrollbar_gutter.md" - "styles/scrollbar_size.md" - - "styles/text_justify.md" + - "styles/text_align.md" - "styles/text_style.md" - "styles/tint.md" - "styles/visibility.md" diff --git a/sandbox/darren/text_justify.py b/sandbox/darren/text_align.py similarity index 64% rename from sandbox/darren/text_justify.py rename to sandbox/darren/text_align.py index 74f8afdfe..ccc55696a 100644 --- a/sandbox/darren/text_justify.py +++ b/sandbox/darren/text_align.py @@ -12,22 +12,22 @@ TEXT = ( ) -class TextJustify(App): +class TextAlign(App): def compose(self) -> ComposeResult: - left = Static("[b]Left justified[/]\n" + TEXT, id="one") + left = Static("[b]Left aligned[/]\n" + TEXT, id="one") yield left - right = Static("[b]Center justified[/]\n" + TEXT, id="two") + right = Static("[b]Center aligned[/]\n" + TEXT, id="two") yield right - center = Static("[b]Right justified[/]\n" + TEXT, id="three") + center = Static("[b]Right aligned[/]\n" + TEXT, id="three") yield center - full = Static("[b]Full justified[/]\n" + TEXT, id="four") + full = Static("[b]Fully justified[/]\n" + TEXT, id="four") yield full -app = TextJustify(css_path="text_justify.scss", watch_css=True) +app = TextAlign(css_path="text_align.scss", watch_css=True) if __name__ == "__main__": app.run() diff --git a/sandbox/darren/text_justify.scss b/sandbox/darren/text_align.scss similarity index 65% rename from sandbox/darren/text_justify.scss rename to sandbox/darren/text_align.scss index 589f7bfd6..c594254d6 100644 --- a/sandbox/darren/text_justify.scss +++ b/sandbox/darren/text_align.scss @@ -1,21 +1,21 @@ #one { - text-justify: left; + text-align: left; background: lightblue; } #two { - text-justify: center; + text-align: center; background: indianred; } #three { - text-justify: right; + text-align: right; background: palegreen; } #four { - text-justify: full; + text-align: justify; background: palevioletred; } diff --git a/src/textual/css/_help_text.py b/src/textual/css/_help_text.py index e607741cb..70d36e28e 100644 --- a/src/textual/css/_help_text.py +++ b/src/textual/css/_help_text.py @@ -13,7 +13,7 @@ from textual.css.constants import ( VALID_ALIGN_HORIZONTAL, VALID_ALIGN_VERTICAL, VALID_STYLE_FLAGS, - VALID_JUSTIFY, + VALID_TEXT_ALIGN, ) if sys.version_info >= (3, 8): @@ -649,31 +649,31 @@ def align_help_text() -> HelpText: ) -def text_justify_help_text(context: str) -> HelpText: - """Help text to show when the user supplies an invalid value for the text-justify property +def text_align_help_text(context: str) -> HelpText: + """Help text to show when the user supplies an invalid value for the text-align property Returns: HelpText: Renderable for displaying the help text for this property. """ return HelpText( - summary="Invalid value for the [i]text-justify[/] property.", + summary="Invalid value for the [i]text-align[/] property.", bullets=[ *ContextSpecificBullets( css=[ Bullet( - f"The [i]text-justify[/] property must be one of {friendly_list(VALID_JUSTIFY)}", + f"The [i]text-align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}", examples=[ - Example("text-justify: center;"), - Example("text-justify: right;"), + Example("text-align: center;"), + Example("text-align: right;"), ], ) ], inline=[ Bullet( - f"The [i]text_justify[/] property must be one of {friendly_list(VALID_JUSTIFY)}", + f"The [i]text_align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}", examples=[ - Example("widget.styles.text_justify = 'center'"), - Example("widget.styles.text_justify = 'right'"), + Example("widget.styles.text_align = 'center'"), + Example("widget.styles.text_align = 'right'"), ], ) ], diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index c6b276528..9973e8f36 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -24,7 +24,7 @@ from ._help_text import ( property_invalid_value_help_text, scrollbar_size_property_help_text, scrollbar_size_single_axis_help_text, - text_justify_help_text, + text_align_help_text, ) from .constants import ( VALID_ALIGN_HORIZONTAL, @@ -37,7 +37,7 @@ from .constants import ( VALID_VISIBILITY, VALID_STYLE_FLAGS, VALID_SCROLLBAR_GUTTER, - VALID_JUSTIFY, + VALID_TEXT_ALIGN, ) from .errors import DeclarationError, StyleValueError from .model import Declaration @@ -620,19 +620,19 @@ class StylesBuilder: style_definition = " ".join(token.value for token in tokens) self.styles.text_style = style_definition - def process_text_justify(self, name: str, tokens: list[Token]) -> None: - """Process a text-justify declaration""" + def process_text_align(self, name: str, tokens: list[Token]) -> None: + """Process a text-align declaration""" if not tokens: return - if len(tokens) > 1 or tokens[0].value not in VALID_JUSTIFY: + if len(tokens) > 1 or tokens[0].value not in VALID_TEXT_ALIGN: self.error( name, tokens[0], - text_justify_help_text("css"), + text_align_help_text("css"), ) - self.styles._rules["text_justify"] = tokens[0].value + self.styles._rules["text_align"] = tokens[0].value def process_dock(self, name: str, tokens: list[Token]) -> None: if not tokens: diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py index 0f9b25e54..0025551d7 100644 --- a/src/textual/css/constants.py +++ b/src/textual/css/constants.py @@ -38,7 +38,7 @@ VALID_BOX_SIZING: Final = {"border-box", "content-box"} VALID_OVERFLOW: Final = {"scroll", "hidden", "auto"} VALID_ALIGN_HORIZONTAL: Final = {"left", "center", "right"} VALID_ALIGN_VERTICAL: Final = {"top", "middle", "bottom"} -VALID_JUSTIFY: Final = {"left", "center", "right", "full"} +VALID_TEXT_ALIGN: Final = {"start", "end", "left", "right", "center", "justify"} VALID_SCROLLBAR_GUTTER: Final = {"auto", "stable"} VALID_STYLE_FLAGS: Final = { "none", diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index c9f22ad6c..77a1043b2 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -31,7 +31,6 @@ from ._style_properties import ( SpacingProperty, StringEnumProperty, StyleFlagsProperty, - StyleProperty, TransitionsProperty, ) from .constants import ( @@ -42,7 +41,7 @@ from .constants import ( VALID_OVERFLOW, VALID_SCROLLBAR_GUTTER, VALID_VISIBILITY, - VALID_JUSTIFY, + VALID_TEXT_ALIGN, ) from .scalar import Scalar, ScalarOffset, Unit from .scalar_animation import ScalarAnimation @@ -145,7 +144,7 @@ class RulesMap(TypedDict, total=False): content_align_horizontal: AlignHorizontal content_align_vertical: AlignVertical - text_justify: TextJustify + text_align: TextJustify RULE_NAMES = list(RulesMap.__annotations__.keys()) @@ -254,7 +253,7 @@ class StylesBase(ABC): content_align_vertical = StringEnumProperty(VALID_ALIGN_VERTICAL, "top") content_align = AlignProperty() - text_justify = StringEnumProperty(VALID_JUSTIFY, "left") + text_align = StringEnumProperty(VALID_TEXT_ALIGN, "start") def __eq__(self, styles: object) -> bool: """Check that Styles contains the same rules.""" diff --git a/src/textual/widget.py b/src/textual/widget.py index d1c31761f..f58202a0b 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1,14 +1,11 @@ from __future__ import annotations from asyncio import Lock -from itertools import islice from fractions import Fraction +from itertools import islice from operator import attrgetter from typing import ( TYPE_CHECKING, - Any, - Awaitable, - Callable, ClassVar, Collection, Iterable, @@ -16,8 +13,7 @@ from typing import ( ) import rich.repr - -from rich.console import Console, RenderableType +from rich.console import Console, RenderableType, JustifyMethod from rich.measure import Measurement from rich.segment import Segment from rich.style import Style @@ -33,13 +29,13 @@ from ._segment_tools import align_lines from ._styles_cache import StylesCache from ._types import Lines from .box_model import BoxModel, get_box_model +from .css.constants import VALID_TEXT_ALIGN from .dom import DOMNode +from .dom import NoScreen from .geometry import Offset, Region, Size, Spacing, clamp from .layouts.vertical import VerticalLayout from .message import Message from .reactive import Reactive -from .dom import NoScreen - if TYPE_CHECKING: from .app import App, ComposeResult @@ -1215,13 +1211,15 @@ class Widget(DOMNode): """ if isinstance(renderable, str): - renderable = Text.from_markup(renderable, justify=self.styles.text_justify) + justify = _get_rich_justify(self.styles.text_align) + renderable = Text.from_markup(renderable, justify=justify) rich_style = self.rich_style if isinstance(renderable, Text): renderable.stylize(rich_style) if not renderable.justify: - renderable.justify = self.styles.text_justify + justify = _get_rich_justify(self.styles.text_align) + renderable.justify = justify else: renderable = Styled(renderable, rich_style) @@ -1579,3 +1577,22 @@ class Widget(DOMNode): self.scroll_page_up() return True return False + + +def _get_rich_justify(css_align: str) -> JustifyMethod: + """Given the value for CSS text-align, return the analogous argument + for the Rich text `justify` parameter. + + Args: + css_align: The value of text-align CSS property. + + Returns: + JustifyMethod: The Rich JustifyMethod that corresponds to the text-align + value + """ + assert css_align in VALID_TEXT_ALIGN + return { + "start": "left", + "end": "right", + "justify": "full", + }.get(css_align, css_align) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 0d340e1b7..026de9d8a 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -1134,14 +1134,14 @@ class TestParsePadding: class TestParseTextJustify: @pytest.mark.parametrize("valid_justify", ["left", "center", "full", "right"]) - def test_text_justify(self, valid_justify): - css = f"#foo {{ text-justify: {valid_justify} }}" + def test_text_align(self, valid_justify): + css = f"#foo {{ text-align: {valid_justify} }}" stylesheet = Stylesheet() stylesheet.add_source(css) - assert stylesheet.rules[0].styles.text_justify == valid_justify + assert stylesheet.rules[0].styles.text_align == valid_justify - def test_text_justify_invalid(self): - css = "#foo { text-justify: invalid-value; }" + def test_text_align_invalid(self): + css = "#foo { text-align: invalid-value; }" stylesheet = Stylesheet() with pytest.raises(StylesheetParseError): stylesheet.add_source(css) @@ -1149,8 +1149,8 @@ class TestParseTextJustify: rules = stylesheet._parse_rules(css, "foo") assert rules[0].errors - def test_text_justify_empty_uses_default(self): - css = "#foo { text-justify: ; }" + def test_text_align_empty_uses_default(self): + css = "#foo { text-align: ; }" stylesheet = Stylesheet() stylesheet.add_source(css) - assert stylesheet.rules[0].styles.text_justify == "left" + assert stylesheet.rules[0].styles.text_align == "left" From 6ff60474174381a87d183bcab8ffd130d653361a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 15:38:09 +0100 Subject: [PATCH 16/23] text-align testing --- tests/css/test_parse.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/css/test_parse.py b/tests/css/test_parse.py index 026de9d8a..d22c8fe46 100644 --- a/tests/css/test_parse.py +++ b/tests/css/test_parse.py @@ -1132,13 +1132,13 @@ class TestParsePadding: assert stylesheet.rules[0].styles.padding == Spacing(2, 3, -1, 1) -class TestParseTextJustify: - @pytest.mark.parametrize("valid_justify", ["left", "center", "full", "right"]) - def test_text_align(self, valid_justify): - css = f"#foo {{ text-align: {valid_justify} }}" +class TestParseTextAlign: + @pytest.mark.parametrize("valid_align", ["left", "start", "center", "right", "end", "justify"]) + def test_text_align(self, valid_align): + css = f"#foo {{ text-align: {valid_align} }}" stylesheet = Stylesheet() stylesheet.add_source(css) - assert stylesheet.rules[0].styles.text_align == valid_justify + assert stylesheet.rules[0].styles.text_align == valid_align def test_text_align_invalid(self): css = "#foo { text-align: invalid-value; }" @@ -1153,4 +1153,4 @@ class TestParseTextJustify: css = "#foo { text-align: ; }" stylesheet = Stylesheet() stylesheet.add_source(css) - assert stylesheet.rules[0].styles.text_align == "left" + assert stylesheet.rules[0].styles.text_align == "start" From 56d2516c645f7db5437aea6e83a6e517cafb60cc Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 15:51:32 +0100 Subject: [PATCH 17/23] TextJustify type to TextAlign --- src/textual/css/styles.py | 4 ++-- src/textual/css/types.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/css/styles.py b/src/textual/css/styles.py index 77a1043b2..e8a25c514 100644 --- a/src/textual/css/styles.py +++ b/src/textual/css/styles.py @@ -57,7 +57,7 @@ from .types import ( Specificity3, Specificity6, Visibility, - TextJustify, + TextAlign, ) if sys.version_info >= (3, 8): @@ -144,7 +144,7 @@ class RulesMap(TypedDict, total=False): content_align_horizontal: AlignHorizontal content_align_vertical: AlignVertical - text_align: TextJustify + text_align: TextAlign RULE_NAMES = list(RulesMap.__annotations__.keys()) diff --git a/src/textual/css/types.py b/src/textual/css/types.py index 7b06e7561..6fb0929a4 100644 --- a/src/textual/css/types.py +++ b/src/textual/css/types.py @@ -39,7 +39,7 @@ ScrollbarGutter = Literal["auto", "stable"] BoxSizing = Literal["border-box", "content-box"] Overflow = Literal["scroll", "hidden", "auto"] EdgeStyle = Tuple[EdgeType, Color] -TextJustify = Literal["left", "center", "right", "full"] +TextAlign = Literal["left", "start", "center", "right", "end", "justify"] Specificity3 = Tuple[int, int, int] Specificity4 = Tuple[int, int, int, int] From 39665fb11065e7c53661e6cb9c4abe4447431765 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 16:19:05 +0100 Subject: [PATCH 18/23] Help text for text align --- src/textual/css/_help_text.py | 28 +++++++--------------------- src/textual/css/_styles_builder.py | 2 +- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/textual/css/_help_text.py b/src/textual/css/_help_text.py index 70d36e28e..2dc87dd1b 100644 --- a/src/textual/css/_help_text.py +++ b/src/textual/css/_help_text.py @@ -9,7 +9,6 @@ from textual.css._help_renderables import Example, Bullet, HelpText from textual.css.constants import ( VALID_BORDER, VALID_LAYOUT, - VALID_EDGE, VALID_ALIGN_HORIZONTAL, VALID_ALIGN_VERTICAL, VALID_STYLE_FLAGS, @@ -649,7 +648,7 @@ def align_help_text() -> HelpText: ) -def text_align_help_text(context: str) -> HelpText: +def text_align_help_text() -> HelpText: """Help text to show when the user supplies an invalid value for the text-align property Returns: @@ -658,26 +657,13 @@ def text_align_help_text(context: str) -> HelpText: return HelpText( summary="Invalid value for the [i]text-align[/] property.", bullets=[ - *ContextSpecificBullets( - css=[ - Bullet( - f"The [i]text-align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}", - examples=[ - Example("text-align: center;"), - Example("text-align: right;"), - ], - ) + Bullet( + f"The [i]text-align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}", + examples=[ + Example("text-align: center;"), + Example("text-align: right;"), ], - inline=[ - Bullet( - f"The [i]text_align[/] property must be one of {friendly_list(VALID_TEXT_ALIGN)}", - examples=[ - Example("widget.styles.text_align = 'center'"), - Example("widget.styles.text_align = 'right'"), - ], - ) - ], - ).get_by_context(context) + ) ], ) diff --git a/src/textual/css/_styles_builder.py b/src/textual/css/_styles_builder.py index 9973e8f36..f94b966b6 100644 --- a/src/textual/css/_styles_builder.py +++ b/src/textual/css/_styles_builder.py @@ -629,7 +629,7 @@ class StylesBuilder: self.error( name, tokens[0], - text_align_help_text("css"), + text_align_help_text(), ) self.styles._rules["text_align"] = tokens[0].value From 3b33e220b78496a264103a3c4ef3316dcf91e984 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Fri, 26 Aug 2022 16:23:44 +0100 Subject: [PATCH 19/23] Change threshold in auto-refresh test --- tests/test_auto_refresh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_auto_refresh.py b/tests/test_auto_refresh.py index 4314f0729..d5122a7a4 100644 --- a/tests/test_auto_refresh.py +++ b/tests/test_auto_refresh.py @@ -25,4 +25,4 @@ def test_auto_refresh(): elapsed = app.run(quit_after=1, headless=True) assert elapsed is not None # CI can run slower, so we need to give this a bit of margin - assert 0.3 <= elapsed < 0.8 + assert 0.2 <= elapsed < 0.8 From 92f15abc42b968107714265af6b82cac6903e75a Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 30 Aug 2022 11:49:11 +0100 Subject: [PATCH 20/23] Values sections added to CSS properties docs --- docs/styles/background.md | 2 +- docs/styles/border.md | 32 +++++++++++++----------- docs/styles/box_sizing.md | 15 +++++++---- docs/styles/display.md | 9 ++++++- docs/styles/margin.md | 4 +-- docs/styles/outline.md | 47 +++++++++++++++++++---------------- docs/styles/overflow.md | 20 +++++++++------ docs/styles/scrollbar.md | 5 ++-- docs/styles/text_style.md | 25 +++++++++++-------- docs/styles/visibility.md | 11 ++++++-- sandbox/darren/just_a_box.css | 25 ++++++------------- sandbox/darren/just_a_box.py | 21 ++++++++-------- src/textual/css/tokenizer.py | 4 --- 13 files changed, 121 insertions(+), 99 deletions(-) diff --git a/docs/styles/background.md b/docs/styles/background.md index 38a1c59c8..1cf5283c7 100644 --- a/docs/styles/background.md +++ b/docs/styles/background.md @@ -5,7 +5,7 @@ The `background` rule sets the background color of the widget. ## Syntax ``` -background: COLOR [PERCENTAGE] +background: []; ``` ## Example diff --git a/docs/styles/border.md b/docs/styles/border.md index c060bfb25..757ce988b 100644 --- a/docs/styles/border.md +++ b/docs/styles/border.md @@ -2,8 +2,22 @@ The `border` rule enables the drawing of a box around a widget. A border is set with a border value (see below) followed by a color. -| Border value | Explanation | -| ------------ |---------------------------------------------------------| +Borders may also be set individually for the four edges of a widget with the `border-top`, `border-right`, `border-bottom` and `border-left` rules. + +## Syntax + +``` +border: [] []; +border-top: [] []; +border-right: [] []; +border-bottom: [] []; +border-left: [] []; +``` + +### Values + +| Border value | Description | +|--------------|---------------------------------------------------------| | `"ascii"` | A border with plus, hyphen, and vertical bar | | `"blank"` | A blank border (reserves space for a border) | | `"dashed"` | Dashed line border | @@ -20,19 +34,7 @@ The `border` rule enables the drawing of a box around a widget. A border is set | `"vkey"` | Vertical key-line border | | `"wide"` | Solid border with additional space left and right | -For example `heavy white` would display a heavy white line around a widget. - -Borders may also be set individually for the four edges of a widget with the `border-top`, `border-right`, `border-bottom` and `border-left` rules. - -## Syntax - -``` -border: [] []; -border-top: [] []; -border-right: [] []; -border-bottom: [] []; -border-left: [] []; -``` +For example, `heavy white` would display a heavy white line around a widget. ## Border command diff --git a/docs/styles/box_sizing.md b/docs/styles/box_sizing.md index 549dcb395..16bc01039 100644 --- a/docs/styles/box_sizing.md +++ b/docs/styles/box_sizing.md @@ -2,19 +2,24 @@ The `box-sizing` rule impacts how `width` and `height` rules are translated in to screen dimensions, when combined with `padding` and `border`. -The default value is `border-box` which means that padding and border are included in the width and height. This setting means that if you add padding and/or border the widget will not change in size, but you will have less space for content. - -You can set `box-sizing` to `content-box` which tells Textual that padding and border should increase the size of the widget, leaving the content area unaffected. - ## Syntax ``` box-sizing: [border-box|content-box]; ``` +### Values + +| Values | Description | +|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `border-box` (default) | Padding and border are included in the width and height. If you add padding and/or border the widget will not change in size, but you will have less space for content. | +| `content-box` | Padding and border will increase the size of the widget, leaving the content area unaffected. | + ## Example -Both widgets in this example have the same height (5). The top widget has `box-sizing: border-box` which means that padding and border reduces the space for content. The bottom widget has `box-sizing: content-box` which increases the size of the widget to compensate for padding and border. +Both widgets in this example have the same height (5). +The top widget has `box-sizing: border-box` which means that padding and border reduces the space for content. +The bottom widget has `box-sizing: content-box` which increases the size of the widget to compensate for padding and border. === "box_sizing.py" diff --git a/docs/styles/display.md b/docs/styles/display.md index de29cffd0..555756738 100644 --- a/docs/styles/display.md +++ b/docs/styles/display.md @@ -1,6 +1,6 @@ # Display -The `display` property defines if a Widget is displayed or not. The default value is `"block"` which will display the widget as normal. Setting the property to `"none"` will effectively make it invisible. +The `display` property defines if a widget is displayed or not. ## Syntax @@ -8,6 +8,13 @@ The `display` property defines if a Widget is displayed or not. The default valu display: [none|block]; ``` +### Values + +| Value | Description | +|-------------------|---------------------------------------------------------------------------| +| `block` (default) | Display the widget as normal | +| `none` | The widget not be displayed, and space will no longer be reserved for it. | + ## Example Note that the second widget is hidden by adding the "hidden" class which sets the display style to None. diff --git a/docs/styles/margin.md b/docs/styles/margin.md index 8f34b0fc6..21cab8c61 100644 --- a/docs/styles/margin.md +++ b/docs/styles/margin.md @@ -2,8 +2,8 @@ The `margin` rule adds space around the entire widget. Margin may be specified with 1, 2 or 4 values. -| example | | -| ------------------ | ------------------------------------------------------------------- | +| Example | Description | +|--------------------|---------------------------------------------------------------------| | `margin: 1;` | A single value sets a margin of 1 around all 4 edges | | `margin: 1 2;` | Two values sets the margin for the top/bottom and left/right edges | | `margin: 1 2 3 4;` | Four values sets top, right, bottom, and left margins independently | diff --git a/docs/styles/outline.md b/docs/styles/outline.md index e77574b15..2267fedb7 100644 --- a/docs/styles/outline.md +++ b/docs/styles/outline.md @@ -1,11 +1,28 @@ # Outline -The `outline` rule enables the drawing of a box around a widget. Similar to `border`, but unlike border, outline will draw over the content area. This rule can be useful for emphasis if you want to display a outline for a brief time to draw the user's attention to it. +The `outline` rule enables the drawing of a box around a widget. Similar to `border`, but unlike border, outline will +draw _over_ the content area. This rule can be useful for emphasis if you want to display an outline for a brief time to +draw the user's attention to it. -An outline is set with a border value (see below) followed by a color. +An outline is set with a border value (see table below) followed by a color. -| Border value | Explanation | -| ------------ | ------------------------------------------------------- | +Outlines may also be set individually with the `outline-top`, `outline-right`, `outline-bottom` and `outline-left` +rules. + +## Syntax + +``` +outline: [] []; +outline-top: [] []; +outline-right: [] []; +outline-bottom: [] []; +outline-left: [] []; +``` + +### Values + +| Border value | Description | +|--------------|---------------------------------------------------------| | `"ascii"` | A border with plus, hyphen, and vertical bar | | `"blank"` | A blank border (reserves space for a border) | | `"dashed"` | Dashed line border | @@ -22,19 +39,7 @@ An outline is set with a border value (see below) followed by a color. | `"vkey"` | Vertical key-line border | | `"wide"` | Solid border with additional space left and right | -For example `heavy white` would display a heavy white line around a widget. - -Outlines may also be set individually with the `outline-top`, `outline-right`, `outline-bottom` and `outline-left` rules. - -## Syntax - -``` -outline: [] []; -outline-top: [] []; -outline-right: [] []; -outline-bottom: [] []; -outline-left: [] []; -``` +For example, `heavy white` would display a heavy white line around a widget. ## Example @@ -61,10 +66,10 @@ This examples shows a widget with an outline. Note how the outline occludes the ```sass /* Set a heavy white outline */ -outline: heavy white; +outline:heavy white; /* set a red outline on the left */ -outline-left: outer red; +outline-left:outer red; ``` ## Python @@ -73,6 +78,6 @@ outline-left: outer red; # Set a heavy white outline widget.outline = ("heavy", "white) -# Set a red outline on the left -widget.outline_left = ("outer", "red) + # Set a red outline on the left + widget.outline_left = ("outer", "red) ``` diff --git a/docs/styles/overflow.md b/docs/styles/overflow.md index af109c3b0..515761bca 100644 --- a/docs/styles/overflow.md +++ b/docs/styles/overflow.md @@ -1,12 +1,7 @@ # Overflow -The `overflow` rule specifies if and when scrollbars should be displayed on the `x` and `y` axis. The rule takes two overflow values; one for the horizontal bar (x axis), followed by the vertical bar (y-axis). - -| Overflow value | Effect | -| -------------- | ------------------------------------------------------------------------- | -| `"auto"` | Automatically show the scrollbar if the content doesn't fit (the default) | -| `"hidden"` | Never show the scrollbar | -| `"scroll"` | Always show the scrollbar | +The `overflow` rule specifies if and when scrollbars should be displayed on the `x` and `y` axis. +The rule takes two overflow values; one for the horizontal bar (x axis), followed by the vertical bar (y-axis). The default value for overflow is `"auto auto"` which will show scrollbars automatically for both scrollbars if content doesn't fit within container. @@ -20,11 +15,20 @@ overflow-x: [auto|hidden|scroll]; overflow-y: [auto|hidden|scroll]; ``` +### Values + +| Value | Description | +|------------------|---------------------------------------------------------| +| `auto` (default) | Automatically show the scrollbar if content doesn't fit | +| `hidden` | Never show the scrollbar | +| `scroll` | Always show the scrollbar | + ## Example Here we split the screen in to left and right sections, each with three vertically scrolling widgets that do not fit in to the height of the terminal. -The left side has `overflow-y: auto` (the default) and will automatically show a scrollbar. The right side has `overflow-y: hidden` which will prevent a scrollbar from being shown. +The left side has `overflow-y: auto` (the default) and will automatically show a scrollbar. +The right side has `overflow-y: hidden` which will prevent a scrollbar from being shown. === "overflow.py" diff --git a/docs/styles/scrollbar.md b/docs/styles/scrollbar.md index 57b4346d7..11ebc6387 100644 --- a/docs/styles/scrollbar.md +++ b/docs/styles/scrollbar.md @@ -1,9 +1,10 @@ # Scrollbar colors -There are a number of rules to set the colors used in Textual scrollbars. You won't typically need to do this, as the default themes have carefully chosen colors, but you can if you want to. +There are a number of rules to set the colors used in Textual scrollbars. +You won't typically need to do this, as the default themes have carefully chosen colors, but you can if you want to. | Rule | Color | -| ----------------------------- | ------------------------------------------------------- | +|-------------------------------|---------------------------------------------------------| | `scrollbar-color` | Scrollbar "thumb" (movable part) | | `scrollbar-color-hover` | Scrollbar thumb when the mouse is hovering over it | | `scrollbar-color-active` | Scrollbar thumb when it is active (being dragged) | diff --git a/docs/styles/text_style.md b/docs/styles/text_style.md index 936220b3c..b54a1f2aa 100644 --- a/docs/styles/text_style.md +++ b/docs/styles/text_style.md @@ -1,23 +1,26 @@ # Text-style -The `text-style` rule enables a number of different ways of displaying text. The value may be set to any of the following: +The `text-style` rule enables a number of different ways of displaying text. -| Style | Effect | -| ------------- | -------------------------------------------------------------- | -| `"bold"` | **bold text** | -| `"italic"` | _italic text_ | -| `"reverse"` | reverse video text (foreground and background colors reversed) | -| `"underline"` | underline text | -| `"strike"` | strikethrough text | - -Text styles may be set in combination. For example "bold underline" or "reverse underline strike". +Text styles may be set in combination. +For example `bold underline` or `reverse underline strike`. ## Syntax ``` -text-style: ... +text-style: ...; ``` +### Values + +| Value | Description | +|-------------|----------------------------------------------------------------| +| `bold` | **bold text** | +| `italic` | _italic text_ | +| `reverse` | reverse video text (foreground and background colors reversed) | +| `underline` | underline text | +| `strike` | strikethrough text | + ## Example Each of the three text panels has a different text style. diff --git a/docs/styles/visibility.md b/docs/styles/visibility.md index ffacd4956..d864487b0 100644 --- a/docs/styles/visibility.md +++ b/docs/styles/visibility.md @@ -1,13 +1,20 @@ # Visibility -The `visibility` rule may be used to make a widget invisible while still reserving spacing for it. The default value is `"visible"` which will cause the Widget to be displayed as normal. Setting the value to `"hidden"` will cause the Widget to become invisible. +The `visibility` rule may be used to make a widget invisible while still reserving spacing for it. ## Syntax ``` -visibility: [hidden|visible]; +visibility: [visible|hidden]; ``` +### Values + +| Value | Description | +|---------------------|----------------------------------------| +| `visible` (default) | The widget will be displayed as normal | +| `hidden` | The widget will be invisible | + ## Example Note that the second widget is hidden, while leaving a space where it would have been rendered. diff --git a/sandbox/darren/just_a_box.css b/sandbox/darren/just_a_box.css index 3a09b9c02..cd30ae532 100644 --- a/sandbox/darren/just_a_box.css +++ b/sandbox/darren/just_a_box.css @@ -2,23 +2,14 @@ Screen { background: lightcoral; } -#left_pane { - background: red; - width: 20 - overflow: scroll scroll; -} - -#middle_pane { - background: green; - width: 140; -} - -#right_pane { - background: blue; - width: 30; -} - -.box { +.box1 { + background: orangered; height: 12; width: 30; } + +.box2 { + background: blueviolet; + height: 6; + width: 12; +} diff --git a/sandbox/darren/just_a_box.py b/sandbox/darren/just_a_box.py index ee8be631e..24d2715f4 100644 --- a/sandbox/darren/just_a_box.py +++ b/sandbox/darren/just_a_box.py @@ -1,7 +1,5 @@ from __future__ import annotations -import asyncio - from rich.console import RenderableType from textual import events @@ -23,17 +21,20 @@ class Box(Widget, can_focus=True): class JustABox(App): def compose(self) -> ComposeResult: - self.box = Box() + self.box = Box(classes="box1") yield self.box + yield Box(classes="box2") def key_a(self): - self.animator.animate( - self.box.styles, - "opacity", - value=0.0, - duration=2.0, - on_complete=self.box.remove, - ) + self.box.styles.display = "none" + # self.box.styles.visibility = "hidden" + # self.animator.animate( + # self.box.styles, + # "opacity", + # value=0.0, + # duration=2.0, + # on_complete=self.box.remove, + # ) async def on_key(self, event: events.Key) -> None: await self.dispatch_key(event) diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index cf090866c..c7fb9183b 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -45,10 +45,6 @@ class TokenError(Exception): def _get_snippet(self) -> Panel: """Get a short snippet of code around a given line number. - Args: - code (str): The code. - line_no (int): Line number. - Returns: Panel: A renderable. """ From 63cf7459c92cb9624bb3915c19f4b121640b0bcd Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 30 Aug 2022 11:52:30 +0100 Subject: [PATCH 21/23] Reword box-sizing description --- docs/styles/box_sizing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/styles/box_sizing.md b/docs/styles/box_sizing.md index 16bc01039..bc089ad44 100644 --- a/docs/styles/box_sizing.md +++ b/docs/styles/box_sizing.md @@ -1,6 +1,6 @@ # Box-sizing -The `box-sizing` rule impacts how `width` and `height` rules are translated in to screen dimensions, when combined with `padding` and `border`. +The `box-sizing` property determines how the width and height of a widget are calculated. ## Syntax From 2fb99b9cc2ba91d33b7ab6cdad922a1c1ff2df72 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 30 Aug 2022 11:55:26 +0100 Subject: [PATCH 22/23] Rewording --- docs/styles/display.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/styles/display.md b/docs/styles/display.md index 555756738..962838939 100644 --- a/docs/styles/display.md +++ b/docs/styles/display.md @@ -1,6 +1,6 @@ # Display -The `display` property defines if a widget is displayed or not. +The `display` property defines whether a widget is displayed or not. ## Syntax From 79ccbae06e4608264e48af4e31231fe60229f074 Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Tue, 30 Aug 2022 12:02:03 +0100 Subject: [PATCH 23/23] 1 character rewording in docs --- docs/styles/overflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/styles/overflow.md b/docs/styles/overflow.md index 515761bca..f850281a1 100644 --- a/docs/styles/overflow.md +++ b/docs/styles/overflow.md @@ -1,7 +1,7 @@ # Overflow The `overflow` rule specifies if and when scrollbars should be displayed on the `x` and `y` axis. -The rule takes two overflow values; one for the horizontal bar (x axis), followed by the vertical bar (y-axis). +The rule takes two overflow values; one for the horizontal bar (x-axis), followed by the vertical bar (y-axis). The default value for overflow is `"auto auto"` which will show scrollbars automatically for both scrollbars if content doesn't fit within container.