forked from Textualize/rich
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspinner.py
134 lines (113 loc) · 4.21 KB
/
spinner.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
from typing import cast, List, Optional, TYPE_CHECKING
from ._spinners import SPINNERS
from .measure import Measurement
from .table import Table
from .text import Text
if TYPE_CHECKING:
from .console import Console, ConsoleOptions, RenderResult, RenderableType
from .style import StyleType
class Spinner:
def __init__(
self,
name: str,
text: "RenderableType" = "",
*,
style: Optional["StyleType"] = None,
speed: float = 1.0,
) -> None:
"""A spinner animation.
Args:
name (str): Name of spinner (run python -m rich.spinner).
text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
style (StyleType, optional): Style for spinner animation. Defaults to None.
speed (float, optional): Speed factor for animation. Defaults to 1.0.
Raises:
KeyError: If name isn't one of the supported spinner animations.
"""
try:
spinner = SPINNERS[name]
except KeyError:
raise KeyError(f"no spinner called {name!r}")
self.text = Text.from_markup(text) if isinstance(text, str) else text
self.frames = cast(List[str], spinner["frames"])[:]
self.interval = cast(float, spinner["interval"])
self.start_time: Optional[float] = None
self.style = style
self.speed = speed
self.frame_no_offset: float = 0.0
self._update_speed = 0.0
def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":
yield self.render(console.get_time())
def __rich_measure__(
self, console: "Console", options: "ConsoleOptions"
) -> Measurement:
text = self.render(0)
return Measurement.get(console, options, text)
def render(self, time: float) -> "RenderableType":
"""Render the spinner for a given time.
Args:
time (float): Time in seconds.
Returns:
RenderableType: A renderable containing animation frame.
"""
if self.start_time is None:
self.start_time = time
frame_no = ((time - self.start_time) * self.speed) / (
self.interval / 1000.0
) + self.frame_no_offset
frame = Text(
self.frames[int(frame_no) % len(self.frames)], style=self.style or ""
)
if self._update_speed:
self.frame_no_offset = frame_no
self.start_time = time
self.speed = self._update_speed
self._update_speed = 0.0
if not self.text:
return frame
elif isinstance(self.text, (str, Text)):
return Text.assemble(frame, " ", self.text)
else:
table = Table.grid(padding=1)
table.add_row(frame, self.text)
return table
def update(
self,
*,
text: "RenderableType" = "",
style: Optional["StyleType"] = None,
speed: Optional[float] = None,
) -> None:
"""Updates attributes of a spinner after it has been started.
Args:
text (RenderableType, optional): A renderable to display at the right of the spinner (str or Text typically). Defaults to "".
style (StyleType, optional): Style for spinner animation. Defaults to None.
speed (float, optional): Speed factor for animation. Defaults to None.
"""
if text:
self.text = Text.from_markup(text) if isinstance(text, str) else text
if style:
self.style = style
if speed:
self._update_speed = speed
if __name__ == "__main__": # pragma: no cover
from time import sleep
from .columns import Columns
from .panel import Panel
from .live import Live
all_spinners = Columns(
[
Spinner(spinner_name, text=Text(repr(spinner_name), style="green"))
for spinner_name in sorted(SPINNERS.keys())
],
column_first=True,
expand=True,
)
with Live(
Panel(all_spinners, title="Spinners", border_style="blue"),
refresh_per_second=20,
) as live:
while True:
sleep(0.1)