feat: add AppState enum and programmatic tray icon generation

Implements Task 5 of the TDD plan: creates the tray package with
AppState enum (WAITING, RECORDING, TRANSCRIBING) and a create_icon()
function that generates colorized microphone icons via Pillow.

- whisper_local/tray/__init__.py: empty package marker
- whisper_local/tray/_tray.py: AppState enum
- whisper_local/tray/_icon.py: icon generation with state-specific colors
- tests/test_tray.py: comprehensive test coverage for icon creation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 21:07:48 +02:00
parent c21344dfc2
commit eb74e4787f
4 changed files with 84 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Tray-Package — wird in späteren Tasks befüllt."""
+50
View File
@@ -0,0 +1,50 @@
"""Programmatische Icon-Generierung via Pillow."""
from PIL import Image, ImageDraw
from whisper_local.tray._tray import AppState
_STATE_COLORS: dict[AppState, tuple[int, int, int]] = {
AppState.WAITING: (150, 150, 150),
AppState.RECORDING: (220, 50, 50),
AppState.TRANSCRIBING: (220, 180, 0),
}
def create_icon(state: AppState, size: int = 64) -> Image.Image:
"""Erzeugt ein Mikrofon-Icon in der Farbe des übergebenen Zustands."""
color = _STATE_COLORS[state]
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
cx = size // 2
bw = max(4, size // 6) # Hälfte der Körperbreite
top = size // 8
mid = size // 2
# Mikrofon-Körper (abgerundetes Rechteck)
draw.rounded_rectangle(
[cx - bw, top, cx + bw, mid + bw],
radius=bw,
fill=color,
)
# Bogen (Stativ-Bogen)
arc_r = size // 4
arc_top = mid
lw = max(2, size // 20)
draw.arc(
[cx - arc_r, arc_top, cx + arc_r, arc_top + arc_r],
start=0, end=180, fill=color, width=lw,
)
# Stiel
pole_top = arc_top + arc_r // 2
pole_bot = size - size // 8
draw.line([cx, pole_top, cx, pole_bot], fill=color, width=lw)
# Sockel
base = size // 5
draw.line([cx - base, pole_bot, cx + base, pole_bot], fill=color, width=lw)
return img
+9
View File
@@ -0,0 +1,9 @@
"""Tray-App und App-Zustände für whisper-local (Windows)."""
import enum
class AppState(enum.Enum):
WAITING = "waiting"
RECORDING = "recording"
TRANSCRIBING = "transcribing"