#![cfg(all(not(feature = "mini"), not(target_arch = "wasm32")))]
use rust_widgets::theme::{theme_test_guard, AppearanceMode};
use rust_widgets::widget::census::{install_preset_appearances, CENSUS_RECT, CENSUS_TEXT};
use rust_widgets::widget::svg::render_widget_to_svg_on;
use rust_widgets::widget::{draw_bridge::draw_of, WidgetFactory};
use std::fs;
use std::path::Path;
const OUTPUT_DIR: &str = "snapshots/svg";
const GENERATED_MARKER: &str = "<!-- GENERATED by examples/export_control_svgs.rs -->";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = theme_test_guard();
install_preset_appearances();
let dir = Path::new(OUTPUT_DIR);
fs::create_dir_all(dir)?;
let factory = WidgetFactory::new_with_defaults();
let names = factory.widget_names();
let mut written = 0usize;
let mut failed: Vec<String> = Vec::new();
for name in &names {
for (appearance, suffix) in [(AppearanceMode::Dark, ""), (AppearanceMode::Light, ".light")]
{
rust_widgets::theme::global_theme_manager().set_appearance(appearance);
let Some(mut widget) = factory.create(name, CENSUS_RECT, CENSUS_TEXT) else {
failed
.push(format!("{name}: the registry publishes it but `create` returned None"));
continue;
};
rust_widgets::theme::apply_theme_to_widget(widget.as_mut());
let Some(drawable) = draw_of(widget.as_mut()) else {
failed.push(format!("{name}: the widget has no `Draw` implementation"));
continue;
};
let backdrop = rust_widgets::theme::global_theme_manager()
.current_theme()
.map(|active| active.colors.background)
.unwrap_or(rust_widgets::core::Color::WHITE);
let body = render_widget_to_svg_on(drawable, CENSUS_RECT, backdrop);
let document = decorate(&body, name, appearance);
fs::write(dir.join(format!("{name}{suffix}.svg")), document)?;
written += 1;
}
}
fs::write(dir.join("README.md"), index(&names))?;
println!("wrote {written} SVG snapshots for {} controls into {OUTPUT_DIR}/", names.len());
println!("checked={} skipped=0 failed={}", names.len(), failed.len());
if !failed.is_empty() {
for entry in &failed {
eprintln!(" {entry}");
}
return Err(format!("{} control(s) could not be exported", failed.len()).into());
}
Ok(())
}
fn decorate(body: &str, name: &str, appearance: AppearanceMode) -> String {
let label = match appearance {
AppearanceMode::Light => "light",
AppearanceMode::Dark => "dark",
};
format!("{GENERATED_MARKER}\n<!-- control: {name} | appearance: {label} -->\n{body}\n")
}
fn index(names: &[&'static str]) -> String {
let mut out = String::new();
out.push_str("<!-- GENERATED by examples/export_control_svgs.rs -->\n");
out.push_str("# Control SVG snapshots (BLUE20 layer 3)\n\n");
out.push_str(
"One SVG per control, named by its **canonical name**. Each control appears twice:\n\
`<name>.svg` is the dark appearance, `<name>.light.svg` the light one.\n\n",
);
out.push_str(
"## Why the pair matters\n\n\
Two files that are byte-identical mean the control renders the same in both\n\
appearances, so it does not respond to the theme. The files exist so that is\n\
visible to a person, not only to the `P3` assertion in\n\
`tools/check_control_rendering.sh`.\n\n",
);
out.push_str(
"## Regenerating\n\n\
```bash\n\
cargo run --no-default-features --features desktop --example export_control_svgs\n\
```\n\n\
`tools/check_svg_snapshots.sh` regenerates the set and requires it to match the\n\
committed files byte for byte, so **any** change to a control's drawing shows up as\n\
a diff. Do not hand-edit these files: a hand-edit is reverted by the next run and\n\
fails the gate in between.\n\n",
);
out.push_str(
"## Adding a control\n\n\
1. Register it in `src/widget/capability/registration.rs` (capability + constructor).\n\
2. Re-run the exporter above; the new control's two files and its index row are\n\
generated automatically — this list is derived from the registry, never typed.\n\
3. Run `bash tools/check_svg_snapshots.sh`.\n\n",
);
out.push_str(&format!("## Controls ({})\n\n", names.len()));
out.push_str("| # | Control | Dark | Light |\n|---|---|---|---|\n");
for (index, name) in names.iter().enumerate() {
out.push_str(&format!(
"| {} | `{name}` | `{name}.svg` | `{name}.light.svg` |\n",
index + 1
));
}
out
}