Files
textual/examples/calculator.py
2021-07-14 20:08:27 +01:00

193 lines
6.1 KiB
Python

from decimal import Decimal
from rich.align import Align
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
from rich.padding import Padding
from rich.text import Text
from textual.app import App
from textual import events
from textual.message import Message
from textual.reactive import Reactive
from textual.view import View
from textual.widget import Widget
from textual.widgets import Button
from textual.layouts.grid import GridLayout
try:
from pyfiglet import Figlet
except ImportError:
print("Please install pyfiglet to run this example")
import sys
sys.exit()
class FigletText:
"""A renderable to generate figlet text that adapts to fit the container."""
def __init__(self, text: str) -> None:
self.text = text
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
size = min(options.max_width / 2, options.max_height)
if size < 4:
yield Text(self.text, style="bold")
else:
if size < 7:
font_name = "mini"
elif size < 8:
font_name = "small"
elif size < 10:
font_name = "standard"
else:
font_name = "big"
font = Figlet(font=font_name, width=options.max_width)
yield Text(font.renderText(self.text).rstrip("\n"), style="bold")
class Numbers(Widget):
"""The digital display of the calculator."""
value: Reactive[str] = Reactive("0")
def render(self) -> RenderableType:
return Padding(
Align.right(FigletText(self.value), vertical="middle"),
(0, 1),
style="white on rgb(51,51,51)",
)
class CalculatorApp(App):
"""A working calculator app."""
async def on_load(self, event: events.Load) -> None:
"""Sent when the app starts, but before displaying anything."""
self.left = Decimal("0")
self.right = Decimal("0")
self.value = ""
self.operator = "+"
self.numbers = Numbers()
def make_button(text: str, style: str) -> Button:
"""Create a button with the given Figlet label."""
return Button(FigletText(text), style=style, name=text)
dark = "white on rgb(51,51,51)"
light = "black on rgb(165,165,165)"
yellow = "white on rgb(255,159,7)"
button_styles = {
"AC": light,
"C": light,
"+/-": light,
"%": light,
"/": yellow,
"X": yellow,
"-": yellow,
"+": yellow,
"=": yellow,
}
# Make all the buttons
self.buttons = {
name: make_button(name, button_styles.get(name, dark))
for name in "+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
}
self.zero = make_button("0", dark)
self.ac = make_button("AC", light)
self.c = make_button("C", light)
self.c.visible = False
async def on_startup(self, event: events.Startup) -> None:
"""Sent when the app has gone full screen."""
# Create the layout which defines where our widgets will go
layout = GridLayout(gap=(2, 1), gutter=1, align=("center", "center"))
await self.push_view(View(layout=layout))
# Create rows / columns / areas
layout.add_column("col", max_size=30, repeat=4)
layout.add_row("numbers", max_size=15)
layout.add_row("row", max_size=15, repeat=5)
layout.add_areas(
clear="col1,row1",
numbers="col1-start|col4-end,numbers",
zero="col1-start|col2-end,row5",
)
# Place out widgets in to the layout
layout.place(clear=self.c)
layout.place(
*self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero
)
async def message_button_pressed(self, message: Message) -> None:
"""A message sent by the button widget"""
assert isinstance(message.sender, Button)
button_name = message.sender.name
def do_math() -> bool:
operator = self.operator
right = self.right
if operator == "+":
self.left += right
elif operator == "-":
self.left -= right
elif operator == "/":
try:
self.left /= right
except ZeroDivisionError:
self.numbers.value = "Error"
return False
elif operator == "X":
self.left *= right
return True
if button_name.isdigit():
self.value = self.value.lstrip("0") + button_name
self.numbers.value = self.value
elif button_name == "+/-":
self.value = str(Decimal(self.value or "0") * -1)
self.numbers.value = self.value
elif button_name == "%":
self.value = str(Decimal(self.value or "0") / Decimal(100))
self.numbers.value = self.value
elif button_name == ".":
if "." not in self.value:
self.value += "."
self.numbers.value = self.value
elif button_name == "AC":
self.value = ""
self.left = self.right = Decimal(0)
self.operator = "+"
self.numbers.value = "0"
elif button_name == "C":
self.value = ""
self.numbers.value = "0"
elif button_name in ("+", "-", "/", "X"):
self.right = Decimal(self.value or "0")
if do_math():
self.numbers.value = str(self.left)
self.value = ""
self.operator = button_name
elif button_name == "=":
if self.value:
self.right = Decimal(self.value or "0")
if do_math():
self.numbers.value = str(self.left)
self.value = ""
show_ac = self.value in ("", "0") and self.numbers.value == "0"
self.c.visible = not show_ac
self.ac.visible = show_ac
CalculatorApp.run(title="Calculator Test")