diff --git a/src/textual/widgets/_led_display.py b/src/textual/widgets/_led_display.py index c7e1164de58..d708c1fb766 100644 --- a/src/textual/widgets/_led_display.py +++ b/src/textual/widgets/_led_display.py @@ -1,5 +1,8 @@ +from textual.app import ComposeResult from textual.reactive import reactive +from textual.widget import Widget from textual.widgets import Static +from textual.containers import Horizontal, Vertical _character_map: dict[str, str] = {} @@ -10,7 +13,7 @@ "0" ] = """ ┏━┓ -┃ ┃ +┃╱┃ ┗━┛ """ @@ -120,13 +123,20 @@ ┗━╸ """ -_character_map["D"] = _character_map["0"] +_character_map[ + "D" +] = """ +┏━╮ +┃ ┃ +┗━╯ +""" + _character_map[ "E" ] = """ ┏━╸ -┣━╸ +┣━ ┗━╸ """ @@ -202,7 +212,13 @@ ╹ ╹ """ -_character_map["O"] = _character_map["0"] +_character_map[ + "O" +] = """ +╭━╮ +┃ ┃ +╰━╯ +""" _character_map[ "P" @@ -372,28 +388,171 @@ __ """ +_character_map[ + ":" +] = """ +_ +╹ +╹ +""" + +_character_map[ + ";" +] = """ +_ +╹ +╸ +""" + +_character_map[ + "(" +] = """ +__╸ +_┃_ +__╸ +""" + +_character_map[ + ")" +] = """ +_╺_ +__┃ +_╺_ +""" + +_character_map[ + "[" +] = """ +_┏╸ +_┃_ +_┗╸ +""" + +_character_map[ + "]" +] = """ +╺┓ +_┃ +╺┛ +""" + +_character_map[ + "{" +] = """ +_┏╸ +_┫_ +_┗╸ +""" + +_character_map[ + "}" +] = """ +╺┓ +_┣ +╺┛ +""" + +_character_map[ + "<" +] = """ +__╸ +_╸_ +__╸ +""" + +_character_map[ + ">" +] = """ +╺__ +_╺_ +╺__ +""" + +_character_map[ + "@" +] = """ +╭━╮ +╹╹┛ +╰━_ +""" + +_character_map[ + "#" +] = """ +╋╋ +╋╋ +___ +""" + + # here we strip spaces and replace underscores with spaces _character_map = {k: v.strip() for k, v in _character_map.items()} -def render_digits(digits: str) -> str: - """Render a string of digits as 7-segment LED-like characters.""" - lines = [""] * 3 - for digit in digits: - for i, line in enumerate(_character_map[digit].splitlines()): - lines[i] += line.replace("_", " ") - return "\n".join(lines) +def render_single_digit(digit: str): + return _character_map[digit].replace("_", " ") + + +class SingleDigitDisplay(Static): + digit = reactive(" ", layout=True) + + DEFAULT_CSS = """ + SingleDigitDisplay { + height: 3; + max-width: 3; + } + """ + + def __init__(self, initial_value=" ", **kwargs): + super().__init__(**kwargs) + self.digit = initial_value + + def watch_digit(self, digit: str) -> None: + """Called when the digit attribute changes.""" + if len(digit) > 1: + raise ValueError(f"Expected a single character, got {len(digit)}") + self.update(render_single_digit(digit.upper())) -class LedDisplay(Static): +class LedDisplay(Widget): """A widget to display characters using 7-segment LED-like format.""" digits = reactive("", layout=True) + DEFAULT_CSS = """ + LedDisplay { + layout: horizontal; + height: 5; + } + """ + def __init__(self, initial_value="", **kwargs): super().__init__(**kwargs) + self._displays = [SingleDigitDisplay(d) for d in initial_value] self.digits = initial_value + self.previous_digits = initial_value + + def compose(self) -> ComposeResult: + for led_display in self._displays: + yield led_display def watch_digits(self, digits: str) -> None: - """Called when the time attribute changes.""" - self.update(render_digits(digits.upper())) + """ + Called when the digits attribute changes. + Here we update the display widgets to match the number of digits. + """ + diff_digits_len = len(digits) - len(self._displays) + if diff_digits_len > 0: + # add new displays + start = len(self._displays) + for i in range(diff_digits_len): + new_widget = SingleDigitDisplay(digits[start + i]) + self._displays.append(new_widget) + self.mount(new_widget) + elif diff_digits_len < 0: + # remove displays + for display in self._displays[diff_digits_len:]: + display.remove() + self._displays.remove(display) + for i, d in enumerate(self.digits): + self._displays[i].digit = d