From 08b75f452d38ce69c035ae8c107f97f9bbf08c80 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 18:10:05 +0100 Subject: [PATCH 01/15] Digits --- src/textual/renderables/digits.py | 138 ++++++++++++++++++++++++++++++ src/textual/widgets/_digits.py | 119 ++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/textual/renderables/digits.py create mode 100644 src/textual/widgets/_digits.py diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py new file mode 100644 index 0000000000..fd7b270c79 --- /dev/null +++ b/src/textual/renderables/digits.py @@ -0,0 +1,138 @@ +from __future__ import annotations + +from rich.console import Console, ConsoleOptions, RenderResult +from rich.measure import Measurement +from rich.segment import Segment +from rich.style import Style, StyleType + +DIGITS = r" 0123456789+-.%ex," +DIGITS3X3 = """\ + + + +┏━┓ +┃ ┃ +┗━┛ + ┓ + ┃ +╺┻╸ +╺━┓ +┏━┛ +┗━╸ +╺━┓ + ━┫ +╺━┛ +╻ ╻ +┗━┫ + ╹ +┏━╸ +┗━┓ +╺━┛ +┏━╸ +┣━┓ +┗━┛ +╺━┓ + ┃ + ╹ +┏━┓ +┣━┫ +┗━┛ +┏━┓ +┗━┫ +╺━┛ + +╺╋╸ + + +╺━╸ + + + +. + + % + + + +e + +x + + + +, +""".splitlines() +DIGIT_WIDTHS = {".": 1, "x": 1, "e": 1, ",": 1} + + +class Digits: + """Renders a 3X3 unicode 'font' for numerical values.""" + + def __init__(self, text: str, style: StyleType = "") -> None: + self._text = text + self._style = style + + @classmethod + def _filter_text(cls, text: str) -> str: + """Remove any unsupported characters.""" + return "".join( + (character if character in DIGITS else " ") + for character in text + if character in DIGITS + ) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + style = console.get_style(self._style) + yield from self.render(style) + + def render(self, style: Style) -> RenderResult: + """Render with the given style + + Args: + style: Rich Style + + """ + segments: list[list[Segment]] = [[], [], []] + _Segment = Segment + row1 = segments[0].append + row2 = segments[1].append + row3 = segments[2].append + + for character in self._filter_text(self._text): + try: + position = DIGITS.index(character) * 3 + except ValueError: + continue + width = DIGIT_WIDTHS.get(character, 3) + line1, line2, line3 = [ + line.ljust(width) for line in DIGITS3X3[position : position + 3] + ] + row1(_Segment(line1, style)) + row2(_Segment(line2, style)) + row3(_Segment(line3, style)) + + new_line = Segment.line() + for line in segments: + yield from line + yield new_line + + @classmethod + def get_width(cls, text: str) -> int: + """Calculate the width without rendering.""" + text = cls._filter_text(text) + width = sum(DIGIT_WIDTHS.get(character, 3) for character in text) + return width + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + width = self.get_width(self._text) + return Measurement(width, width) + + +if __name__ == "__main__": + from rich import print + + print(Digits("3x10e4%", "white on blue")) diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py new file mode 100644 index 0000000000..cbab6b5993 --- /dev/null +++ b/src/textual/widgets/_digits.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from typing import cast + +from rich.align import Align, AlignMethod +from rich.console import RenderableType + +from textual.geometry import Size + +from ..css.types import AlignHorizontal +from ..geometry import Size +from ..reactive import reactive +from ..renderables.digits import Digits as DigitsRenderable +from ..widget import Widget + + +class Digits(Widget): + """A widget to display numerical values using a 3x3 grid of unicode characters.""" + + DEFAULT_CSS = """ + Digits { + width: 1fr; + height: 3; + text-align: left; + text-style: bold; + } + """ + + def __init__( + self, + value: str = "0", + *, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ) -> None: + """Initialize digits + + Args: + label: The text that appears within the button. + variant: The variant of the button. + name: The name of the button. + id: The ID of the button in the DOM. + classes: The CSS classes of the button. + disabled: Whether the button is disabled or not. + """ + super().__init__(name=name, id=id, classes=classes, disabled=disabled) + self.value = value + + def update(self, new_value: str) -> None: + """Update the Digits with a new value. + + Args: + new_value: New value to display. + """ + layout_required = len(new_value) != len(self.value) or ( + DigitsRenderable.get_width(self.value) + != DigitsRenderable.get_width(new_value) + ) + self.value = new_value + self.refresh(layout=layout_required) + + def render(self) -> RenderableType: + """Render digits.""" + rich_style = self.rich_style + digits = DigitsRenderable(self.value, rich_style) + text_align = self.styles.text_align + align = "left" if text_align not in {"left", "center", "right"} else text_align + return Align(digits, cast(AlignMethod, align), rich_style) + + def get_content_width(self, container: Size, viewport: Size) -> int: + """Called by textual to get the width of the content area. + + Args: + container: Size of the container (immediate parent) widget. + viewport: Size of the viewport. + + Returns: + The optimal width of the content. + """ + return DigitsRenderable.get_width(self.value) + + def get_content_height(self, container: Size, viewport: Size, width: int) -> int: + """Called by Textual to get the height of the content area. + + Args: + container: Size of the container (immediate parent) widget. + viewport: Size of the viewport. + width: Width of renderable. + + Returns: + The height of the content. + """ + return 3 # Always 3 lines + + +if __name__ == "__main__": + from time import time + + from textual.app import App, ComposeResult + + class DigitApp(App): + def on_ready(self) -> None: + digits = self.query_one(Digits) + number = 2**32 + + def update(): + nonlocal number + number += 1 + digits.update("123,232.23") + + self.set_interval(1 / 10, update) + + def compose(self) -> ComposeResult: + yield Digits() + + app = DigitApp() + app.run() From cd97943c76de40e6846a0b632a21124715ad4e81 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 18:27:58 +0100 Subject: [PATCH 02/15] digits widget --- docs/examples/widgets/digits.py | 21 ++++++++++++++++ docs/widgets/digits.md | 0 src/textual/renderables/digits.py | 8 +++++-- src/textual/widgets/__init__.py | 2 ++ src/textual/widgets/__init__.pyi | 1 + src/textual/widgets/_digits.py | 3 ++- tests/snapshot_tests/snapshot_apps/digits.py | 25 ++++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 16 ++++++++++--- 8 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 docs/examples/widgets/digits.py create mode 100644 docs/widgets/digits.md create mode 100644 tests/snapshot_tests/snapshot_apps/digits.py diff --git a/docs/examples/widgets/digits.py b/docs/examples/widgets/digits.py new file mode 100644 index 0000000000..d79beb475b --- /dev/null +++ b/docs/examples/widgets/digits.py @@ -0,0 +1,21 @@ +from textual.app import App, ComposeResult +from textual.widgets import Digits + + +class DigitApp(App): + CSS = """ + Screen { + align: center middle; + } + #pi { + border: double green; + width: auto; + } + """ + + def compose(self) -> ComposeResult: + yield Digits("3.141,592,653,5897", id="pi") + + +app = DigitApp() +app.run() diff --git a/docs/widgets/digits.md b/docs/widgets/digits.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index fd7b270c79..305e7c11a3 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -5,7 +5,7 @@ from rich.segment import Segment from rich.style import Style, StyleType -DIGITS = r" 0123456789+-.%ex," +DIGITS = r" 0123456789+-.%ex,^" DIGITS3X3 = """\ @@ -61,8 +61,12 @@ , +^ + + + """.splitlines() -DIGIT_WIDTHS = {".": 1, "x": 1, "e": 1, ",": 1} +DIGIT_WIDTHS = {".": 1, "x": 1, "e": 1, ",": 1, "^": 1} class Digits: diff --git a/src/textual/widgets/__init__.py b/src/textual/widgets/__init__.py index d21bcd47a2..8c71dfa7fd 100644 --- a/src/textual/widgets/__init__.py +++ b/src/textual/widgets/__init__.py @@ -14,6 +14,7 @@ from ._checkbox import Checkbox from ._content_switcher import ContentSwitcher from ._data_table import DataTable + from ._digits import Digits from ._directory_tree import DirectoryTree from ._footer import Footer from ._header import Header @@ -48,6 +49,7 @@ "Checkbox", "ContentSwitcher", "DataTable", + "Digits", "DirectoryTree", "Footer", "Header", diff --git a/src/textual/widgets/__init__.pyi b/src/textual/widgets/__init__.pyi index bae8df27c5..86f17d13cb 100644 --- a/src/textual/widgets/__init__.pyi +++ b/src/textual/widgets/__init__.pyi @@ -3,6 +3,7 @@ from ._button import Button as Button from ._checkbox import Checkbox as Checkbox from ._content_switcher import ContentSwitcher as ContentSwitcher from ._data_table import DataTable as DataTable +from ._digits import Digits as Digits from ._directory_tree import DirectoryTree as DirectoryTree from ._footer import Footer as Footer from ._header import Header as Header diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index cbab6b5993..81e5bdf7c0 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -20,9 +20,10 @@ class Digits(Widget): DEFAULT_CSS = """ Digits { width: 1fr; - height: 3; + height: auto; text-align: left; text-style: bold; + box-sizing: border-box; } """ diff --git a/tests/snapshot_tests/snapshot_apps/digits.py b/tests/snapshot_tests/snapshot_apps/digits.py new file mode 100644 index 0000000000..66ba9bffc2 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/digits.py @@ -0,0 +1,25 @@ +from textual.app import App, ComposeResult +from textual.widgets import Digits + + +class DigitApp(App): + CSS = """ + #digits1 { + text-align: left; + } + #digits2 { + text-align:center; + } + #digits3 { + text-align:right; + } + """ + + def compose(self) -> ComposeResult: + yield Digits("3.1427", id="digits1") + yield Digits(" 0123456789+-.%ex,", id="digits2") + yield Digits("1,2,3,4,5,6,7,8,9", id="digits3") + + +app = DigitApp() +app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 5c2d07b24e..9d2257f667 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -239,15 +239,21 @@ def test_option_list_build(snap_compare): def test_option_list_replace_prompt_from_single_line_to_single_line(snap_compare): - assert snap_compare(SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["1"]) + assert snap_compare( + SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["1"] + ) def test_option_list_replace_prompt_from_single_line_to_two_lines(snap_compare): - assert snap_compare(SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["2"]) + assert snap_compare( + SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["2"] + ) def test_option_list_replace_prompt_from_two_lines_to_three_lines(snap_compare): - assert snap_compare(SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["3"]) + assert snap_compare( + SNAPSHOT_APPS_DIR / "option_list_multiline_options.py", press=["3"] + ) def test_progress_bar_indeterminate(snap_compare): @@ -622,3 +628,7 @@ def test_text_log_blank_write(snap_compare) -> None: def test_nested_fr(snap_compare) -> None: # https://github.com/Textualize/textual/pull/3059 assert snap_compare(SNAPSHOT_APPS_DIR / "nested_fr.py") + + +def test_digits(snap_compare) -> None: + assert snap_compare(SNAPSHOT_APPS_DIR / "digits.py") From 98c5766dc25cc22e2167b5cffcc5ddf1f770c028 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 19:21:39 +0100 Subject: [PATCH 03/15] update requires str --- src/textual/widgets/_digits.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index 81e5bdf7c0..bcc1b3ec67 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -47,19 +47,22 @@ def __init__( disabled: Whether the button is disabled or not. """ super().__init__(name=name, id=id, classes=classes, disabled=disabled) + if not isinstance(value, str): + raise TypeError("value must be a str") self.value = value - def update(self, new_value: str) -> None: + def update(self, value: str | float) -> None: """Update the Digits with a new value. Args: - new_value: New value to display. + value: New value to display. """ - layout_required = len(new_value) != len(self.value) or ( - DigitsRenderable.get_width(self.value) - != DigitsRenderable.get_width(new_value) + if not isinstance(value, str): + raise TypeError("value must be a str") + layout_required = len(value) != len(self.value) or ( + DigitsRenderable.get_width(self.value) != DigitsRenderable.get_width(value) ) - self.value = new_value + self.value = value self.refresh(layout=layout_required) def render(self) -> RenderableType: From 8be9c32d06a906acf06423fb8c530032939bb852 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:15:05 +0100 Subject: [PATCH 04/15] digits docs --- docs/examples/widgets/clock.py | 31 ++ docs/examples/widgets/digits.py | 5 +- docs/widget_gallery.md | 9 + docs/widgets/digits.md | 66 +++ mkdocs-nav.yml | 407 ++++++++++--------- src/textual/renderables/digits.py | 54 +-- src/textual/widgets/_digits.py | 48 +-- tests/snapshot_tests/snapshot_apps/digits.py | 4 +- 8 files changed, 342 insertions(+), 282 deletions(-) create mode 100644 docs/examples/widgets/clock.py diff --git a/docs/examples/widgets/clock.py b/docs/examples/widgets/clock.py new file mode 100644 index 0000000000..6b18af3c85 --- /dev/null +++ b/docs/examples/widgets/clock.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from textual.app import App, ComposeResult +from textual.widgets import Digits + + +class ClockApp(App): + CSS = """ + Screen { + align: center middle; + } + #clock { + width: auto; + } + """ + + def compose(self) -> ComposeResult: + yield Digits("", id="clock") + + def on_ready(self) -> None: + self.update_clock() + self.set_interval(1, self.update_clock) + + def update_clock(self) -> None: + clock = datetime.now().time() + self.query_one(Digits).update(f"{clock:%T}") + + +if __name__ == "__main__": + app = ClockApp() + app.run() diff --git a/docs/examples/widgets/digits.py b/docs/examples/widgets/digits.py index d79beb475b..b7e13f0a5a 100644 --- a/docs/examples/widgets/digits.py +++ b/docs/examples/widgets/digits.py @@ -17,5 +17,6 @@ def compose(self) -> ComposeResult: yield Digits("3.141,592,653,5897", id="pi") -app = DigitApp() -app.run() +if __name__ == "__main__": + app = DigitApp() + app.run() diff --git a/docs/widget_gallery.md b/docs/widget_gallery.md index 1286d78e0f..da06823945 100644 --- a/docs/widget_gallery.md +++ b/docs/widget_gallery.md @@ -52,6 +52,15 @@ A powerful data table, with configurable cursors. ```{.textual path="docs/examples/widgets/data_table.py"} ``` +## Digits + +Display numbers in tall characters. + +[Digits reference](./widgets/digits.md){ .md-button .md-button--primary } + +```{.textual path="docs/examples/widgets/digits.py"} +``` + ## DirectoryTree A tree view of files and folders. diff --git a/docs/widgets/digits.md b/docs/widgets/digits.md index e69de29bb2..78635b3ae6 100644 --- a/docs/widgets/digits.md +++ b/docs/widgets/digits.md @@ -0,0 +1,66 @@ +# Digits + +!!! tip "Added in version 0.33.0" + +A widget to display numerical values in tall multi-line characters. + +The digits 0-9 are supported, in addition to the following characters `+`, `-`, `^`, `:`, and `×`. +Other characters will be displayed in a regular size font. + +You can set the text to be displayed in the constructor, or call [`update()`][textual.widgets.Digits.update] to change the text after the widget has been mounted. + +!!! note Note that this widget will respect the [text-align](../styles/text_align.md) rule. + +- [ ] Focusable +- [ ] Container + + +## Example + +Here's a simple examples to display a few digits of PI: + +=== "Output" + + ```{.textual path="docs/examples/widgets/digits.py"} + ``` + +=== "digits.py" + + ```python + --8<-- "docs/examples/widgets/digits.py" + ``` + +Here's another example which uses `Digits` to display the current time: + + +=== "Output" + + ```{.textual path="docs/examples/widgets/clock.py"} + ``` + +=== "clock.py" + + ```python + --8<-- "docs/examples/widgets/clock.py" + ``` + +## Reactive attributes + +This widget has no reactive attributes. + +## Bindings + +This widget has no bindings. + +## Component classes + +This widget has no component classes. + + + +--- + + +::: textual.widgets.Digits + options: + heading_level: 2 diff --git a/mkdocs-nav.yml b/mkdocs-nav.yml index d97cda74ed..adacbb6723 100644 --- a/mkdocs-nav.yml +++ b/mkdocs-nav.yml @@ -1,204 +1,205 @@ nav: - - Introduction: - - "index.md" - - "getting_started.md" - - "help.md" - - "tutorial.md" - - Guide: - - "guide/index.md" - - "guide/devtools.md" - - "guide/app.md" - - "guide/styles.md" - - "guide/CSS.md" - - "guide/design.md" - - "guide/queries.md" - - "guide/layout.md" - - "guide/events.md" - - "guide/input.md" - - "guide/actions.md" - - "guide/reactivity.md" - - "guide/widgets.md" - - "guide/animation.md" - - "guide/screens.md" - - "guide/workers.md" - - "widget_gallery.md" - - Reference: - - "reference/index.md" - - CSS Types: - - "css_types/index.md" - - "css_types/border.md" - - "css_types/color.md" - - "css_types/horizontal.md" - - "css_types/integer.md" - - "css_types/name.md" - - "css_types/number.md" - - "css_types/overflow.md" - - "css_types/percentage.md" - - "css_types/scalar.md" - - "css_types/text_align.md" - - "css_types/text_style.md" - - "css_types/vertical.md" - - Events: - - "events/index.md" - - "events/blur.md" - - "events/descendant_blur.md" - - "events/descendant_focus.md" - - "events/enter.md" - - "events/focus.md" - - "events/hide.md" - - "events/key.md" - - "events/leave.md" - - "events/load.md" - - "events/mount.md" - - "events/mouse_capture.md" - - "events/click.md" - - "events/mouse_down.md" - - "events/mouse_move.md" - - "events/mouse_release.md" - - "events/mouse_scroll_down.md" - - "events/mouse_scroll_up.md" - - "events/mouse_up.md" - - "events/paste.md" - - "events/resize.md" - - "events/screen_resume.md" - - "events/screen_suspend.md" - - "events/show.md" - - Styles: - - "styles/align.md" - - "styles/background.md" - - "styles/border.md" - - "styles/border_subtitle_align.md" - - "styles/border_subtitle_background.md" - - "styles/border_subtitle_color.md" - - "styles/border_subtitle_style.md" - - "styles/border_title_align.md" - - "styles/border_title_background.md" - - "styles/border_title_color.md" - - "styles/border_title_style.md" - - "styles/box_sizing.md" - - "styles/color.md" - - "styles/content_align.md" - - "styles/display.md" - - "styles/dock.md" - - "styles/index.md" - - Grid: - - "styles/grid/index.md" - - "styles/grid/column_span.md" - - "styles/grid/grid_columns.md" - - "styles/grid/grid_gutter.md" - - "styles/grid/grid_rows.md" - - "styles/grid/grid_size.md" - - "styles/grid/row_span.md" - - "styles/height.md" - - "styles/layer.md" - - "styles/layers.md" - - "styles/layout.md" - - Links: - - "styles/links/index.md" - - "styles/links/link_background.md" - - "styles/links/link_color.md" - - "styles/links/link_hover_background.md" - - "styles/links/link_hover_color.md" - - "styles/links/link_hover_style.md" - - "styles/links/link_style.md" - - "styles/margin.md" - - "styles/max_height.md" - - "styles/max_width.md" - - "styles/min_height.md" - - "styles/min_width.md" - - "styles/offset.md" - - "styles/opacity.md" - - "styles/outline.md" - - "styles/overflow.md" - - "styles/padding.md" - - Scrollbar colors: - - "styles/scrollbar_colors/index.md" - - "styles/scrollbar_colors/scrollbar_background.md" - - "styles/scrollbar_colors/scrollbar_background_active.md" - - "styles/scrollbar_colors/scrollbar_background_hover.md" - - "styles/scrollbar_colors/scrollbar_color.md" - - "styles/scrollbar_colors/scrollbar_color_active.md" - - "styles/scrollbar_colors/scrollbar_color_hover.md" - - "styles/scrollbar_colors/scrollbar_corner_color.md" - - "styles/scrollbar_gutter.md" - - "styles/scrollbar_size.md" - - "styles/text_align.md" - - "styles/text_opacity.md" - - "styles/text_style.md" - - "styles/tint.md" - - "styles/visibility.md" - - "styles/width.md" - - Widgets: - - "widgets/button.md" - - "widgets/checkbox.md" - - "widgets/content_switcher.md" - - "widgets/data_table.md" - - "widgets/directory_tree.md" - - "widgets/footer.md" - - "widgets/header.md" - - "widgets/index.md" - - "widgets/input.md" - - "widgets/label.md" - - "widgets/list_item.md" - - "widgets/list_view.md" - - "widgets/loading_indicator.md" - - "widgets/log.md" - - "widgets/markdown_viewer.md" - - "widgets/markdown.md" - - "widgets/option_list.md" - - "widgets/placeholder.md" - - "widgets/pretty.md" - - "widgets/progress_bar.md" - - "widgets/radiobutton.md" - - "widgets/radioset.md" - - "widgets/rich_log.md" - - "widgets/select.md" - - "widgets/selection_list.md" - - "widgets/sparkline.md" - - "widgets/static.md" - - "widgets/switch.md" - - "widgets/tabbed_content.md" - - "widgets/tabs.md" - - "widgets/tree.md" - - API: - - "api/index.md" - - "api/app.md" - - "api/await_remove.md" - - "api/binding.md" - - "api/color.md" - - "api/containers.md" - - "api/coordinate.md" - - "api/dom_node.md" - - "api/events.md" - - "api/errors.md" - - "api/filter.md" - - "api/geometry.md" - - "api/logger.md" - - "api/logging.md" - - "api/map_geometry.md" - - "api/message_pump.md" - - "api/message.md" - - "api/on.md" - - "api/pilot.md" - - "api/query.md" - - "api/reactive.md" - - "api/screen.md" - - "api/scrollbar.md" - - "api/scroll_view.md" - - "api/strip.md" - - "api/suggester.md" - - "api/timer.md" - - "api/types.md" - - "api/validation.md" - - "api/walk.md" - - "api/widget.md" - - "api/work.md" - - "api/worker.md" - - "api/worker_manager.md" - - "How To": - - "how-to/index.md" - - "how-to/center-things.md" - - "how-to/design-a-layout.md" - - "roadmap.md" - - "Blog": - - blog/index.md + - Introduction: + - "index.md" + - "getting_started.md" + - "help.md" + - "tutorial.md" + - Guide: + - "guide/index.md" + - "guide/devtools.md" + - "guide/app.md" + - "guide/styles.md" + - "guide/CSS.md" + - "guide/design.md" + - "guide/queries.md" + - "guide/layout.md" + - "guide/events.md" + - "guide/input.md" + - "guide/actions.md" + - "guide/reactivity.md" + - "guide/widgets.md" + - "guide/animation.md" + - "guide/screens.md" + - "guide/workers.md" + - "widget_gallery.md" + - Reference: + - "reference/index.md" + - CSS Types: + - "css_types/index.md" + - "css_types/border.md" + - "css_types/color.md" + - "css_types/horizontal.md" + - "css_types/integer.md" + - "css_types/name.md" + - "css_types/number.md" + - "css_types/overflow.md" + - "css_types/percentage.md" + - "css_types/scalar.md" + - "css_types/text_align.md" + - "css_types/text_style.md" + - "css_types/vertical.md" + - Events: + - "events/index.md" + - "events/blur.md" + - "events/descendant_blur.md" + - "events/descendant_focus.md" + - "events/enter.md" + - "events/focus.md" + - "events/hide.md" + - "events/key.md" + - "events/leave.md" + - "events/load.md" + - "events/mount.md" + - "events/mouse_capture.md" + - "events/click.md" + - "events/mouse_down.md" + - "events/mouse_move.md" + - "events/mouse_release.md" + - "events/mouse_scroll_down.md" + - "events/mouse_scroll_up.md" + - "events/mouse_up.md" + - "events/paste.md" + - "events/resize.md" + - "events/screen_resume.md" + - "events/screen_suspend.md" + - "events/show.md" + - Styles: + - "styles/align.md" + - "styles/background.md" + - "styles/border.md" + - "styles/border_subtitle_align.md" + - "styles/border_subtitle_background.md" + - "styles/border_subtitle_color.md" + - "styles/border_subtitle_style.md" + - "styles/border_title_align.md" + - "styles/border_title_background.md" + - "styles/border_title_color.md" + - "styles/border_title_style.md" + - "styles/box_sizing.md" + - "styles/color.md" + - "styles/content_align.md" + - "styles/display.md" + - "styles/dock.md" + - "styles/index.md" + - Grid: + - "styles/grid/index.md" + - "styles/grid/column_span.md" + - "styles/grid/grid_columns.md" + - "styles/grid/grid_gutter.md" + - "styles/grid/grid_rows.md" + - "styles/grid/grid_size.md" + - "styles/grid/row_span.md" + - "styles/height.md" + - "styles/layer.md" + - "styles/layers.md" + - "styles/layout.md" + - Links: + - "styles/links/index.md" + - "styles/links/link_background.md" + - "styles/links/link_color.md" + - "styles/links/link_hover_background.md" + - "styles/links/link_hover_color.md" + - "styles/links/link_hover_style.md" + - "styles/links/link_style.md" + - "styles/margin.md" + - "styles/max_height.md" + - "styles/max_width.md" + - "styles/min_height.md" + - "styles/min_width.md" + - "styles/offset.md" + - "styles/opacity.md" + - "styles/outline.md" + - "styles/overflow.md" + - "styles/padding.md" + - Scrollbar colors: + - "styles/scrollbar_colors/index.md" + - "styles/scrollbar_colors/scrollbar_background.md" + - "styles/scrollbar_colors/scrollbar_background_active.md" + - "styles/scrollbar_colors/scrollbar_background_hover.md" + - "styles/scrollbar_colors/scrollbar_color.md" + - "styles/scrollbar_colors/scrollbar_color_active.md" + - "styles/scrollbar_colors/scrollbar_color_hover.md" + - "styles/scrollbar_colors/scrollbar_corner_color.md" + - "styles/scrollbar_gutter.md" + - "styles/scrollbar_size.md" + - "styles/text_align.md" + - "styles/text_opacity.md" + - "styles/text_style.md" + - "styles/tint.md" + - "styles/visibility.md" + - "styles/width.md" + - Widgets: + - "widgets/button.md" + - "widgets/checkbox.md" + - "widgets/content_switcher.md" + - "widgets/data_table.md" + - "widgets/digits.md" + - "widgets/directory_tree.md" + - "widgets/footer.md" + - "widgets/header.md" + - "widgets/index.md" + - "widgets/input.md" + - "widgets/label.md" + - "widgets/list_item.md" + - "widgets/list_view.md" + - "widgets/loading_indicator.md" + - "widgets/log.md" + - "widgets/markdown_viewer.md" + - "widgets/markdown.md" + - "widgets/option_list.md" + - "widgets/placeholder.md" + - "widgets/pretty.md" + - "widgets/progress_bar.md" + - "widgets/radiobutton.md" + - "widgets/radioset.md" + - "widgets/rich_log.md" + - "widgets/select.md" + - "widgets/selection_list.md" + - "widgets/sparkline.md" + - "widgets/static.md" + - "widgets/switch.md" + - "widgets/tabbed_content.md" + - "widgets/tabs.md" + - "widgets/tree.md" + - API: + - "api/index.md" + - "api/app.md" + - "api/await_remove.md" + - "api/binding.md" + - "api/color.md" + - "api/containers.md" + - "api/coordinate.md" + - "api/dom_node.md" + - "api/events.md" + - "api/errors.md" + - "api/filter.md" + - "api/geometry.md" + - "api/logger.md" + - "api/logging.md" + - "api/map_geometry.md" + - "api/message_pump.md" + - "api/message.md" + - "api/on.md" + - "api/pilot.md" + - "api/query.md" + - "api/reactive.md" + - "api/screen.md" + - "api/scrollbar.md" + - "api/scroll_view.md" + - "api/strip.md" + - "api/suggester.md" + - "api/timer.md" + - "api/types.md" + - "api/validation.md" + - "api/walk.md" + - "api/widget.md" + - "api/work.md" + - "api/worker.md" + - "api/worker_manager.md" + - "How To": + - "how-to/index.md" + - "how-to/center-things.md" + - "how-to/design-a-layout.md" + - "roadmap.md" + - "Blog": + - blog/index.md diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index 305e7c11a3..824765f6cf 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -5,7 +5,7 @@ from rich.segment import Segment from rich.style import Style, StyleType -DIGITS = r" 0123456789+-.%ex,^" +DIGITS = " 0123456789+-^x:" DIGITS3X3 = """\ @@ -46,27 +46,16 @@ ╺━╸ + ^ -. - % - - - -e - -x - - - -, -^ + × + : """.splitlines() -DIGIT_WIDTHS = {".": 1, "x": 1, "e": 1, ",": 1, "^": 1} class Digits: @@ -76,15 +65,6 @@ def __init__(self, text: str, style: StyleType = "") -> None: self._text = text self._style = style - @classmethod - def _filter_text(cls, text: str) -> str: - """Remove any unsupported characters.""" - return "".join( - (character if character in DIGITS else " ") - for character in text - if character in DIGITS - ) - def __rich_console__( self, console: Console, options: ConsoleOptions ) -> RenderResult: @@ -98,35 +78,33 @@ def render(self, style: Style) -> RenderResult: style: Rich Style """ - segments: list[list[Segment]] = [[], [], []] - _Segment = Segment + segments: list[list[str]] = [[], [], []] row1 = segments[0].append row2 = segments[1].append row3 = segments[2].append - for character in self._filter_text(self._text): + for character in self._text: try: position = DIGITS.index(character) * 3 except ValueError: - continue - width = DIGIT_WIDTHS.get(character, 3) - line1, line2, line3 = [ - line.ljust(width) for line in DIGITS3X3[position : position + 3] - ] - row1(_Segment(line1, style)) - row2(_Segment(line2, style)) - row3(_Segment(line3, style)) + row1(" ") + row2(" ") + row3(character) + else: + width = 3 if character in DIGITS else 1 + row1(DIGITS3X3[position].ljust(width)) + row2(DIGITS3X3[position + 1].ljust(width)) + row3(DIGITS3X3[position + 2].ljust(width)) new_line = Segment.line() for line in segments: - yield from line + yield Segment("".join(line), style) yield new_line @classmethod def get_width(cls, text: str) -> int: """Calculate the width without rendering.""" - text = cls._filter_text(text) - width = sum(DIGIT_WIDTHS.get(character, 3) for character in text) + width = sum(3 if character in DIGITS else 1 for character in text) return width def __rich_measure__( diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index bcc1b3ec67..418f2be153 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -15,7 +15,15 @@ class Digits(Widget): - """A widget to display numerical values using a 3x3 grid of unicode characters.""" + """A widget to display numerical values using a 3x3 grid of unicode characters. + + Args: + name: The name of the widget. + id: The ID of the widget in the DOM. + classes: The CSS classes of the widget. + disabled: Whether the widget is disabled or not. + + """ DEFAULT_CSS = """ Digits { @@ -36,22 +44,12 @@ def __init__( classes: str | None = None, disabled: bool = False, ) -> None: - """Initialize digits - - Args: - label: The text that appears within the button. - variant: The variant of the button. - name: The name of the button. - id: The ID of the button in the DOM. - classes: The CSS classes of the button. - disabled: Whether the button is disabled or not. - """ - super().__init__(name=name, id=id, classes=classes, disabled=disabled) if not isinstance(value, str): raise TypeError("value must be a str") + super().__init__(name=name, id=id, classes=classes, disabled=disabled) self.value = value - def update(self, value: str | float) -> None: + def update(self, value: str) -> None: """Update the Digits with a new value. Args: @@ -97,27 +95,3 @@ def get_content_height(self, container: Size, viewport: Size, width: int) -> int The height of the content. """ return 3 # Always 3 lines - - -if __name__ == "__main__": - from time import time - - from textual.app import App, ComposeResult - - class DigitApp(App): - def on_ready(self) -> None: - digits = self.query_one(Digits) - number = 2**32 - - def update(): - nonlocal number - number += 1 - digits.update("123,232.23") - - self.set_interval(1 / 10, update) - - def compose(self) -> ComposeResult: - yield Digits() - - app = DigitApp() - app.run() diff --git a/tests/snapshot_tests/snapshot_apps/digits.py b/tests/snapshot_tests/snapshot_apps/digits.py index 66ba9bffc2..7e5d59ff1b 100644 --- a/tests/snapshot_tests/snapshot_apps/digits.py +++ b/tests/snapshot_tests/snapshot_apps/digits.py @@ -17,8 +17,8 @@ class DigitApp(App): def compose(self) -> ComposeResult: yield Digits("3.1427", id="digits1") - yield Digits(" 0123456789+-.%ex,", id="digits2") - yield Digits("1,2,3,4,5,6,7,8,9", id="digits3") + yield Digits(" 0123456789+-.,", id="digits2") + yield Digits("3x10^4", id="digits3") app = DigitApp() From e1ac7dcc433decb1c8e27f263780324b932223d1 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:15:56 +0100 Subject: [PATCH 05/15] simplify --- src/textual/widgets/_digits.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index 418f2be153..7478cc4218 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -7,9 +7,7 @@ from textual.geometry import Size -from ..css.types import AlignHorizontal from ..geometry import Size -from ..reactive import reactive from ..renderables.digits import Digits as DigitsRenderable from ..widget import Widget @@ -37,7 +35,7 @@ class Digits(Widget): def __init__( self, - value: str = "0", + value: str = "", *, name: str | None = None, id: str | None = None, From 771f9905ec02db9ae5e1e3320deded326f4d0680 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:20:30 +0100 Subject: [PATCH 06/15] tweak docs --- docs/widgets/digits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/digits.md b/docs/widgets/digits.md index 78635b3ae6..506dd02629 100644 --- a/docs/widgets/digits.md +++ b/docs/widgets/digits.md @@ -17,7 +17,7 @@ You can set the text to be displayed in the constructor, or call [`update()`][te ## Example -Here's a simple examples to display a few digits of PI: +The following example displays a few digits of Pi: === "Output" From 3202d00bdcafb8ac27e12ed236124828835ede89 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:31:03 +0100 Subject: [PATCH 07/15] snapshot test --- .../__snapshots__/test_snapshots.ambr | 156 ++++++++++++++++++ tests/snapshot_tests/snapshot_apps/digits.py | 5 +- 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index 803455113a..1802220c43 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -13719,6 +13719,162 @@ ''' # --- +# name: test_digits + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DigitApp + + + + + + + + + + ╺━┓  ┓ ╻ ╻╺━┓╺━┓ +  ━┫  ┃ ┗━┫┏━┛  ┃ + ╺━┛.╺┻╸  ╹┗━╸  ╹ +    ┏━┓ ┓ ╺━┓╺━┓╻ ╻┏━╸┏━╸╺━┓┏━┓┏━┓         +    ┃ ┃ ┃ ┏━┛ ━┫┗━┫┗━┓┣━┓  ┃┣━┫┗━┫╺╋╸╺━╸   +    ┗━┛╺┻╸┗━╸╺━┛  ╹╺━┛┗━┛  ╹┗━┛╺━┛      ., + ╺━┓    ┓ ┏━┓ ^ ╻ ╻ +  ━┫ ×  ┃ ┃ ┃   ┗━┫ + ╺━┛   ╺┻╸┗━┛     ╹ + + + + + + + + + + + + + + + + + + + + ''' +# --- # name: test_disabled_widgets ''' diff --git a/tests/snapshot_tests/snapshot_apps/digits.py b/tests/snapshot_tests/snapshot_apps/digits.py index 7e5d59ff1b..713cf2f6a9 100644 --- a/tests/snapshot_tests/snapshot_apps/digits.py +++ b/tests/snapshot_tests/snapshot_apps/digits.py @@ -21,5 +21,6 @@ def compose(self) -> ComposeResult: yield Digits("3x10^4", id="digits3") -app = DigitApp() -app.run() +if __name__ == "__main__": + app = DigitApp() + app.run() From 3b5ecc54f30351937b62b579387f9b7872111ee2 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:33:02 +0100 Subject: [PATCH 08/15] change name --- src/textual/renderables/digits.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index 824765f6cf..9118351035 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -78,10 +78,10 @@ def render(self, style: Style) -> RenderResult: style: Rich Style """ - segments: list[list[str]] = [[], [], []] - row1 = segments[0].append - row2 = segments[1].append - row3 = segments[2].append + digit_pieces: list[list[str]] = [[], [], []] + row1 = digit_pieces[0].append + row2 = digit_pieces[1].append + row3 = digit_pieces[2].append for character in self._text: try: @@ -97,7 +97,7 @@ def render(self, style: Style) -> RenderResult: row3(DIGITS3X3[position + 2].ljust(width)) new_line = Segment.line() - for line in segments: + for line in digit_pieces: yield Segment("".join(line), style) yield new_line From bcf09650090cf8a9cfa2846f69106d67381f64b2 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:35:31 +0100 Subject: [PATCH 09/15] simplify --- src/textual/renderables/digits.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index 9118351035..a24c48d279 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -91,10 +91,9 @@ def render(self, style: Style) -> RenderResult: row2(" ") row3(character) else: - width = 3 if character in DIGITS else 1 - row1(DIGITS3X3[position].ljust(width)) - row2(DIGITS3X3[position + 1].ljust(width)) - row3(DIGITS3X3[position + 2].ljust(width)) + row1(DIGITS3X3[position].ljust(3)) + row2(DIGITS3X3[position + 1].ljust(3)) + row3(DIGITS3X3[position + 2].ljust(3)) new_line = Segment.line() for line in digit_pieces: From 9da3c12a6ac31656fc06c6de5457db0b2923f585 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 21:38:02 +0100 Subject: [PATCH 10/15] docs --- docs/widgets/digits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/widgets/digits.md b/docs/widgets/digits.md index 506dd02629..6dd33044ce 100644 --- a/docs/widgets/digits.md +++ b/docs/widgets/digits.md @@ -9,7 +9,7 @@ Other characters will be displayed in a regular size font. You can set the text to be displayed in the constructor, or call [`update()`][textual.widgets.Digits.update] to change the text after the widget has been mounted. -!!! note Note that this widget will respect the [text-align](../styles/text_align.md) rule. +!!! note "This widget will respect the [text-align](../styles/text_align.md) rule." - [ ] Focusable - [ ] Container From 6da74630316f0eeb788acb1ced1a813d2c9f0d38 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 22:07:13 +0100 Subject: [PATCH 11/15] Update _digits.py superfluous import --- src/textual/widgets/_digits.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index 7478cc4218..81b45d444b 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -5,8 +5,6 @@ from rich.align import Align, AlignMethod from rich.console import RenderableType -from textual.geometry import Size - from ..geometry import Size from ..renderables.digits import Digits as DigitsRenderable from ..widget import Widget From a427617eb5532932bc2e6681b5c44de73cfc7708 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 8 Aug 2023 22:08:44 +0100 Subject: [PATCH 12/15] Update _digits.py docstring --- src/textual/widgets/_digits.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index 81b45d444b..167bfed10f 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -14,6 +14,7 @@ class Digits(Widget): """A widget to display numerical values using a 3x3 grid of unicode characters. Args: + value: Value to display in widget. name: The name of the widget. id: The ID of the widget in the DOM. classes: The CSS classes of the widget. From f80add9110d0f55140bcbca8a6a21035cbd65c54 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 9 Aug 2023 09:59:32 +0100 Subject: [PATCH 13/15] address review --- src/textual/renderables/digits.py | 19 +++++++++++++++++-- src/textual/widgets/_digits.py | 20 ++++++++++++++------ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index a24c48d279..26539ce6c3 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -59,7 +59,13 @@ class Digits: - """Renders a 3X3 unicode 'font' for numerical values.""" + """Renders a 3X3 unicode 'font' for numerical values. + + Args: + text: Text to display. + style: Style to apply to the digits. + + """ def __init__(self, text: str, style: StyleType = "") -> None: self._text = text @@ -77,6 +83,8 @@ def render(self, style: Style) -> RenderResult: Args: style: Rich Style + Returns: + Result of render. """ digit_pieces: list[list[str]] = [[], [], []] row1 = digit_pieces[0].append @@ -102,7 +110,14 @@ def render(self, style: Style) -> RenderResult: @classmethod def get_width(cls, text: str) -> int: - """Calculate the width without rendering.""" + """Calculate the width without rendering. + + Args: + text: Text which may be displayed in the `Digits` widget. + + Returns: + width of the text (in cells). + """ width = sum(3 if character in DIGITS else 1 for character in text) return width diff --git a/src/textual/widgets/_digits.py b/src/textual/widgets/_digits.py index 167bfed10f..799811ac70 100644 --- a/src/textual/widgets/_digits.py +++ b/src/textual/widgets/_digits.py @@ -44,26 +44,34 @@ def __init__( if not isinstance(value, str): raise TypeError("value must be a str") super().__init__(name=name, id=id, classes=classes, disabled=disabled) - self.value = value + self._value = value + + @property + def value(self) -> str: + """The current value displayed in the Digits.""" + return self._value def update(self, value: str) -> None: """Update the Digits with a new value. Args: value: New value to display. + + Raises: + ValueError: If the value isn't a `str`. """ if not isinstance(value, str): raise TypeError("value must be a str") - layout_required = len(value) != len(self.value) or ( - DigitsRenderable.get_width(self.value) != DigitsRenderable.get_width(value) + layout_required = len(value) != len(self._value) or ( + DigitsRenderable.get_width(self._value) != DigitsRenderable.get_width(value) ) - self.value = value + self._value = value self.refresh(layout=layout_required) def render(self) -> RenderableType: """Render digits.""" rich_style = self.rich_style - digits = DigitsRenderable(self.value, rich_style) + digits = DigitsRenderable(self._value, rich_style) text_align = self.styles.text_align align = "left" if text_align not in {"left", "center", "right"} else text_align return Align(digits, cast(AlignMethod, align), rich_style) @@ -78,7 +86,7 @@ def get_content_width(self, container: Size, viewport: Size) -> int: Returns: The optimal width of the content. """ - return DigitsRenderable.get_width(self.value) + return DigitsRenderable.get_width(self._value) def get_content_height(self, container: Size, viewport: Size, width: int) -> int: """Called by Textual to get the height of the content area. From a686289850bbcc8e0358a0445f2456ef4ada5c38 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 9 Aug 2023 10:00:43 +0100 Subject: [PATCH 14/15] formatting --- src/textual/renderables/digits.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/textual/renderables/digits.py b/src/textual/renderables/digits.py index 26539ce6c3..f22943e414 100644 --- a/src/textual/renderables/digits.py +++ b/src/textual/renderables/digits.py @@ -81,7 +81,7 @@ def render(self, style: Style) -> RenderResult: """Render with the given style Args: - style: Rich Style + style: Rich Style. Returns: Result of render. @@ -126,9 +126,3 @@ def __rich_measure__( ) -> Measurement: width = self.get_width(self._text) return Measurement(width, width) - - -if __name__ == "__main__": - from rich import print - - print(Digits("3x10e4%", "white on blue")) From 5530402a55073e570813806c2d33f9df2ccab83c Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 9 Aug 2023 10:01:12 +0100 Subject: [PATCH 15/15] Update tests/snapshot_tests/snapshot_apps/digits.py Co-authored-by: Dave Pearson --- tests/snapshot_tests/snapshot_apps/digits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snapshot_tests/snapshot_apps/digits.py b/tests/snapshot_tests/snapshot_apps/digits.py index 713cf2f6a9..f4c1be6e01 100644 --- a/tests/snapshot_tests/snapshot_apps/digits.py +++ b/tests/snapshot_tests/snapshot_apps/digits.py @@ -5,7 +5,7 @@ class DigitApp(App): CSS = """ #digits1 { - text-align: left; + text-align: left; } #digits2 { text-align:center;