Skip to content

Using the library

Including the widget

textual-countdown provides just one widget: Countdown; this can be composed into your application like any other widget:

from textual.app import App, ComposeResult

from textual_countdown import Countdown


class CountdownApp(App[None]):
    def compose(self) -> ComposeResult:
        yield Countdown()


if __name__ == "__main__":
    CountdownApp().run()

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Starting a countdown

As you can see above, the Countdown widget doesn't do anything interesting to start with; it's just a dim line. To have the widget start to count down and show the user that something is happening you need to call the start method:

from textual.app import App, ComposeResult

from textual_countdown import Countdown


class CountdownApp(App[None]):
    def compose(self) -> ComposeResult:
        yield Countdown()

    def on_mount(self) -> None:
        self.query_one(Countdown).start(10)


if __name__ == "__main__":
    CountdownApp().run()

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Once the countdown starts, as you can see above in the tab that shows the result, the countdown bar will change colour to show how much time is left to go. Over time the highlighted portion of the bar will reduce:

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Knowing when the countdown starts and ends

To help react to a countdown starting and ending the Countdown widget posts two different messages: Started and Finished.

from textual import on
from textual.app import App, ComposeResult

from textual_countdown import Countdown


class CountdownApp(App[None]):
    def compose(self) -> ComposeResult:
        yield Countdown()

    def on_mount(self) -> None:
        self.query_one(Countdown).start(2)

    @on(Countdown.Started)
    def _react_to_start(self) -> None:
        self.notify("The countdown has begun!")

    @on(Countdown.Finished)
    def _react_to_finish(self) -> None:
        self.notify("The countdown has ended!")


if __name__ == "__main__":
    CountdownApp().run()

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The countdown has begun!

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The countdown has begun! The countdown has ended!

Cancelling a countdown

There are going to be times when you want to cancel a running countdown, this can be done with the cancel method. If called the countdown will stop, the display will revert back to the "not running" appearance, and a Cancelled message is posted.

from textual import on
from textual.app import App, ComposeResult

from textual_countdown import Countdown


class CountdownApp(App[None]):
    def compose(self) -> ComposeResult:
        yield Countdown()

    def on_mount(self) -> None:
        self.query_one(Countdown).start(3)
        self.set_timer(1, self.query_one(Countdown).cancel)

    @on(Countdown.Cancelled)
    def _react_to_cancel(self) -> None:
        self.notify("The countdown was cancelled!")


if __name__ == "__main__":
    CountdownApp().run()

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ The countdown was cancelled!

Styling the countdown widget

The background of the widget is styled using the usual Textual background style. Likewise, the "non-counting" portion of the bar is controlled with the usual Textual color style.

To style the time-remaining portion of the bar of a running countdown, use the countdown--remaining component class:

from textual.app import App, ComposeResult

from textual_countdown import Countdown


class CountdownApp(App[None]):
    CSS = """
    Countdown {
        background: red;
        color: blue;
        &> .countdown--remaining {
            color: yellow;
        }
    }
    """

    def compose(self) -> ComposeResult:
        yield Countdown()

    def on_mount(self) -> None:
        self.query_one(Countdown).start(3)


if __name__ == "__main__":
    CountdownApp().run()

CountdownApp ━━━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━