spinner.py 4.3 KB

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