use std::{fmt, mem};
use bevy::log::error;
use bevy::prelude::{Bundle, Deref, DerefMut};
use cuicui_dsl::{BaseDsl, DslBundle, EntityCommands};
use crate::bundles::{Layout, LayoutBundle, RootBundle};
use crate::{Alignment, Distribution, Flow, LeafRule, Node, Oriented, Rule};
#[cfg(doc)]
use crate::{Container, Root, ScreenRoot};
pub trait IntoUiBundle<Marker> {
type Target: Bundle;
fn into_ui_bundle(self) -> Self::Target;
}
impl IntoUiBundle<()> for () {
type Target = ();
fn into_ui_bundle(self) {}
}
#[derive(Default, Debug)]
enum RootKind {
ScreenRoot,
Root,
#[default]
None,
}
#[derive(Default, Deref, DerefMut)]
pub struct LayoutDsl<T = BaseDsl> {
#[deref]
inner: T,
root: RootKind,
layout: Layout,
set_flow: bool,
ui_bundle: Option<Box<dyn FnOnce(&mut EntityCommands)>>,
layout_bundle: Option<LayoutBundle>,
}
impl<D: fmt::Debug> fmt::Debug for LayoutDsl<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let has_bundle = self.ui_bundle.is_some();
let ui_bundle = if has_bundle { "Some([FnOnce(EntityCommands)])" } else { "None" };
f.debug_struct("LayoutDsl")
.field("inner", &self.inner)
.field("root", &self.root)
.field("layout", &self.layout)
.field("set_flow", &self.set_flow)
.field("ui_bundle", &ui_bundle)
.field("layout_bundle", &self.layout_bundle)
.finish()
}
}
#[cfg_attr(
feature = "chirp",
cuicui_chirp::parse_dsl_impl(delegate = inner, type_parsers(Rule = args::from_str))
)]
impl<D: DslBundle> LayoutDsl<D> {
#[cfg_attr(feature = "chirp", parse_dsl(ignore))]
pub fn flow(&mut self, flow: Flow) {
self.set_flow = true;
self.layout.flow = flow;
}
pub fn column(&mut self) {
self.flow(Flow::Vertical);
}
pub fn row(&mut self) {
self.flow(Flow::Horizontal);
}
pub fn distrib_start(&mut self) {
self.layout.distrib = Distribution::Start;
}
pub fn distrib_end(&mut self) {
self.layout.distrib = Distribution::End;
}
pub fn fill_main_axis(&mut self) {
self.layout.distrib = Distribution::FillMain;
}
pub fn layout(&mut self, spec: &str) {
let correct_len = spec.len() == 5;
if !correct_len {
error!("'layout' method accpets '[v>]d[SEC]a[SEC]', got '{spec}'");
return;
};
let (Ok(flow), Ok(distrib), Ok(align)) =
(spec[0..1].parse(), spec[1..3].parse(), spec[3..5].parse())
else {
error!("'layout' method accpets '[v>]d[SEC]a[SEC]', got '{spec}'");
return;
};
self.set_flow = true;
self.layout.flow = flow;
self.layout.distrib = distrib;
self.layout.align = align;
}
pub fn margins(&mut self, main: f32, cross: f32) {
self.main_margin(main);
self.cross_margin(cross);
}
pub fn margin(&mut self, pixels: f32) {
self.main_margin(pixels);
self.cross_margin(pixels);
}
pub fn main_margin(&mut self, pixels: f32) {
self.layout.margin.main = pixels;
}
pub fn cross_margin(&mut self, pixels: f32) {
self.layout.margin.cross = pixels;
}
pub fn rules(&mut self, width: Rule, height: Rule) {
self.width(width);
self.height(height);
}
pub fn width(&mut self, rule: Rule) {
self.layout.size.width = Some(rule);
}
pub fn height(&mut self, rule: Rule) {
self.layout.size.height = Some(rule);
}
pub fn align_start(&mut self) {
self.layout.align = Alignment::Start;
}
pub fn align_end(&mut self) {
self.layout.align = Alignment::End;
}
pub fn screen_root(&mut self) {
self.root = RootKind::ScreenRoot;
}
pub fn root(&mut self) {
self.root = RootKind::Root;
}
pub fn empty_pct(&mut self, percent: u8) {
assert!(percent <= 100);
let node = Node::Axis(Oriented {
main: LeafRule::Parent(f32::from(percent) / 100.0),
cross: LeafRule::Fixed(0.0),
});
self.layout_bundle = Some(LayoutBundle { node, ..Default::default() });
}
pub fn empty_px(&mut self, pixels: u16) {
let node = Node::Axis(Oriented {
main: LeafRule::Fixed(f32::from(pixels)),
cross: LeafRule::Fixed(0.0),
});
self.layout_bundle = Some(LayoutBundle { node, ..Default::default() });
}
#[cfg_attr(feature = "chirp", parse_dsl(ignore))]
pub fn ui<M>(&mut self, ui_bundle: impl IntoUiBundle<M>) {
let ui_bundle = ui_bundle.into_ui_bundle();
self.ui_bundle = Some(Box::new(move |cmds| {
cmds.insert(ui_bundle);
}));
}
}
impl<D: DslBundle> DslBundle for LayoutDsl<D> {
fn insert(&mut self, cmds: &mut EntityCommands) {
if self.set_flow {
let container = self.layout.container();
let root_bundle = || RootBundle::new(self.layout);
let non_screen_root_bundle = || {
let r = RootBundle::new(self.layout);
(r.pos_rect, r.root)
};
match self.root {
RootKind::ScreenRoot => cmds.insert(root_bundle()),
RootKind::Root => cmds.insert(non_screen_root_bundle()),
RootKind::None => cmds.insert(LayoutBundle::node(container)),
};
} else {
let size = self.layout.size.map(LeafRule::from_rule);
cmds.insert(LayoutBundle::boxy(size));
}
if let Some(layout) = mem::take(&mut self.layout_bundle) {
cmds.insert(layout);
}
if let Some(ui_bundle_fn) = mem::take(&mut self.ui_bundle) {
let size = self.layout.size.map(LeafRule::from_rule);
cmds.insert(LayoutBundle::boxy(size));
ui_bundle_fn(cmds);
}
self.inner.insert(cmds);
}
}
#[must_use]
pub const fn px(pixels: u16) -> Rule {
Rule::Fixed(pixels as f32)
}
#[must_use]
pub fn pct(percent: u8) -> Rule {
assert!(percent <= 100);
Rule::Parent(f32::from(percent) / 100.0)
}
#[must_use]
pub fn child(ratio: f32) -> Rule {
assert!(ratio >= 1.0);
Rule::Children(ratio)
}