use std::collections::BTreeMap;
use std::fmt::Write as _;
use super::AnyStyleProperty;
use crate::ts_codegen::json_key;
pub(crate) fn render_app_style_augmentation<'a>(
properties: impl IntoIterator<Item = &'a dyn AnyStyleProperty>,
decls: &mut BTreeMap<String, String>,
) -> String {
let mut rows: Vec<(&str, String)> = properties
.into_iter()
.map(|p| {
p.ts_decls(decls);
(p.name(), p.ts_type())
})
.collect();
if rows.is_empty() {
return String::new();
}
rows.sort();
let mut out = String::from(
"/** The app's own style properties (`app.add_react_style`). Augments the\n\
\x20* `BevyStyle` interface in the `bevy-react` package, so `style` (and the\n\
\x20* hover/press/focus variants) type them. */\n\
declare module \"bevy-react\" {\n\
\x20 interface BevyStyle {\n",
);
for (name, ts) in &rows {
writeln!(out, " {}?: {ts};", json_key(name)).unwrap();
}
out.push_str(" }\n}\n\n");
out
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use super::*;
use crate::ts_codegen::type_idents;
const TYPE_MODULES: &[(&str, &str)] = &[
("Animatable", "../animated"),
("FilterChainValue", "../filters"),
("MorphFilterValue", "../filters"),
];
const DEFAULT_TYPE_MODULE: &str = "../jsx";
pub(crate) const CORE_STYLE_TS_PATH: &str = "js/src/generated/style.ts";
pub(crate) fn render_core_style_ts(properties: &[&dyn AnyStyleProperty]) -> String {
let mut decls = BTreeMap::new();
let rows: Vec<(&str, String)> = properties
.iter()
.map(|p| {
p.ts_decls(&mut decls);
(p.name(), p.ts_type())
})
.collect();
let mut imports: BTreeMap<&str, BTreeSet<String>> = BTreeMap::new();
for (_, ts) in &rows {
for ident in type_idents(ts) {
if decls.contains_key(&ident) {
continue;
}
let module = TYPE_MODULES
.iter()
.find(|(name, _)| *name == ident)
.map_or(DEFAULT_TYPE_MODULE, |(_, module)| module);
imports.entry(module).or_default().insert(ident);
}
}
let mut out = String::from(
"// @generated by bevy-react from the core style registry (`CORE_STYLES`) —\n\
// do not edit by hand. Regenerate with `npm run style:generate`.\n\n",
);
for (module, names) in &imports {
let names: Vec<&str> = names.iter().map(String::as_str).collect();
writeln!(
out,
"import type {{ {} }} from {module:?};",
names.join(", ")
)
.unwrap();
}
for decl in decls.values() {
write!(out, "\nexport {decl}\n").unwrap();
}
out.push_str(
"\n/** A CSS-like style object mapped onto `bevy_ui::Node` and its sibling\n\
\x20* visual components. Every field is optional; unset fields keep Bevy's\n\
\x20* defaults. An app's own style properties (`app.add_react_style`) augment\n\
\x20* this interface from its generated `bevy.ts`. */\n\
export interface BevyStyle {\n",
);
for (name, ts) in &rows {
writeln!(out, " {}?: {ts};", json_key(name)).unwrap();
}
out.push_str("}\n");
out
}
#[test]
fn core_style_ts_is_current() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(CORE_STYLE_TS_PATH);
let rendered = render_core_style_ts(super::super::props::CORE_STYLES);
if std::env::var_os("UPDATE_STYLE_TS").is_some() {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(&path, &rendered).unwrap();
return;
}
let committed = std::fs::read_to_string(&path).unwrap_or_default();
assert!(
committed == rendered,
"{CORE_STYLE_TS_PATH} is stale — regenerate with `npm run style:generate`"
);
}
#[test]
fn type_idents_skip_literals_and_keys() {
assert_eq!(
type_idents(r#""flexStart" | Animatable<Length> | { color?: Color; x: number }"#),
["Animatable", "Length", "Color"]
);
}
}