use std::collections::HashMap;
use ratatui::{Terminal, backend::TestBackend};
use splashboard::config::{Config, DashboardConfig, SettingsConfig, WidgetConfig};
use splashboard::fetcher::{FetchContext, RegisteredFetcher, Registry as FetcherRegistry};
use splashboard::layout::{self, WidgetId};
use splashboard::payload::{Body, ImageData, Payload, TextBlockData, TextData};
use splashboard::render::{Registry as RenderRegistry, RenderSpec, Shape};
use splashboard::runtime;
use splashboard::samples;
use splashboard::templates::{self, Template};
use splashboard::theme::Theme;
fn main() {
let mut args = std::env::args().skip(1);
let requested = args.next();
let width: u16 = args.next().and_then(|s| s.parse().ok()).unwrap_or(100);
let height: u16 = args.next().and_then(|s| s.parse().ok()).unwrap_or(40);
let Some(name) = requested.as_deref() else {
list_templates();
return;
};
let Some(template) = templates::find(name) else {
eprintln!("unknown template: {name}\n");
list_templates();
std::process::exit(1);
};
render_template(template, width, height);
}
fn list_templates() {
println!("Available templates:");
for t in templates::TEMPLATES {
println!(" {:<28} {:?} {}", t.name, t.context, t.description);
}
println!("\nUsage: cargo run --example preview_template -- <name> [width] [height]");
}
fn render_template(template: &Template, width: u16, height: u16) {
let dashboard =
DashboardConfig::parse(template.body).expect("template TOML must parse (checked in tests)");
let config = Config::from_parts(SettingsConfig::default(), dashboard);
let fetchers = FetcherRegistry::with_builtins();
let renderers = RenderRegistry::with_builtins();
let shapes = runtime::derive_shapes(&config.widgets, &fetchers, &renderers);
let mut payloads: HashMap<WidgetId, Payload> = HashMap::new();
let mut specs: HashMap<WidgetId, RenderSpec> = HashMap::new();
for w in &config.widgets {
let shape = shapes
.get(&w.id)
.copied()
.or_else(|| fetchers.get(&w.fetcher).map(|f| f.default_shape()))
.unwrap_or(Shape::Text);
let fetcher = fetchers
.get(&w.fetcher)
.unwrap_or_else(|| panic!("unknown fetcher {}", w.fetcher));
payloads.insert(w.id.clone(), preview_payload(w, &fetcher, shape));
if let Some(spec) = w.render.clone() {
specs.insert(w.id.clone(), spec);
}
}
let layout = config.to_layout();
let theme = Theme::default();
let loading = HashMap::new();
let backend = TestBackend::new(width, height);
let mut terminal = Terminal::new(backend).unwrap();
let mut draw_once = || {
terminal
.draw(|f| {
layout::draw(
f,
f.area(),
&layout,
&payloads,
&specs,
&renderers,
&theme,
&config.general,
&loading,
);
})
.unwrap();
};
draw_once();
std::thread::sleep(std::time::Duration::from_millis(1600));
draw_once();
let buf = terminal.backend().buffer().clone();
let bar = "─".repeat(width as usize);
println!("{bar}");
println!("{} ({}×{})", template.name, width, height);
println!("{bar}");
for y in 0..buf.area.height {
let line: String = (0..buf.area.width)
.map(|x| buf.cell((x, y)).unwrap().symbol().to_string())
.collect();
println!("{}", line.trim_end());
}
println!("{bar}");
}
fn preview_payload(w: &WidgetConfig, fetcher: &RegisteredFetcher, shape: Shape) -> Payload {
if w.fetcher == "static" {
return wrap(static_body(w.format.as_deref().unwrap_or(""), shape));
}
if let Some(rt) = fetcher.as_realtime() {
let ctx = FetchContext {
widget_id: w.id.clone(),
format: w.format.clone(),
shape: Some(shape),
options: w.options.clone(),
..Default::default()
};
return rt.compute(&ctx);
}
wrap(
fetcher
.sample_body(shape)
.or_else(|| samples::canonical_sample(shape))
.unwrap_or_else(|| {
Body::Image(ImageData {
path: "/tmp/splashboard-preview-nonexistent.png".into(),
})
}),
)
}
fn wrap(body: Body) -> Payload {
Payload {
icon: None,
status: None,
format: None,
body,
}
}
fn static_body(format: &str, shape: Shape) -> Body {
match shape {
Shape::Text => Body::Text(TextData {
value: format.to_string(),
}),
_ => {
let lines = if format.is_empty() {
Vec::new()
} else {
format.split('\n').map(String::from).collect()
};
Body::TextBlock(TextBlockData { lines })
}
}
}