use yog_ids::yui_attr as at;
use yog_ui::layout::Align;
use super::anchor::Anchor;
use super::values::{boolean, number, pick, str_consts};
use super::widget::Gen;
use super::{Element, UI_USE, parse_xml};
use crate::rust_code::{f32_lit, fill};
use yog_data::Diagnostic;
pub(crate) struct Frame {
pub(crate) width: f32,
pub(crate) height: f32,
pub(crate) anchor: &'static str,
pub(crate) modal: bool,
pub(crate) pause: bool,
pub(crate) live: bool,
}
pub(crate) fn screen_frame(root: &Element) -> Result<Frame, Diagnostic> {
let size = |key: &str, default: f32| {
root.attr(key)
.map(|v| number(root, key, v))
.transpose()
.map(|v| v.unwrap_or(default))
};
let flag = |key: &str, default: bool| {
root.attr(key)
.map(|v| boolean(root, key, v))
.transpose()
.map(|v| v.unwrap_or(default))
};
let words = Anchor::words();
let anchor = pick(
root,
at::ANCHOR,
root.attr(at::ANCHOR)
.unwrap_or(yog_ids::screen_anchor::CENTER),
&words,
)?;
Ok(Frame {
width: size(at::WIDTH, yog_ui::widget::SCREEN_W)?,
height: size(at::HEIGHT, yog_ui::widget::SCREEN_H)?,
anchor,
modal: flag(at::MODAL, true)?,
pause: flag(at::PAUSE, false)?,
live: flag(at::LIVE, false)?,
})
}
pub(crate) fn screen_theme(root: &Element) -> Result<Option<yog_ui::theme::Theme>, Diagnostic> {
let Some(id) = root
.attr(yog_ids::yui_attr::THEME)
.map(str::trim)
.filter(|t| !t.is_empty())
else {
return Ok(None);
};
yog_ui::theme::Theme::builtin(id).map(Some).ok_or_else(|| {
Diagnostic::from(format!(
"<Screen> theme=\"{id}\": no such theme (built in: {})",
yog_ui::theme::Theme::BUILTIN.join(", ")
))
.at_line(root.line)
})
}
pub(crate) fn apply_theme(el: &mut Element, theme: &yog_ui::theme::Theme) {
theme.apply(&el.name, &mut el.attrs);
for c in &mut el.children {
apply_theme(c, theme);
}
}
#[cfg(test)]
pub fn generate(src: &str, name: &str) -> Result<String, Diagnostic> {
generate_for(src, name, None).map(|(code, _)| code)
}
pub fn generate_for(
src: &str,
name: &str,
mod_id: Option<&str>,
) -> Result<(String, Vec<Diagnostic>), Diagnostic> {
generate_with(src, name, mod_id, &[])
}
pub fn generate_with(
src: &str,
name: &str,
mod_id: Option<&str>,
layouts: &[yog_formats::book::layout::RecipeLayout],
) -> Result<(String, Vec<Diagnostic>), Diagnostic> {
let mut root = parse_xml(src)?;
if crate::book_page::is_book_ui(&root) {
return Ok((String::new(), Vec::new()));
}
crate::recipe_area::expand(&mut root, layouts, mod_id.unwrap_or(yog_ids::ns::MINECRAFT))?;
crate::yui_grid::normalize(&mut root)?;
if root.name == yog_ids::yui::COMPONENT {
return crate::yui_component::generate(&root, name, mod_id);
}
if root.attr(at::X_VIEW_MODEL).is_some() {
return crate::yui_vm::generate(&root, name, mod_id);
}
if let Some(theme) = screen_theme(&root)? {
apply_theme(&mut root, &theme);
}
Ok((screen_code(&root, name, mod_id)?, Vec::new()))
}
fn place(anchor: Anchor) -> String {
let at = |align: Align, screen: &str, size: &str| match align {
Align::Start => f32_lit(0.0),
Align::Center => format!("(({screen} - {size}) / 2.0).trunc()"),
Align::End => format!("{screen} - {size}"),
};
match anchor {
Anchor::Fill => {
let whole = f32_lit(WHOLE);
format!("content.w(Units::Percent({whole})).h(Units::Percent({whole})).at(0.0, 0.0)")
}
Anchor::At(h, v) => format!(
"content.at({}, {})",
at(h, "screen_w", "WIDTH"),
at(v, "screen_h", "HEIGHT")
),
}
}
const WHOLE: f32 = 100.0;
const TEMPLATE: &str = include_str!("screen.g.rs.in");
fn screen_code(root: &Element, name: &str, mod_id: Option<&str>) -> Result<String, Diagnostic> {
if root.name != yog_ids::yui::SCREEN {
return Err(Diagnostic::from(format!(
"the root element must be <Screen>, not <{}>",
root.name
))
.at_line(root.line));
}
let id = root
.attr(at::ID)
.filter(|s| !s.trim().is_empty())
.ok_or_else(|| {
Diagnostic::from("<Screen> needs an id (the UI id, e.g. \"mymod:main\")".to_string())
.at_line(root.line)
})?
.to_string();
let frame = screen_frame(root)?;
let mut codegen = Gen::new(mod_id);
let mut content_root = root.clone();
content_root.attrs.retain(|(k, _)| k != at::ID);
let body = codegen.widget(&content_root, false, 0)?;
let anchor = Anchor::parse(frame.anchor).unwrap_or(Anchor::At(Align::Center, Align::Center));
let values = [
("NAME", name.to_string()),
("ID_LIT", format!("{id:?}")),
("WIDTH", f32_lit(frame.width)),
("HEIGHT", f32_lit(frame.height)),
("EVENTS", str_consts(codegen.events.as_slice())),
("IDS", str_consts(codegen.ids.as_slice())),
("UI_USE", UI_USE.to_string()),
("BODY", body),
("ANCHOR", frame.anchor.to_string()),
("PLACE", place(anchor)),
("MODAL", frame.modal.to_string()),
("PAUSE", frame.pause.to_string()),
];
Ok(fill(TEMPLATE, &values))
}