arch: split ConfigRepository, extract polling, consolidate conversions, decouple protocol

- Value↔JSON: From impls on domain Value behind `json` feature, delete 4 duplicate converters
- ConfigRepository split into ConfigRepository (12), UserRepository (3), WidgetStateCache (2)
- polling orchestration moved from bootstrap to application::polling_service
- WidgetRenderer in client-domain owns scroll/cache, both clients use it
- network loop consolidated into client-application::run_connection_loop
- protocol crate drops domain dep, Wire↔Domain conversions move to adapters
This commit is contained in:
2026-06-19 18:12:50 +02:00
parent 1c854d127f
commit 7001b5e911
46 changed files with 1063 additions and 951 deletions

View File

@@ -0,0 +1,113 @@
use crate::{BoundingBox, DrawCommand, RenderEngine, ScrollState};
use domain::{DisplayHint, Value, WidgetError};
use std::collections::HashMap;
use std::time::Duration;
pub struct RenderUpdate {
pub bounds: BoundingBox,
pub commands: Vec<DrawCommand>,
}
struct WidgetCache {
hint: DisplayHint,
data: Vec<(String, Value)>,
error: Option<WidgetError>,
bounds: BoundingBox,
scroll: ScrollState,
}
pub struct RepaintRequest {
pub widget_id: u16,
pub bounds: BoundingBox,
pub display_hint: DisplayHint,
pub data: Vec<(String, Value)>,
pub error: Option<WidgetError>,
}
pub struct WidgetRenderer {
widgets: HashMap<u16, WidgetCache>,
}
impl Default for WidgetRenderer {
fn default() -> Self {
Self::new()
}
}
impl WidgetRenderer {
pub fn new() -> Self {
Self {
widgets: HashMap::new(),
}
}
pub fn apply_repaints(
&mut self,
engine: &RenderEngine,
repaints: Vec<RepaintRequest>,
) -> Vec<RenderUpdate> {
let mut updates = Vec::new();
for req in repaints {
let content_h = engine.content_height(
&req.display_hint,
&req.data,
req.bounds.width,
req.error.as_ref(),
);
let scroll = ScrollState::new(req.bounds.height, content_h);
let cmds = engine.render_widget(
&req.display_hint,
&req.data,
req.bounds,
scroll.offset(),
req.error.as_ref(),
);
updates.push(RenderUpdate {
bounds: req.bounds,
commands: cmds,
});
self.widgets.insert(
req.widget_id,
WidgetCache {
hint: req.display_hint,
data: req.data,
error: req.error,
bounds: req.bounds,
scroll,
},
);
}
updates
}
pub fn tick_scroll(&mut self, engine: &RenderEngine, elapsed: Duration) -> Vec<RenderUpdate> {
let mut updates = Vec::new();
for cache in self.widgets.values_mut() {
if cache.scroll.tick(elapsed) {
let cmds = engine.render_widget(
&cache.hint,
&cache.data,
cache.bounds,
cache.scroll.offset(),
cache.error.as_ref(),
);
updates.push(RenderUpdate {
bounds: cache.bounds,
commands: cmds,
});
}
}
updates
}
pub fn has_active_scrollers(&self) -> bool {
self.widgets.values().any(|c| c.scroll.is_active())
}
pub fn clear(&mut self) {
self.widgets.clear();
}
}