"""Minimal Display-Designs für ACeP 7-Farben. Inspiration von loiccoyle/tinyticker + inkflow-eink: - Weißer Hintergrund, schwarze Geometrie - Bold Typography, große Zahlen - SVG-style rendering (saubere Linien, einheitliche Strichstärke) - Dezente Akzentfarbe pro Widget-Kategorie """ from PIL import Image, ImageDraw from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT DISPLAY_W = 800 DISPLAY_H = 480 COLS, ROWS = 4, 4 CELL_W = DISPLAY_W // COLS CELL_H = DISPLAY_H // ROWS # Einheitliche Strichstärke STROKE = 2 # px für alle Linien SMALL_STROKE = 1 def render_design(items, widgets, fonts, design="minimal") -> Image.Image: fn = { "minimal": _render_minimal, "minimal_color": _render_minimal_color, "bold": _render_bold, "analog": _render_analog_clock, }.get(design, _render_minimal) return fn(items, widgets, fonts) # ============================================================================ # MINIMAL — das Referenz-Design # Schwarz/Weiß, bold, clean, viel Weißraum # ============================================================================ def _render_minimal(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) draw = ImageDraw.Draw(img) # Keine sichtbaren Grid-Linien — jedes Widget steht für sich for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue # Kein Rahmen, kein Hintergrund — reiner Content auf Weiß # Content füllt die ganze Zelle widget.render(draw, fonts, x + 12, y + 12, w - 24, h - 24) return img # ============================================================================ # MINIMAL COLOR — Minimal mit dezenter Akzentfarbe pro Kategorie # Kleiner Farbbalken oben links, sonst weiß + schwarz # ============================================================================ def _render_minimal_color(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) draw = ImageDraw.Draw(img) CATEGORY_COLORS = { "clock": (60, 80, 200), # blau "weather": (50, 160, 80), # grün "system": (200, 120, 30), # orange "hello": (180, 60, 60), # rot "spotify": (0, 160, 80), # spotify-grün "strava": (230, 110, 20), # strava-orange "gmail": (200, 60, 60), # gmail-rot "minimax": (140, 80, 240), # lila } for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue color = CATEGORY_COLORS.get(widget.name, FG) # Winziger Akzent-Strich oben (8px hoch, 3px breit) draw.rectangle((x + 12, y + 10, x + 16, y + 18), fill=color) # Widget-Name winzig darüber label = (widget.label or widget.name or "").upper() draw.text((x + 22, y + 8), label, font=fonts.get("11", fonts.get("default")), fill=(160, 160, 160)) # Content widget.render(draw, fonts, x + 12, y + 24, w - 24, h - 36) return img # ============================================================================ # BOLD — Riesen-Typography # Zahlen und Text füllen die Zelle komplett. # Kaum Weißraum, maximaler Informationsdichte. # ============================================================================ def _render_bold(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), WHITE) draw = ImageDraw.Draw(img) for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue # Weißer Hintergrund, schwarzer Rahmen (1px) draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=BLACK, width=1) # Content — groß widget.render(draw, fonts, x + 6, y + 6, w - 12, h - 12) return img # ============================================================================ # ANALOG CLOCK — Die Uhr als Mittelpunkt # Mittlere Zellen (2x2) zeigen die Uhr als analoges Ziffernblatt. # Kleine Zellen (1x1) zeigen minimalistische Icons. # Übrige Zellen: Daten-Widget mit Monospace-Text. # ============================================================================ def _render_analog_clock(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm white draw = ImageDraw.Draw(img) for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue is_2x2 = item.w >= 2 and item.h >= 2 is_clock = widget.name == "clock" and is_2x2 if is_clock: # 2x2 Clock → Analoges Ziffernblatt _draw_analog_clock(draw, x, y, w, h, fonts) else: # Andere Widgets: cleanes Panel draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) # Kleiner farbiger Punkt oben links color = (60, 100, 200) draw.ellipse((x + 8, y + 8, x + 14, y + 14), fill=color) # Label label = (widget.label or widget.name or "").upper()[:12] draw.text((x + 20, y + 6), label, font=fonts.get("12", fonts.get("default")), fill=(100, 100, 100)) # Separator draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=(200, 200, 200), width=1) # Content widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 34) return img def _draw_analog_clock(draw, x, y, w, h, fonts): """Zeichne ein analoges Ziffernblatt.""" import math cx, cy = x + w // 2, y + h // 2 r = min(w, h) // 2 - 16 # Ziffernblatt-Kreis draw.ellipse((cx - r, cy - r, cx + r, cy + r), fill=WHITE, outline=BLACK, width=STROKE) # Stunden-Markierungen (kleine punkte) for i in range(12): angle = (i / 12) * 2 * math.pi - math.pi / 2 mx = cx + int((r - 12) * math.cos(angle)) my = cy + int((r - 12) * math.sin(angle)) draw.ellipse((mx - 3, my - 3, mx + 3, my + 3), fill=BLACK) # Zeiger from datetime import datetime now = datetime.now() hour_angle = ((now.hour % 12) / 12) * 2 * math.pi - math.pi / 2 min_angle = (now.minute / 60) * 2 * math.pi - math.pi / 2 # Stundenzeiger hx = cx + int(r * 0.5 * math.cos(hour_angle)) hy = cy + int(r * 0.5 * math.sin(hour_angle)) draw.line((cx, cy, hx, hy), fill=BLACK, width=4) # Minutenzeiger mx = cx + int(r * 0.75 * math.cos(min_angle)) my = cy + int(r * 0.75 * math.sin(min_angle)) draw.line((cx, cy, mx, my), fill=BLACK, width=2) # Mittelpunkt draw.ellipse((cx - 4, cy - 4, cx + 4, cy + 4), fill=BLACK) # Digitale Zeit darunter draw.text((cx - 40, cy + r + 8), now.strftime("%H:%M"), font=fonts.get("20", fonts.get("default")), fill=BLACK) # ============================================================================ # Helper # ============================================================================ def _px(item): COLS, ROWS = 4, 4 CELL_W = DISPLAY_W // COLS CELL_H = DISPLAY_H // ROWS return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H