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()