theme config, layout preview, container alignment

Server: ThemeConfig entity + CRUD (GET/PUT /theme), SQLite persistence,
ThemeUpdate broadcast to ESP32 on save and initial connect.
Client: render engine uses theme colors, full-screen redraw on theme change.
SPA: theme page with color pickers + presets, layout preview with TS port
of layout engine, justify/align controls on containers.
DisplayHint refactored to struct (kind + h_align + v_align).
This commit is contained in:
2026-06-19 03:26:18 +02:00
parent 81a4167382
commit fe59b68c37
46 changed files with 1276 additions and 118 deletions

View File

@@ -1,6 +1,6 @@
use crate::error::TcpServerError;
use domain::{BroadcastPort, DisplayHint, Layout, WidgetId, WidgetState};
use protocol::{ServerMessage, WidgetDescriptor, WireLayoutNode, encode};
use domain::{BroadcastPort, DisplayHint, Layout, ThemeConfig, WidgetId, WidgetState};
use protocol::{ServerMessage, WidgetDescriptor, WireColor, WireLayoutNode, WireTheme, encode};
use tokio::sync::broadcast;
pub struct TcpBroadcaster {
@@ -70,4 +70,26 @@ impl BroadcastPort for TcpBroadcaster {
let frame = encode(&msg).map_err(TcpServerError::Encode)?;
self.send_frame(frame)
}
async fn push_theme_update(&self, theme: &ThemeConfig) -> Result<(), Self::Error> {
let wire_theme = domain_theme_to_wire(theme);
let msg = ServerMessage::ThemeUpdate { theme: wire_theme };
let frame = encode(&msg).map_err(TcpServerError::Encode)?;
self.send_frame(frame)
}
}
pub(crate) fn domain_theme_to_wire(t: &ThemeConfig) -> WireTheme {
let c = |c: &domain::ThemeColor| WireColor {
r: c.r,
g: c.g,
b: c.b,
};
WireTheme {
primary: c(&t.primary),
secondary: c(&t.secondary),
accent: c(&t.accent),
text: c(&t.text),
background: c(&t.background),
}
}