spinner.pyΒΆ

 1#!/usr/bin/env python
 2
 3"""Example: a spinner for every frame preset, started and stopped together.
 4
 5Space starts or stops the spinners, + and - make them faster or slower,
 6s shows the next frame of the stopped spinners, and q exits.
 7"""
 8
 9from __future__ import annotations
10
11import urwid
12
13# Bounds for + and -: a shorter interval redraws the screen almost continuously.
14MIN_INTERVAL = 0.0125
15MAX_INTERVAL = 1.6
16PALETTE = [("frame", "light cyan", ""), ("help", "black", "light gray")]
17
18
19def main() -> None:
20    """Build a row per frame preset and the key handling, and run the main loop."""
21    spinners = [urwid.Spinner(frames) for frames in urwid.Spinner.Frames]
22    rows = [
23        urwid.Columns([(urwid.PACK, urwid.AttrMap(spinner, "frame")), urwid.Text(frames.name)], dividechars=1)
24        for spinner, frames in zip(spinners, urwid.Spinner.Frames, strict=True)
25    ]
26    status = urwid.Text("")
27
28    def show_status() -> None:
29        state = "running" if spinners[0].running else "stopped"
30        status.set_text(
31            f"{state}, {spinners[0].interval:g} s per frame  space: start/stop  +/-: faster/slower  s: step  q: quit"
32        )
33
34    def start_all(_loop: urwid.MainLoop | None = None, _data: object = None) -> None:
35        for spinner in spinners:
36            spinner.start()
37        show_status()
38
39    def unhandled(key: str | tuple[str, int, int, int]) -> None:
40        if key == "q":
41            for spinner in spinners:
42                spinner.stop()
43            raise urwid.ExitMainLoop
44        if key == " ":
45            if spinners[0].running:
46                for spinner in spinners:
47                    spinner.stop()
48                show_status()
49            else:
50                start_all()
51        elif key in {"+", "-"}:
52            for spinner in spinners:
53                spinner.interval = min(MAX_INTERVAL, max(MIN_INTERVAL, spinner.interval * (0.5 if key == "+" else 2)))
54            show_status()
55        elif key == "s":
56            for spinner in spinners:
57                if not spinner.running:
58                    spinner.step()
59
60    body = urwid.Filler(urwid.Pile(rows), valign=urwid.TOP)
61    frame: urwid.Frame[urwid.Filler[urwid.Pile], None, urwid.AttrMap[urwid.Text]] = urwid.Frame(
62        body,
63        footer=urwid.AttrMap(status, "help"),
64    )
65    loop = urwid.MainLoop(frame, PALETTE, unhandled_input=unhandled)
66    # Started from an alarm, where the loop is running, so the spinners find it without loop=.
67    loop.set_alarm_in(0, start_all)
68    loop.run()
69
70
71if __name__ == "__main__":
72    main()