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:
@@ -0,0 +1,24 @@
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="Tray nur auf Windows")
|
||||
class TestCreateIcon:
|
||||
def test_returns_image_for_each_state(self):
|
||||
from PIL import Image
|
||||
from whisper_local.tray._tray import AppState
|
||||
from whisper_local.tray._icon import create_icon
|
||||
|
||||
for state in AppState:
|
||||
img = create_icon(state)
|
||||
assert isinstance(img, Image.Image)
|
||||
assert img.size == (64, 64)
|
||||
assert img.mode == "RGBA"
|
||||
|
||||
def test_different_states_have_different_colors(self):
|
||||
from whisper_local.tray._tray import AppState
|
||||
from whisper_local.tray._icon import create_icon
|
||||
|
||||
waiting = create_icon(AppState.WAITING)
|
||||
recording = create_icon(AppState.RECORDING)
|
||||
assert waiting.tobytes() != recording.tobytes()
|
||||
@@ -0,0 +1 @@
|
||||
"""Tray-Package — wird in späteren Tasks befüllt."""
|
||||
@@ -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
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user