From 54c9dbdf4b3e0c26e445aabdb09df77e10a485d4 Mon Sep 17 00:00:00 2001 From: Daylin Morgan Date: Wed, 8 Mar 2023 02:49:53 -0600 Subject: [PATCH] docs: add textual/stopwatch example --- examples/stopwatch.py | 141 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 examples/stopwatch.py diff --git a/examples/stopwatch.py b/examples/stopwatch.py new file mode 100644 index 0000000..d37f32e --- /dev/null +++ b/examples/stopwatch.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""A tui stopwatch built w/textual adapted from their tutorial: +https://github.com/Textualize/textual/tree/main/docs/examples/tutorial +""" + +__import__("viv").activate("textual") + +from time import monotonic + +from textual.app import App, ComposeResult +from textual.containers import Container +from textual.reactive import reactive +from textual.widgets import Button, Footer, Header, Static + + +class TimeDisplay(Static): + """A widget to display elapsed time.""" + + start_time = reactive(monotonic) + time = reactive(0.0) + total = reactive(0.0) + + def on_mount(self) -> None: + """Event handler called when widget is added to the app.""" + self.update_timer = self.set_interval(1 / 60, self.update_time, pause=True) + + def update_time(self) -> None: + """Method to update time to current.""" + self.time = self.total + (monotonic() - self.start_time) + + def watch_time(self, time: float) -> None: + """Called when the time attribute changes.""" + minutes, seconds = divmod(time, 60) + hours, minutes = divmod(minutes, 60) + self.update(f"{hours:02,.0f}:{minutes:02.0f}:{seconds:05.2f}") + + def start(self) -> None: + """Method to start (or resume) time updating.""" + self.start_time = monotonic() + self.update_timer.resume() + + def stop(self): + """Method to stop the time display updating.""" + self.update_timer.pause() + self.total += monotonic() - self.start_time + self.time = self.total + + def reset(self): + """Method to reset the time display to zero.""" + self.total = 0 + self.time = 0 + + +class Stopwatch(Static): + """A stopwatch widget.""" + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Event handler called when a button is pressed.""" + button_id = event.button.id + time_display = self.query_one(TimeDisplay) + if button_id == "start": + time_display.start() + self.add_class("started") + elif button_id == "stop": + time_display.stop() + self.remove_class("started") + elif button_id == "reset": + time_display.reset() + + def compose(self) -> ComposeResult: + """Create child widgets of a stopwatch.""" + yield Button("Start", id="start", variant="success") + yield Button("Stop", id="stop", variant="error") + yield Button("Reset", id="reset") + yield TimeDisplay() + + +class StopwatchApp(App): + """A Textual app to manage stopwatches.""" + + # CSS_PATH = "stopwatch.css" + DEFAULT_CSS = """ +Stopwatch { + layout: horizontal; + background: $boost; + height: 5; + min-width: 50; + margin: 1; + padding: 1; +} +TimeDisplay { + content-align: center middle; + text-opacity: 60%; + height: 3; +} +Button { width: 16; } +#start { dock: left; } +#stop { dock: left; display: none; } +#reset { dock: right; } +.started { + text-style: bold; + background: $success; + color: $text; +} +.started TimeDisplay { text-opacity: 100%; } +.started #start { display: none } +.started #stop { display: block } +.started #reset { visibility: hidden }""" + + BINDINGS = [ + ("d", "toggle_dark", "Toggle dark mode"), + ("a", "add_stopwatch", "Add"), + ("r", "remove_stopwatch", "Remove"), + ] + + def compose(self) -> ComposeResult: + """Called to add widgets to the app.""" + yield Header() + yield Footer() + yield Container(Stopwatch(), Stopwatch(), Stopwatch(), id="timers") + + def action_add_stopwatch(self) -> None: + """An action to add a timer.""" + new_stopwatch = Stopwatch() + self.query_one("#timers").mount(new_stopwatch) + new_stopwatch.scroll_visible() + + def action_remove_stopwatch(self) -> None: + """Called to remove a timer.""" + timers = self.query("Stopwatch") + if timers: + timers.last().remove() + + def action_toggle_dark(self) -> None: + """An action to toggle dark mode.""" + self.dark = not self.dark + + +if __name__ == "__main__": + app = StopwatchApp() + app.run()