"""Neue Display-Designs für ACeP 7-Farben e-Paper. Jedes Design definiert: - Hintergrund / Rahmen - Widget-Rahmen ( Frames) - content_fn: wie das Widget gerendert wird """ from PIL import Image, ImageDraw from palette import FG, BG, WHITE, BLACK, GREEN, BLUE, RED, YELLOW, ORANGE, OK, WARN, ALERT, INFO, ACCENT import math DISPLAY_W = 800 DISPLAY_H = 480 COLS, ROWS = 4, 4 CELL_W = DISPLAY_W // COLS CELL_H = DISPLAY_H // ROWS # ============================================================================ # DESIGN 5: WIREFRAME # Technisches Plan-Drawing. Blaue Linien, weiße Flächen, # schmale weiße Zellenrahmen. Blueprint-Ästhetik. # ============================================================================ def render_wireframe(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (240, 248, 255)) # aliceblue 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 pro Zelle draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), fill=WHITE) # Dünner Rahmen (blau) draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=BLUE, width=1) # Obere Akzent-Linie (breit) draw.rectangle((x + 2, y + 2, x + w - 3, y + 10), fill=BLUE) # Widget-Name oben links label = (widget.label or widget.name or "").upper() draw.text((x + 10, y + 14), label, font=fonts.get("14", fonts.get("default")), fill=BLUE) # Grid-Checkerboard im Hintergrund (subtle) for row in range(item.y, item.y + item.h): for col in range(item.x, item.x + item.w): if (row + col) % 2 == 0: cx = col * CELL_W + 4 cy = row * CELL_H + 4 cw = CELL_W - 6 ch = CELL_H - 6 draw.rectangle((cx, cy, cx + cw, cy + ch), fill=(240, 248, 255)) # Content widget.render(draw, fonts, x + 6, y + 32, w - 12, h - 38) return img # ============================================================================ # DESIGN 6: BAUHAUS # Geometrische Primärfarben. Jedes Widget bekommt eine der # 7 ACeP-Farben als Hintergrund-Balken links. Reine Formen. # ============================================================================ def render_bauhaus(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (245, 245, 240)) # warm gray draw = ImageDraw.Draw(img) PALETTE_7 = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 100, 200)] item_colors = {} for i, (item, widget) in enumerate(zip(items, widgets)): x, y, w, h = _px(item) if widget is None: continue color = PALETTE_7[i % len(PALETTE_7)] item_colors[item.id] = color # Großer farbiger Block links (30% width) block_w = w // 3 draw.rectangle((x + 2, y + 2, x + block_w - 2, y + h - 3), fill=color) # Rest weiß draw.rectangle((x + block_w, y + 2, x + w - 3, y + h - 3), fill=WHITE) # Rahmen draw.rectangle((x + 2, y + 2, x + w - 3, y + h - 3), outline=FG, width=2) # Widget-Name in der farbigen Fläche label = (widget.label or widget.name or "").upper() draw.text((x + 6, y + 8), label, font=fonts.get("14", fonts.get("default")), fill=WHITE) # Content (rechte Seite) widget.render(draw, fonts, x + block_w + 4, y + 4, w - block_w - 8, h - 8) return img # ============================================================================ # DESIGN 7: POSTER # Überdimensionierte Typography. Jedes Widget großflächig. # Widget-Hintergrund = monochrome Farbe aus Palette. # Fast nur Text, kein UI-Chrome. # ============================================================================ def render_poster(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), BLACK) draw = ImageDraw.Draw(img) for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue # Zufällige kräftige Farbe pro widget (basierend auf name hash) c = _hash_color(widget.name) draw.rectangle((x, y, x + w - 1, y + h - 1), fill=c) # Weißer Rahmen drum draw.rectangle((x, y, x + w - 1, y + h - 1), outline=WHITE, width=3) # Label oben Winzig label = (widget.label or widget.name or "").upper() draw.text((x + 8, y + 6), label[:18], font=fonts.get("12", fonts.get("default")), fill=WHITE) # Content — das Widget bekommt 90% der Fläche # Wir übergeben ein invertiertes draw-Objekt (fg=WHITE, bg=c) widget.render(draw, fonts, x + 6, y + 24, w - 12, h - 30) return img # ============================================================================ # DESIGN 8: RETRO LCD # Alte LCD/Uhren-Ästhetik. Schwarzer Hintergrund, Ziffern in # satten UnOld Green (LCD-STYLE). Rahmen mit abgerundeten Ecken, # die wie eingeritzte Rillen aussehen. # ============================================================================ def render_retro_lcd(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (0, 20, 0)) # dunkles LCD-grün draw = ImageDraw.Draw(img) LCD_GREEN = (0, 255, 120) LCD_DARK = (0, 60, 30) LCD_BG = (0, 30, 15) for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue # LCD-Zelle: dunkler als Hintergrund draw.rectangle((x + 3, y + 3, x + w - 4, y + h - 4), fill=LCD_DARK) # Rahmen mit Fake-3D (oben/links hell, unten/rechts dunkel) draw.rectangle((x + 1, y + 1, x + w - 2, y + h - 2), outline=LCD_GREEN, width=2) # Label in kleiner Schrift oben label = (widget.label or widget.name or "").upper() draw.text((x + 8, y + 6), label[:16], font=fonts.get("11", fonts.get("default")), fill=LCD_GREEN) # Separator-Linie draw.line((x + 8, y + 22, x + w - 8, y + 22), fill=LCD_GREEN, width=1) # Content widget.render(draw, fonts, x + 6, y + 26, w - 12, h - 32) return img # ============================================================================ # DESIGN 9: GLOSSY # Moderner Glossy-Style. Weiße Karten mit leichtem Farbverlauf oben, # ikonischer Rand, dezente Schatten durch mehrfache Ränder. # ============================================================================ def render_glossy(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (220, 228, 235)) # cool gray draw = ImageDraw.Draw(img) WIDGET_ACCENTS = { "clock": (80, 160, 255), "weather": (60, 200, 100), "system": (255, 160, 40), "hello": (200, 80, 80), "spotify": (0, 180, 80), "strava": (255, 120, 0), "gmail": (200, 60, 60), "minimax": (160, 80, 255), } for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue accent = WIDGET_ACCENTS.get(widget.name, (100, 100, 100)) # Karte weiß draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), fill=WHITE) # Glossy-Effekt: heller Balken oben draw.rectangle((x + 2, y + 2, x + w - 2, y + 14), fill=accent) # Weißer Gradient-Overlay oben for i in range(6): alpha = int(80 - i * 12) c = tuple(min(255, accent[j] + alpha) for j in range(3)) draw.line((x + 2, y + 2 + i, x + w - 2, y + 2 + i), fill=c, width=1) # Rahmeneffekt: 3-lagig für Schatten draw.rectangle((x + 2, y + 2, x + w - 2, y + h - 2), outline=(200, 210, 220), width=1) draw.rectangle((x + 3, y + 3, x + w - 3, y + h - 3), outline=(220, 228, 235), width=1) draw.rectangle((x + 4, y + 4, x + w - 4, y + h - 4), outline=(180, 190, 200), width=1) # Label in Accent-Farbe label = (widget.label or widget.name or "").title() draw.text((x + 10, y + 18), label, font=fonts.get("14", fonts.get("default")), fill=accent) # Separator draw.line((x + 8, y + 36, x + w - 8, y + 36), fill=(220, 220, 220), width=1) # Content widget.render(draw, fonts, x + 6, y + 40, w - 12, h - 46) return img # ============================================================================ # DESIGN 10: DATA BOARD # Kontrollraum-Ästhetik. Winzige Zellen, viele Daten. # Horizontale Balken, Tick-Marks, monochrome Palette. #=========================================================================== def render_data_board(items, widgets, fonts) -> Image.Image: img = Image.new("RGB", (DISPLAY_W, DISPLAY_H), (10, 10, 15)) draw = ImageDraw.Draw(img) for item, widget in zip(items, widgets): x, y, w, h = _px(item) if widget is None: continue # Weißer Rand draw.rectangle((x, y, x + w - 1, y + h - 1), fill=WHITE, width=2) # Header-Balken (invertiert) draw.rectangle((x, y, x + w - 1, y + 22), fill=WHITE) draw.text((x + 6, y + 4), (widget.label or widget.name or "").upper(), font=fonts.get("12", fonts.get("default")), fill=BLACK) # Kleine Tick-Marks oben (dekorativ) for tx in range(x + 10, x + w - 10, 20): draw.line((tx, y + 24, tx, y + 28), fill=(180, 180, 180), width=1) # Content widget.render(draw, fonts, x + 4, y + 30, w - 8, h - 34) return img # ============================================================================ # Helper # ============================================================================ def _px(item): return item.x * CELL_W, item.y * CELL_H, item.w * CELL_W, item.h * CELL_H def _hash_color(name: str): """Consistent color from name using simple hash.""" colors = [RED, GREEN, BLUE, YELLOW, ORANGE, (0, 200, 200), (200, 0, 200)] h = sum(ord(c) for c in name) return colors[h % len(colors)]