| import sys |
| import threading |
| import time |
|
|
|
|
| class _BaseAnimation: |
| """Base común para animaciones de terminal.""" |
|
|
| def __init__(self, message="Colmena"): |
| self.message = message |
| self._stop = threading.Event() |
| self._thread = None |
|
|
| def _clear(self, width=80): |
| if sys.stdout.isatty(): |
| sys.stdout.write("\r" + " " * width + "\r") |
| sys.stdout.flush() |
|
|
| def _show(self, text): |
| if sys.stdout.isatty(): |
| sys.stdout.write(f"\r{text}") |
| sys.stdout.flush() |
|
|
| def stop(self): |
| if self._thread is None: |
| return |
| self._stop.set() |
| self._thread.join(timeout=0.6) |
| self._clear() |
|
|
| def __enter__(self): |
| self.start() |
| return self |
|
|
| def __exit__(self, *args): |
| self.stop() |
|
|
|
|
| class HexLoader(_BaseAnimation): |
| """Abejita volando sobre un panal de hexágonos; llena uno por uno.""" |
|
|
| FRAMES = [ |
| "⬡ ⬡ ⬡ ⬡ ⬡ 🐝", |
| "⬢ ⬡ ⬡ ⬡ ⬡ 🐝", |
| "⬢ ⬢ ⬡ ⬡ ⬡ 🐝", |
| "⬢ ⬢ ⬢ ⬡ ⬡ 🐝", |
| "⬢ ⬢ ⬢ ⬢ ⬡ 🐝", |
| "⬢ ⬢ ⬢ ⬢ ⬢ 🐝", |
| "🐝 ⬢ ⬢ ⬢ ⬢ ⬢ ", |
| " 🐝 ⬢ ⬢ ⬢ ⬢ ", |
| " 🐝 ⬢ ⬢ ⬢ ", |
| " 🐝 ⬢ ⬢ ", |
| " 🐝 ⬢ ", |
| " 🐝 ", |
| ] |
|
|
| def start(self): |
| if not sys.stdout.isatty(): |
| return self |
|
|
| def _spin(): |
| i = 0 |
| while not self._stop.is_set(): |
| frame = self.FRAMES[i % len(self.FRAMES)] |
| self._show(f"{self.message} {frame}") |
| time.sleep(0.18) |
| i += 1 |
| self._clear() |
|
|
| self._thread = threading.Thread(target=_spin, daemon=True) |
| self._thread.start() |
| return self |
|
|
|
|
| class BeeSwarmLoader(_BaseAnimation): |
| """Enjambre de abejas zumbando alrededor de un hexágono.""" |
|
|
| FRAMES = [ |
| " ⬡ ", |
| " 🐝 ⬡ ", |
| " ⬡ ⬡ 🐝 ", |
| " ⬡ 🐝 ⬡ ", |
| " 🐝 ⬡ 🐝 ", |
| " ⬡ 🐝 ", |
| "🐝 ⬡ ", |
| " ⬡ 🐝 ", |
| ] |
|
|
| def start(self): |
| if not sys.stdout.isatty(): |
| return self |
|
|
| def _spin(): |
| i = 0 |
| while not self._stop.is_set(): |
| frame = self.FRAMES[i % len(self.FRAMES)] |
| self._show(f"{self.message} {frame}") |
| time.sleep(0.16) |
| i += 1 |
| self._clear() |
|
|
| self._thread = threading.Thread(target=_spin, daemon=True) |
| self._thread.start() |
| return self |
|
|
|
|
| class MessageReceiver(_BaseAnimation): |
| """Animación corta para cuando llega un mensaje / respuesta.""" |
|
|
| FRAMES = [ |
| "📡 ~ ~ ~ 🐝 ", |
| "📡 ~ ~ ~ 🐝 ", |
| "📡~ ~ ~ 🐝 ", |
| "✉️ 🐝 ", |
| "✉️ 🐝 ", |
| "✉️ 🐝 ", |
| "🍯 ⬢ ", |
| "🍯 ⬢ ", |
| "🍯 ⬢ ", |
| ] |
|
|
| def start(self): |
| if not sys.stdout.isatty(): |
| return self |
|
|
| def _run_once(): |
| for frame in self.FRAMES: |
| if self._stop.is_set(): |
| break |
| self._show(f"{self.message} {frame}") |
| time.sleep(0.18) |
| self._clear() |
|
|
| self._thread = threading.Thread(target=_run_once, daemon=True) |
| self._thread.start() |
| return self |
|
|
| def play_and_wait(self): |
| """Bloquea hasta terminar la animación (útil tras recibir respuesta).""" |
| self.start() |
| self._thread.join(timeout=4) |
| self._clear() |
|
|
|
|
| class SplashScreen: |
| """Pantalla de inicio estática con arte de panal + abejas.""" |
|
|
| ART = """ |
| 🍯 COLMENA 🍯 |
| ⬡ ⬡ ⬡ ⬡ ⬡ |
| ⬢ 🐝 ⬢ |
| ⬡ ⬡ ⬡ ⬡ ⬡ |
| ⬢ ⬢ ⬢ |
| ⬡ ⬡ ⬡ ⬡ ⬡ |
| ~~~ zumbando ~~~ |
| """.strip() |
|
|
| @classmethod |
| def show(cls): |
| if sys.stdout.isatty(): |
| print(cls.ART) |
|
|
|
|
| |
| BeeSpinner = HexLoader |
|
|