move example apps, add render_lines to scrollview

This commit is contained in:
Will McGugan
2022-06-08 16:19:17 +01:00
parent b9e1022f6e
commit 53693e6200
20 changed files with 45 additions and 1163 deletions

View File

@@ -1,216 +0,0 @@
"""
A Textual app to create a fully working calculator, modelled after MacOS Calculator.
"""
from decimal import Decimal
from rich.align import Align
from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
from rich.padding import Padding
from rich.style import Style
from rich.text import Text
from textual.app import App
from textual.reactive import Reactive
from textual.views import GridView
from textual.widget import Widget
from textual.widgets import Button
try:
from pyfiglet import Figlet
except ImportError:
print("Please install pyfiglet to run this example")
raise
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:
"""Build a Rich renderable to render the Figlet text."""
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("0")
def render(self) -> RenderableType:
"""Build a Rich renderable to render the calculator display."""
return Padding(
Align.right(FigletText(self.value), vertical="middle"),
(0, 1),
style="white on rgb(51,51,51)",
)
class Calculator(GridView):
"""A working calculator app."""
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,
}
display = Reactive("0")
show_ac = Reactive(True)
def watch_display(self, value: str) -> None:
"""Called when self.display is modified."""
# self.numbers is a widget that displays the calculator result
# Setting the attribute value changes the display
# This allows us to write self.display = "100" to update the display
self.numbers.value = value
def compute_show_ac(self) -> bool:
"""Compute show_ac reactive value."""
# Condition to show AC button over C
return self.value in ("", "0") and self.display == "0"
def watch_show_ac(self, show_ac: bool) -> None:
"""When the show_ac attribute change we need to update the buttons."""
# Show AC and hide C or vice versa
self.c.display = not show_ac
self.ac.display = show_ac
def on_mount(self) -> None:
"""Event when widget is first mounted (added to a parent view)."""
# Attributes to store the current calculation
self.left = Decimal("0")
self.right = Decimal("0")
self.value = ""
self.operator = "+"
# The calculator display
self.numbers = Numbers()
self.numbers.style_border = "bold"
def make_button(text: str, style: str) -> Button:
"""Create a button with the given Figlet label."""
return Button(FigletText(text), style=style, name=text)
# Make all the buttons
self.buttons = {
name: make_button(name, self.BUTTON_STYLES.get(name, self.DARK))
for name in "+/-,%,/,7,8,9,X,4,5,6,-,1,2,3,+,.,=".split(",")
}
# Buttons that have to be treated specially
self.zero = make_button("0", self.DARK)
self.ac = make_button("AC", self.LIGHT)
self.c = make_button("C", self.LIGHT)
self.c.display = False
# Set basic grid settings
self.grid.set_gap(2, 1)
self.grid.set_gutter(1)
self.grid.set_align("center", "center")
# Create rows / columns / areas
self.grid.add_column("col", max_size=30, repeat=4)
self.grid.add_row("numbers", max_size=15)
self.grid.add_row("row", max_size=15, repeat=5)
self.grid.add_areas(
clear="col1,row1",
numbers="col1-start|col4-end,numbers",
zero="col1-start|col2-end,row5",
)
# Place out widgets in to the layout
self.grid.place(clear=self.c)
self.grid.place(
*self.buttons.values(), clear=self.ac, numbers=self.numbers, zero=self.zero
)
def handle_button_pressed(self, message: ButtonPressed) -> None:
"""A message sent by the button widget"""
assert isinstance(message.sender, Button)
button_name = message.sender.name
def do_math() -> None:
"""Does the math: LEFT OPERATOR RIGHT"""
self.log(self.left, self.operator, self.right)
try:
if self.operator == "+":
self.left += self.right
elif self.operator == "-":
self.left -= self.right
elif self.operator == "/":
self.left /= self.right
elif self.operator == "X":
self.left *= self.right
self.display = str(self.left)
self.value = ""
self.log("=", self.left)
except Exception:
self.display = "Error"
if button_name.isdigit():
self.display = self.value = self.value.lstrip("0") + button_name
elif button_name == "+/-":
self.display = self.value = str(Decimal(self.value or "0") * -1)
elif button_name == "%":
self.display = self.value = str(Decimal(self.value or "0") / Decimal(100))
elif button_name == ".":
if "." not in self.value:
self.display = self.value = (self.value or "0") + "."
elif button_name == "AC":
self.value = ""
self.left = self.right = Decimal(0)
self.operator = "+"
self.display = "0"
elif button_name == "C":
self.value = ""
self.display = "0"
elif button_name in ("+", "-", "/", "X"):
self.right = Decimal(self.value or "0")
do_math()
self.operator = button_name
elif button_name == "=":
if self.value:
self.right = Decimal(self.value)
do_math()
class CalculatorApp(App):
"""The Calculator Application"""
async def on_mount(self) -> None:
"""Mount the calculator widget."""
await self.screen.dock(Calculator())
CalculatorApp.run(title="Calculator Test", log_path="textual.log")