# app/gui/dashboard.py from __future__ import annotations import tkinter as tk from tkinter import ttk class DashboardView: def __init__(self, parent, sim, refresh_ms: int = 250): self.sim = sim self.frame = ttk.LabelFrame(parent, text="Dashboard", padding=6) self.refresh_ms = refresh_ms cols = ("label", "value", "unit", "key", "module") self.tree = ttk.Treeview(self.frame, columns=cols, show="headings") for c, w in zip(cols, (160, 80, 40, 160, 80)): self.tree.heading(c, text=c.capitalize()) self.tree.column(c, width=w, anchor="w") self.tree.grid(row=0, column=0, sticky="nsew") sb_y = ttk.Scrollbar(self.frame, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=sb_y.set); sb_y.grid(row=0, column=1, sticky="ns") self.frame.columnconfigure(0, weight=1) self.frame.rowconfigure(0, weight=1) self._rows = {} self._tick() def _tick(self): snap = self.sim.v.dashboard_snapshot() specs = snap.get("specs", {}) values = snap.get("values", {}) # sort by priority then label ordered = sorted(specs.values(), key=lambda s: (s.get("priority", 100), s.get("label", s["key"]))) seen_keys = set() for spec in ordered: k = spec["key"]; seen_keys.add(k) label = spec.get("label", k) unit = spec.get("unit", "") or "" fmt = spec.get("fmt") src = spec.get("source", "") val = values.get(k, "") if fmt and isinstance(val, (int, float)): try: val = format(val, fmt) except Exception: pass row_id = self._rows.get(k) row_vals = (label, val, unit, k, src) if row_id is None: row_id = self.tree.insert("", "end", values=row_vals) self._rows[k] = row_id else: self.tree.item(row_id, values=row_vals) # delete rows that disappeared for k, rid in list(self._rows.items()): if k not in seen_keys: try: self.tree.delete(rid) except Exception: pass self._rows.pop(k, None) try: self.frame.after(self.refresh_ms, self._tick) except tk.TclError: pass