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"