This commit is contained in:
Rui Carmo
2026-01-21 23:53:57 +00:00
commit a0e31d43fd
52 changed files with 6312 additions and 0 deletions
+33
View File
@@ -0,0 +1,33 @@
Screen {
overflow: auto;
}
#calculator {
layout: grid;
grid-size: 4;
grid-gutter: 1 2;
grid-columns: 1fr;
grid-rows: 2fr 1fr 1fr 1fr 1fr 1fr;
margin: 1 2;
min-height: 25;
min-width: 26;
height: 100%;
}
Button {
width: 100%;
height: 100%;
}
#numbers {
column-span: 4;
content-align: right middle;
padding: 0 1;
height: 100%;
background: $primary-lighten-2;
color: $text;
}
#number-0 {
column-span: 2;
}
+145
View File
@@ -0,0 +1,145 @@
from contextlib import suppress
from decimal import Decimal
from typing import ClassVar
from textual import events
from textual.app import App, ComposeResult
from textual.containers import Container
from textual.css.query import NoMatches
from textual.reactive import var
from textual.widgets import Button, Static
class CalculatorApp(App):
"""A working 'desktop' calculator."""
CSS_PATH = "calculator.css"
numbers = var("0")
show_ac = var(True)
left = var(Decimal("0"))
right = var(Decimal("0"))
value = var("")
operator = var("plus")
NAME_MAP: ClassVar = {
"asterisk": "multiply",
"slash": "divide",
"underscore": "plus-minus",
"full_stop": "point",
"plus_minus_sign": "plus-minus",
"percent_sign": "percent",
"equals_sign": "equals",
"minus": "minus",
"plus": "plus",
}
def watch_numbers(self, value: str) -> None:
"""Called when numbers is updated."""
# Update the Numbers widget
self.query_one("#numbers", Static).update(value)
def compute_show_ac(self) -> bool:
"""Compute switch to show AC or C button"""
return self.value in ("", "0") and self.numbers == "0"
def watch_show_ac(self, show_ac: bool) -> None:
"""Called when show_ac changes."""
self.query_one("#c").display = not show_ac
self.query_one("#ac").display = show_ac
def compose(self) -> ComposeResult:
"""Add our buttons."""
with Container(id="calculator"):
yield Static(id="numbers")
yield Button("AC", id="ac", variant="primary")
yield Button("C", id="c", variant="primary")
yield Button("+/-", id="plus-minus", variant="primary")
yield Button("%", id="percent", variant="primary")
yield Button("÷", id="divide", variant="warning")
yield Button("7", id="number-7")
yield Button("8", id="number-8")
yield Button("9", id="number-9")
yield Button("x", id="multiply", variant="warning")
yield Button("4", id="number-4")
yield Button("5", id="number-5")
yield Button("6", id="number-6")
yield Button("-", id="minus", variant="warning")
yield Button("1", id="number-1")
yield Button("2", id="number-2")
yield Button("3", id="number-3")
yield Button("+", id="plus", variant="warning")
yield Button("0", id="number-0")
yield Button(".", id="point")
yield Button("=", id="equals", variant="warning")
def on_key(self, event: events.Key) -> None:
"""Called when the user presses a key."""
def press(button_id: str) -> None:
with suppress(NoMatches):
self.query_one(f"#{button_id}", Button).press()
key = event.key
if key.isdecimal():
press(f"number-{key}")
elif key == "c":
press("c")
press("ac")
else:
button_id = self.NAME_MAP.get(key)
if button_id is not None:
press(self.NAME_MAP.get(key, key))
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Called when a button is pressed."""
button_id = event.button.id
assert button_id is not None
def do_math() -> None:
"""Does the math: LEFT OPERATOR RIGHT"""
try:
if self.operator == "plus":
self.left += self.right
elif self.operator == "minus":
self.left -= self.right
elif self.operator == "divide":
self.left /= self.right
elif self.operator == "multiply":
self.left *= self.right
self.numbers = str(self.left)
self.value = ""
except Exception:
self.numbers = "Error"
if button_id.startswith("number-"):
number = button_id.partition("-")[-1]
self.numbers = self.value = self.value.lstrip("0") + number
elif button_id == "plus-minus":
self.numbers = self.value = str(Decimal(self.value or "0") * -1)
elif button_id == "percent":
self.numbers = self.value = str(Decimal(self.value or "0") / Decimal(100))
elif button_id == "point":
if "." not in self.value:
self.numbers = self.value = (self.value or "0") + "."
elif button_id == "ac":
self.value = ""
self.left = self.right = Decimal(0)
self.operator = "plus"
self.numbers = "0"
elif button_id == "c":
self.value = ""
self.numbers = "0"
elif button_id in ("plus", "minus", "divide", "multiply"):
self.right = Decimal(self.value or "0")
do_math()
self.operator = button_id
elif button_id == "equals":
if self.value:
self.right = Decimal(self.value)
do_math()
if __name__ == "__main__":
CalculatorApp().run()
+74
View File
@@ -0,0 +1,74 @@
import io
from pathlib import Path
from textual import on
from textual.app import App, ComposeResult
from textual.events import DeliveryComplete
from textual.widgets import Button, Input, Label
class ScreenshotApp(App[None]):
def compose(self) -> ComposeResult:
yield Button("screenshot: no filename or mime", id="button-1")
yield Button("screenshot: screenshot.svg / open in browser", id="button-2")
yield Button("screenshot: screenshot.svg / download", id="button-3")
yield Button(
"screenshot: screenshot.svg / open in browser / plaintext mime",
id="button-4",
)
yield Label("Deliver custom file:")
yield Input(id="custom-path-input", placeholder="Path to file...")
@on(Button.Pressed, selector="#button-1")
def on_button_pressed(self) -> None:
screenshot_string = self.export_screenshot()
string_io = io.StringIO(screenshot_string)
self.deliver_text(string_io)
@on(Button.Pressed, selector="#button-2")
def on_button_pressed_2(self) -> None:
screenshot_string = self.export_screenshot()
string_io = io.StringIO(screenshot_string)
self.deliver_text(
string_io, save_filename="screenshot.svg", open_method="browser"
)
@on(Button.Pressed, selector="#button-3")
def on_button_pressed_3(self) -> None:
screenshot_string = self.export_screenshot()
string_io = io.StringIO(screenshot_string)
self.deliver_text(
string_io, save_filename="screenshot.svg", open_method="download"
)
@on(Button.Pressed, selector="#button-4")
def on_button_pressed_4(self) -> None:
screenshot_string = self.export_screenshot()
string_io = io.StringIO(screenshot_string)
self.deliver_text(
string_io,
save_filename="screenshot.svg",
open_method="browser",
mime_type="text/plain",
)
@on(DeliveryComplete)
def on_delivery_complete(self, event: DeliveryComplete) -> None:
self.notify(title="Download complete", message=event.key)
@on(Input.Submitted)
def on_input_submitted(self, event: Input.Submitted) -> None:
path = Path(event.value)
if path.exists():
self.deliver_binary(path)
else:
self.notify(
title="Invalid path",
message="The path does not exist.",
severity="error",
)
app = ScreenshotApp()
if __name__ == "__main__":
app.run()
+13
View File
@@ -0,0 +1,13 @@
import os
from textual.app import App, ComposeResult
from textual.widgets import Pretty
class TerminalEnv(App):
def compose(self) -> ComposeResult:
yield Pretty(dict(os.environ))
if __name__ == "__main__":
TerminalEnv().run()
+47
View File
@@ -0,0 +1,47 @@
[account]
[app.Calculator]
path = "./"
command = "python calculator.py"
[app.Easing]
slug = "easing"
path = "./"
command = "textual easing"
[app.Keys]
slug = "keys"
path = "./"
command = "textual keys"
[app.Borders]
slug = "borders"
path = "./"
command = "textual borders"
[app.Demo]
name = "Demo"
slug = "demo"
path = "./"
command = "python -m textual"
[terminal.Terminal]
name = "Terminal"
path = "./"
terminal = true
[app.OpenLink]
name = "Open Link"
slug = "open-link"
path = "./"
command = "python open_link.py"
[app.Download]
name = "Download"
slug = "download"
path = "./"
command = "python download.py"
+24
View File
@@ -0,0 +1,24 @@
from textual import on
from textual.app import App, ComposeResult
from textual.widgets import Button
class OpenLink(App[None]):
"""Demonstrates opening a URL in the same tab or a new tab."""
def compose(self) -> ComposeResult:
yield Button("Visit the Textual docs", id="open-link-same-tab")
yield Button("Visit the Textual docs in a new tab", id="open-link-new-tab")
@on(Button.Pressed)
def open_link(self, event: Button.Pressed) -> None:
"""Open the URL in the same tab or a new tab depending on which button was pressed."""
self.open_url(
"https://textual.textualize.io",
new_tab=event.button.id == "open-link-new-tab",
)
app = OpenLink()
if __name__ == "__main__":
app.run()