use kui_core::calc::{self, Expr};
use kui_core::{Bound, Calc, Sizing};
use crate::convert::kstr;
use crate::types::{KuiSizing, KuiStr};
const FIT: KuiSizing = KuiSizing { tag: 0, value: 0.0 };
fn expr_of(s: KuiSizing) -> Option<Expr> {
match s.tag {
2 | 3 if !s.value.is_finite() => None,
2 => Some(Expr::Px(s.value)),
3 => Some(Expr::Pct(s.value)),
4 => Calc::from_id(s.value as u32)
.and_then(|c| c.expr())
.map(|e| (*e).clone()),
_ => None,
}
}
pub(crate) fn c_sizing(s: Sizing) -> KuiSizing {
match s {
Sizing::Fit => FIT,
Sizing::Grow(v) => KuiSizing { tag: 1, value: v },
Sizing::Fixed(v) => KuiSizing { tag: 2, value: v },
Sizing::Percent(v) => KuiSizing { tag: 3, value: v },
Sizing::Calc(c) => KuiSizing {
tag: 4,
value: c.id() as f32,
},
}
}
pub(crate) fn bound_of(s: KuiSizing) -> Option<Bound> {
match s.tag {
2 => Some(Bound::Px(s.value)),
3 | 4 => {
let e = expr_of(s)?;
calc::intern(e).ok().map(Bound::Calc)
}
_ => None,
}
}
fn reduced(e: Option<Expr>) -> KuiSizing {
e.and_then(|e| calc::sizing_of(e).ok())
.map_or(FIT, c_sizing)
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_px(px: f32) -> KuiSizing {
KuiSizing { tag: 2, value: px }
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_pct(percent: f32) -> KuiSizing {
KuiSizing {
tag: 3,
value: percent / 100.0,
}
}
fn args(ptr: *const KuiSizing, n: usize) -> Option<Vec<Expr>> {
if ptr.is_null() || n == 0 {
return None;
}
let xs = unsafe { std::slice::from_raw_parts(ptr, n) };
xs.iter().map(|s| expr_of(*s)).collect()
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_min(args_ptr: *const KuiSizing, n: usize) -> KuiSizing {
reduced(args(args_ptr, n).map(Expr::Min))
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_max(args_ptr: *const KuiSizing, n: usize) -> KuiSizing {
reduced(args(args_ptr, n).map(Expr::Max))
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_clamp(lo: KuiSizing, target: KuiSizing, hi: KuiSizing) -> KuiSizing {
reduced((|| {
Some(Expr::Clamp(
Box::new(expr_of(lo)?),
Box::new(expr_of(target)?),
Box::new(expr_of(hi)?),
))
})())
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_size_parse(s: KuiStr, out: *mut KuiSizing) -> bool {
let sizing = match kui_core::schema::sizing_str(&kstr(s)) {
Ok(sizing) => sizing,
Err(e) if kui_core::calc::is_full(&e) => kui_core::Sizing::Fit,
Err(_) => return false,
};
if !out.is_null() {
unsafe { *out = c_sizing(sizing) };
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn built_and_spelled_are_one() {
let built = kui_size_clamp(kui_size_px(400.0), kui_size_pct(80.0), kui_size_px(1000.0));
let mut spelled = FIT;
let s = "clamp(400px, 80%, 1000px)";
assert!(kui_size_parse(
KuiStr {
ptr: s.as_ptr() as _,
len: s.len()
},
&mut spelled
));
assert_eq!((built.tag, built.value), (spelled.tag, spelled.value));
assert_eq!(built.tag, 4);
let lengths = [kui_size_px(300.0), kui_size_px(400.0)];
let m = kui_size_min(lengths.as_ptr(), 2);
assert_eq!((m.tag, m.value), (2, 300.0), "no percentage: a length");
let bad = kui_size_clamp(FIT, kui_size_pct(50.0), kui_size_px(9.0));
assert_eq!(bad.tag, 0, "fit is not a size");
}
#[test]
fn nesting_by_hand_stops_at_the_cap() {
let mut s = kui_size_pct(50.0);
let mut calcs = 0;
for _ in 0..40 {
let one = [s];
s = kui_size_min(one.as_ptr(), 1);
calcs += (s.tag == 4) as u32;
}
assert_eq!(s.tag, 0, "refused past the cap");
assert_eq!(calcs, kui_core::calc::MAX_DEPTH);
}
#[test]
fn a_number_that_is_not_finite_is_not_a_size() {
for n in [f32::NAN, f32::INFINITY] {
let xs = [kui_size_px(n), kui_size_pct(50.0)];
assert_eq!(kui_size_min(xs.as_ptr(), 2).tag, 0, "{n}");
let xs = [kui_size_px(10.0), kui_size_pct(n)];
assert_eq!(kui_size_max(xs.as_ptr(), 2).tag, 0, "{n}");
assert!(bound_of(kui_size_pct(n)).is_none(), "{n}");
}
let a = [kui_size_px(-0.0), kui_size_pct(50.0)];
let b = [kui_size_px(0.0), kui_size_pct(50.0)];
let (a, b) = (kui_size_min(a.as_ptr(), 2), kui_size_min(b.as_ptr(), 2));
assert_eq!((a.tag, a.value), (b.tag, b.value), "-0 is 0");
}
#[test]
fn a_negative_px_ceiling_is_zero() {
let l = kui_core::NodeSpec::column()
.with_bounds(
None,
bound_of(kui_size_px(-1.0)),
None,
bound_of(kui_size_px(f32::NAN)),
)
.layout;
assert_eq!((l.max_w, l.max_h), (0.0, 0.0));
}
}