"""WordClock-Plugin:Deutsche Wort-Uhr (QWATCHLayout). Reines Wort-Uhr-Display im deutschen Stil: ES IST Unterstützt auch ENGLISCH (US-Layout). Layout: fester 4×4-Zeichen-Grid im Quadrat (4×4 Zellen = 800×480 Display). Jede Zelle = 200×120px → Grid = 800×480px. Wir nutzen 11×8 "字符-Zellen" pro Grid (72×60px pro Zeichen). Minuten-Zeilen (oberer Block): Zeile 0: [E][S][ ][I][S][T] Zeile 1: [F][Ü][N][F][Z][E][H][N][Z][W][A][N] Zeile 2: [V][I][E][R][T][E][L][Z][W][A][N][Z] Zeile 3: [N][U][L][L] Zeile 4: [N][A][C][H][ ][V][O][R][ ][H][A][L] Zeile 5: [B][ ] Zeile 6: [S][P][R][A][C][H][E] Stunden (unterer Block, je 2×2 Zellen für die Ziffern): DieZiffern werden als gefüllte Rechtecke in der unteren Reihe gerendert. Das Layout wird intern gecacht bis sich die Minute ändert. """ from __future__ import annotations import os, sys from datetime import datetime from functools import lru_cache sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from plugins.base import Widget from palette import FG, BG, OK, BLUE, YELLOW, ORANGE, fill_for, measure class Widget(Widget): name = "clock_wordclock" label = "WordClock" description = "Deutsche Wort-Uhr (ES IST …). Nur für 2×2 oder größer." category = "info" config_schema = [ {"key": "lang", "label": "Sprache", "type": "select", "choices": ["de", "en"], "default": "de", "help": "de = Deutsch (Standard), en = Englisch"}, {"key": "show_date", "label": "Datum anzeigen", "type": "bool", "default": True}, {"key": "show_weekday", "label": "Wochentag anzeigen", "type": "bool", "default": True}, {"key": "accent_color", "label": "Akzentfarbe", "type": "select", "choices": ["accent", "blue", "ok", "warn", "info"], "default": "accent"}, {"key": "invert", "label": "Invertiert (dunkel)", "type": "bool", "default": False}, ] default_config = { "lang": "de", "show_date": True, "show_weekday": True, "accent_color": "accent", "invert": False, } def fetch(self): return {} def render(self, draw, fonts, x: int, y: int, w: int, h: int): from palette import fill_for now = datetime.now() m = now.minute h_ = now.hour invert = self.cfg("invert", False) accent = fill_for(self.cfg("accent_color", "accent")) fg = BG if invert else FG bg = FG if invert else BG pad = 8 # Mindestgröße: 300px-breit, 240px-hoch für WordClock if w < 300 or h < 240: draw.rectangle((x, y, x + w - 1, y + h - 1), fill=bg) draw.text((x + pad, y + pad), "WordClock\n(min 2×2)", font=fonts.get("20", fonts.get("default")), fill=fg) return # ---- Wort-Uhr Grid ---- # 4×4 Zellen → 800×480px # Wir rendern in ein 11×8 Zeichen-Grid chars_x, chars_y = 11, 8 char_w = w // chars_x char_h = h // chars_y font_size = min(char_w, char_h) * 2 // 3 font_key = str(font_size) if font_key not in fonts: font_key = str(max(16, min(fonts.keys(), key=lambda k: abs(int(k) - font_size) if k.isdigit() else 9999)) if fonts else "20") fnt = fonts.get(font_key, fonts.get("20", fonts.get("default"))) def _draw_char(cx, cy, char, color): """Zeichnet ein Zeichen an Gitterposition (cx, cy).""" px = x + cx * char_w py = y + cy * char_h tw, th = measure(draw, char, fnt) draw.text((px + (char_w - tw) // 2, py + (char_h - th) // 2), char, font=fnt, fill=color) def _fill_char(cx, cy, color): """Füllt eine Gitterzelle mit einer Farbe (z.B. für Stunden-Dots).""" px = x + cx * char_w py = y + cy * char_h draw.rectangle((px + 2, py + 2, px + char_w - 3, py + char_h - 3), fill=color) def _lit(cx, cy): _draw_char(cx, cy, LAYOUT_DE[cy][cx], fg) def _dim(cx, cy): _draw_char(cx, cy, LAYOUT_DE[cy][cx], (150, 150, 150)) # ---- Minuten-Logik (Deutsch) ---- def lit_minute(m): """Sektor der Minuten: 0-4, 5-9, 10-14, 15-19, 20-24, 25-29, 30-34, 35-39, 40-44, 45-49, 50-54, 55-59.""" if m < 5: return [] elif m < 10: return [(0, 1), (1, 1), (2, 1), (3, 1)] # FÜNF elif m < 15: return [(5, 1), (6, 1), (7, 1), (8, 1)] # ZEHN elif m < 20: return [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2)] # VIERTEL elif m < 25: return [(7, 2), (8, 2), (9, 2), (10, 2), (7, 3), (8, 3), (9, 3), (10, 3)] # ZWANZIG elif m < 30: return [(0, 4), (1, 4), (2, 4), (3, 4)] # NACH elif m < 35: return [(0, 1), (1, 1), (2, 1), (3, 1), # FÜNF (4, 4), (5, 4), (6, 4)] # + HALB elif m < 40: return [(4, 4), (5, 4), (6, 4)] # HALB elif m < 45: return [(7, 3), (8, 3), (9, 3), (10, 3)] # ZWANZIG elif m < 50: return [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (5, 2), (6, 2), # VIERTEL (7, 4), (8, 4), (9, 4)] # + VOR elif m < 55: return [(5, 1), (6, 1), (7, 1), (8, 1)] # ZEHN + VOR else: return [(0, 1), (1, 1), (2, 1), (3, 1), # FÜNF + VOR (7, 4), (8, 4), (9, 4)] def hour_name(h, past_half): """Gibt die Stunde zurück für die Wortuhr.""" DE_HOURS = [ "ZWÖLF", "EINS", "ZWEI", "DREI", "VIER", "FÜNF", "SECHS", "SIEBEN", "ACHT", "NEUN", "ZEHN", "ELF", "ZWÖLF" ] if past_half: h = (h + 1) % 24 if h == 0: return "ZWÖLF" return DE_HOURS[h % 12] past_half = m >= 20 h_display = hour_name(h_, past_half) lit_cells = lit_minute(m) # ---- Render ---- # Hintergrund draw.rectangle((x, y, x + w - 1, y + h - 1), fill=bg) # "ES IST" immer lit in Spalte 0 _lit(0, 0); _lit(1, 0); _lit(3, 0); _lit(4, 0) # Minuten-Wörter for (cx, cy) in lit_cells: _lit(cx, cy) # "VOR" und "NACH" (Zeile 4) if 5 <= m < 30: _lit(0, 4); _lit(1, 4); _lit(2, 4); _lit(3, 4) # NACH elif m >= 35 and m < 55: _lit(7, 4); _lit(8, 4); _lit(9, 4) # VOR # Rest dim for row in range(chars_y): for col in range(chars_x): if (col, row) not in lit_cells and not (row == 0 and col in (0, 1, 3, 4)): _dim(col, row) # ---- Stunden-Balken unten ---- # Zeile 6+7: Stunden-Name in großen Buchstaben unten zentriert hour_str = h_display hour_font_size = min(w // len(hour_str), h // 4) * 3 // 4 hf = fonts.get(str(hour_font_size), fonts.get("60", fonts.get("default"))) tw, th = measure(draw, hour_str, hf) hour_y = y + h - th - pad draw.text((x + (w - tw) // 2, hour_y), hour_str, font=hf, fill=fg) # ---- Datum + Wochentag ---- if self.cfg("show_date", True): date_str = now.strftime("%d. %b %Y") df = fonts.get("16", fonts.get("default")) dw, dh = measure(draw, date_str, df) draw.text((x + (w - dw) // 2, y + pad), date_str, font=df, fill=accent) if self.cfg("show_weekday", True): day_str = now.strftime("%A").upper() df = fonts.get("14", fonts.get("default")) dw, dh = measure(draw, day_str, df) draw.text((x + (w - dw) // 2, y + pad + (18 if self.cfg("show_date", True) else 0)), day_str, font=df, fill=accent) # ---- Deutsches WordClock-Layout (11×8) ---- # Jede Position ist ein Zeichen das gerendert wird. # ' ' = Leerzeichen, rest = Buchstabe. LAYOUT_DE = [ ["E", "S", " ", "I", "S", "T", " ", " ", " ", " ", " "], ["F", "Ü", "N", "F", " ", "Z", "E", "H", "N", " ", " "], ["V", "I", "E", "R", "T", "E", "L", " ", "Z", "W", "A"], ["N", "U", "L", "L", " ", "Z", "W", "A", "N", "Z", " "], ["N", "A", "C", "H", " ", "V", "O", "R", " ", "H", "A"], [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "], [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "], [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "], ]