diff --git a/Cargo.lock b/Cargo.lock index ee50f1c..f0172c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3642,6 +3642,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "shturman-splash" +version = "0.0.0" +dependencies = [ + "anyhow", + "png 0.17.16", + "shturman-common", + "shturman-render", + "slint", + "tempfile", + "tracing", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" diff --git a/Cargo.toml b/Cargo.toml index 934cb86..5405c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/core/shturman-settings", "crates/core/shturman-power", "crates/apps/shturman-render", + "crates/apps/shturman-splash", "crates/apps/shturman-shell", "crates/tools/shturman-manifest-validator", ] diff --git a/crates/apps/shturman-splash/Cargo.toml b/crates/apps/shturman-splash/Cargo.toml new file mode 100644 index 0000000..1a7dd0b --- /dev/null +++ b/crates/apps/shturman-splash/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "shturman-splash" +version = "0.0.0" +edition.workspace = true +license.workspace = true + +[dependencies] +shturman-render = { path = "../shturman-render" } +shturman-common = { path = "../../shturman-common" } +anyhow.workspace = true +tracing.workspace = true +slint.workspace = true + +[dev-dependencies] +tempfile.workspace = true +png = "0.17" diff --git a/crates/apps/shturman-splash/src/lib.rs b/crates/apps/shturman-splash/src/lib.rs new file mode 100644 index 0000000..6bc1b56 --- /dev/null +++ b/crates/apps/shturman-splash/src/lib.rs @@ -0,0 +1,30 @@ +//! `shturman-splash` (lib) — Stage-0 splash-кадр (A05). Статичный брендовый кадр (без шины → «мгновенно»). +//! Визуальные токены — каркас (язык design-system — гейт v0.5). Спека v0.2 §6. + +use std::path::Path; + +slint::slint! { + export component SplashWindow inherits Window { + in property brand: "Штурман"; + width: 1024px; + height: 600px; + background: #0e1014; + Text { + text: root.brand; + font-size: 64px; + color: #e8eaed; + horizontal-alignment: center; + vertical-alignment: center; + } + } +} + +const W: u32 = 1024; +const H: u32 = 600; + +/// Headless software-render splash-кадра в PNG (без дисплея/композитора). +pub fn render_splash(path: &Path) -> anyhow::Result<()> { + shturman_render::render_to_png(|| Ok(SplashWindow::new()?), W, H, path)?; + tracing::info!(path = %path.display(), "splash записан (software-render)"); + Ok(()) +} diff --git a/crates/apps/shturman-splash/src/main.rs b/crates/apps/shturman-splash/src/main.rs new file mode 100644 index 0000000..d22d8d8 --- /dev/null +++ b/crates/apps/shturman-splash/src/main.rs @@ -0,0 +1,34 @@ +//! `shturman-splash` (bin) — Stage-0 splash. `--screenshot ` → headless PNG (VM/E2E); +//! без аргументов — интерактив (dev/HW; в v0 VM используется только screenshot-режим). + +use shturman_splash::render_splash; +use std::path::PathBuf; + +fn main() -> anyhow::Result<()> { + shturman_common::init_tracing("shturman-splash"); + match parse_screenshot_arg() { + Some(path) => { + render_splash(&path)?; + println!("{}", path.display()); + } + None => { + // интерактив придёт с живым дисплеем (v0.5); в v0 VM splash — только screenshot. + anyhow::bail!("ожидался --screenshot (интерактивный splash — v0.5)"); + } + } + Ok(()) +} + +/// Разобрать `--screenshot ` / `--screenshot=` (без внешних зависимостей). +fn parse_screenshot_arg() -> Option { + let mut args = std::env::args().skip(1); + while let Some(a) = args.next() { + if a == "--screenshot" { + return args.next().map(PathBuf::from); + } + if let Some(p) = a.strip_prefix("--screenshot=") { + return Some(PathBuf::from(p)); + } + } + None +} diff --git a/crates/apps/shturman-splash/tests/screenshot.rs b/crates/apps/shturman-splash/tests/screenshot.rs new file mode 100644 index 0000000..9dcba28 --- /dev/null +++ b/crates/apps/shturman-splash/tests/screenshot.rs @@ -0,0 +1,29 @@ +//! Splash-кадр Stage 0: непустой брендовый PNG, тёмный фон (План 6 P6.2 / спека v0.2 §6). + +use shturman_splash::render_splash; + +#[test] +fn renders_dark_branded_splash() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("splash.png"); + render_splash(&path).expect("render_splash"); + + let dec = png::Decoder::new(std::fs::File::open(&path).unwrap()); + let mut r = dec.read_info().unwrap(); + assert_eq!((r.info().width, r.info().height), (1024, 600)); + let mut buf = vec![0u8; r.output_buffer_size()]; + let info = r.next_frame(&mut buf).unwrap(); + let px = &buf[..info.buffer_size()]; + // фон тёмный (угол) + не одноцветный (wordmark отрисован) + assert!( + px[0] < 64 && px[1] < 64 && px[2] < 64, + "splash фон не тёмный: {},{},{}", + px[0], + px[1], + px[2] + ); + assert!( + px.iter().any(|&b| b != px[0]), + "splash одноцветный — wordmark не отрисован" + ); +}