Skip to content

Commit

Permalink
add DigitDisplay for digits and basic arithmetic operators
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasdorneles committed Jul 23, 2023
1 parent 2f055f6 commit 73bd532
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ unit-test:

.PHONY: test-snapshot-update
test-snapshot-update:
$(run) pytest --cov-report term-missing --cov=textual tests/ -vv --snapshot-update
$(run) pytest --cov-report term-missing --cov=textual tests/ -vv --snapshot-update $(PYTEST_ARGS)

.PHONY: typecheck
typecheck:
Expand Down
24 changes: 24 additions & 0 deletions docs/examples/widgets/digit_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from textual.app import App, ComposeResult
from textual.widgets import Static
from textual.widgets import DigitDisplay


class MyApp(App):
BINDINGS = []

def compose(self) -> ComposeResult:
yield Static("Digits: 0123456789")
yield DigitDisplay("0123456789")

punctuation=" .+,XYZ^*/-="
yield Static("Punctuation: " + punctuation)
yield DigitDisplay(punctuation)

equation = "x = y^2 + 3.14159*y + 10"
yield Static("Equation: " + equation)
yield DigitDisplay(equation)


if __name__ == "__main__":
app = MyApp()
app.run()
2 changes: 2 additions & 0 deletions src/textual/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from ._tooltip import Tooltip
from ._tree import Tree
from ._welcome import Welcome
from ._digit_display import DigitDisplay


__all__ = [
Expand Down Expand Up @@ -76,6 +77,7 @@
"Tooltip",
"Tree",
"Welcome",
"DigitDisplay",
]

_WIDGETS_LAZY_LOADING_CACHE: dict[str, type[Widget]] = {}
Expand Down
269 changes: 269 additions & 0 deletions src/textual/widgets/_digit_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
from textual.app import ComposeResult
from textual.reactive import reactive
from textual.widget import Widget
from textual.widgets import Static


_character_map: dict[str, str] = {}

# in the mappings below, we use underscores to make spaces more visible,
# we will strip them out later
_character_map[
"0"
] = """
┏━┓
┃╱┃
┗━┛
"""

_character_map[
"1"
] = """
·┓·
┃·
╺┻╸
"""

_character_map[
"2"
] = """
╺━┓
┏━┛
┗━╸
"""

_character_map[
"3"
] = """
╺━┓
━┫
╺━┛
"""

_character_map[
"4"
] = """
╻ ╻
┗━┫
"""

_character_map[
"5"
] = """
┏━╸
┗━┓
╺━┛
"""

_character_map[
"6"
] = """
┏━╸
┣━┓
┗━┛
"""

_character_map[
"7"
] = """
╺━┓
╹·
"""

_character_map[
"8"
] = """
┏━┓
┣━┫
┗━┛
"""

_character_map[
"9"
] = """
┏━┓
┗━┫
╺━┛
"""

_character_map[
" "
] = """
···
···
···
"""


_character_map[
"X"
] = """
╻ ╻
·╋·
╹ ╹
"""

_character_map[
"Y"
] = """
╻ ╻
·┳·
·╹·
"""

_character_map[
"Z"
] = """
╺━┓
·▞·
┗━╸
"""


_character_map[
"."
] = """
··
··
·•
"""


_character_map[
","
] = """
··
··
·▞
"""

_character_map[
"+"
] = """
···
╺╋╸
···
"""

_character_map[
"-"
] = """
···
╺━╸
···
"""

_character_map[
"="
] = """
···
╺━·
╺━·
"""

_character_map[
"*"
] = """
···
·✱·
···
"""


_character_map[
"/"
] = """
··╻
·▞·
╹··
"""

_character_map[
"^"
] = """
·╻·
▝·▘
···
"""


_VIRTUAL_SPACE = "·"

# here we strip spaces and replace virtual spaces with spaces
_character_map = {
k: v.strip().replace(_VIRTUAL_SPACE, " ") for k, v in _character_map.items()
}


class SingleDigitDisplay(Static):
digit = reactive(" ", layout=True)

DEFAULT_CSS = """
SingleDigitDisplay {
height: 3;
min-width: 2;
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(_character_map[digit.upper()])


class DigitDisplay(Widget):
"""A widget to display digits and basic arithmetic operators using Unicode blocks."""

digits = reactive("", layout=True)

DEFAULT_CSS = """
DigitDisplay {
layout: horizontal;
height: 3;
}
"""

def __init__(self, initial_value="", **kwargs):
super().__init__(**kwargs)
self._displays = [SingleDigitDisplay(d) for d in initial_value]
self.digits = initial_value

def compose(self) -> ComposeResult:
for widget in self._displays:
yield widget

def _add_digit_widget(self, digit: str) -> None:
new_widget = SingleDigitDisplay(digit)
self._displays.append(new_widget)
self.mount(new_widget)

def watch_digits(self, digits: str) -> None:
"""
Called when the digits attribute changes.
Here we update the display widgets to match the input digits.
"""
diff_digits_len = len(digits) - len(self._displays)

# Here we add or remove widgets to match the number of digits
if diff_digits_len > 0:
start = len(self._displays)
for i in range(diff_digits_len):
self._add_digit_widget(digits[start + i])
elif diff_digits_len < 0:
for display in self._displays[diff_digits_len:]:
self._displays.remove(display)
display.remove()

# At this point, the number of widgets matches the number of digits, and we can
# update the contents of the widgets that might need it
for i, d in enumerate(self.digits):
if self._displays[i].digit != d:
self._displays[i].digit = d
Loading

0 comments on commit 73bd532

Please sign in to comment.