Files
k-frame/crates/adapters/tcp-server/src/broadcaster.rs
Gabriel Kaszewski fe59b68c37 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).
2026-06-19 03:26:18 +02:00

96 lines
2.7 KiB
Rust

use crate::error::TcpServerError;
use domain::{BroadcastPort, DisplayHint, Layout, ThemeConfig, WidgetId, WidgetState};
use protocol::{ServerMessage, WidgetDescriptor, WireColor, WireLayoutNode, WireTheme, encode};
use tokio::sync::broadcast;
pub struct TcpBroadcaster {
tx: broadcast::Sender<Vec<u8>>,
}
impl TcpBroadcaster {
pub fn new(capacity: usize) -> Self {
let (tx, _) = broadcast::channel(capacity);
Self { tx }
}
pub fn subscribe(&self) -> broadcast::Receiver<Vec<u8>> {
self.tx.subscribe()
}
fn send_frame(&self, frame: Vec<u8>) -> Result<(), TcpServerError> {
let _ = self.tx.send(frame);
Ok(())
}
}
impl BroadcastPort for TcpBroadcaster {
type Error = TcpServerError;
async fn push_screen_update(
&self,
layout: &Layout,
widgets: &[(WidgetId, DisplayHint, WidgetState)],
) -> Result<(), Self::Error> {
let wire_layout: WireLayoutNode = (&layout.root).into();
let wire_widgets: Vec<WidgetDescriptor> = widgets
.iter()
.map(|(id, hint, state)| WidgetDescriptor {
id: *id,
display_hint: hint.into(),
state: state.into(),
})
.collect();
let msg = ServerMessage::ScreenUpdate {
layout: wire_layout,
widgets: wire_widgets,
};
let frame = encode(&msg).map_err(TcpServerError::Encode)?;
self.send_frame(frame)
}
async fn push_data_update(
&self,
updates: &[(WidgetId, DisplayHint, WidgetState)],
) -> Result<(), Self::Error> {
let wire_widgets: Vec<WidgetDescriptor> = updates
.iter()
.map(|(id, hint, state)| WidgetDescriptor {
id: *id,
display_hint: hint.into(),
state: state.into(),
})
.collect();
let msg = ServerMessage::DataUpdate {
widgets: wire_widgets,
};
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),
}
}