use crate::layout::prim;
use crate::layout::{Bbox, PlacedNode};
use crate::resolve::{AttrMap, NodeKind, ResolvedValue};
use std::collections::HashMap;
const SIDE_GAP: f64 = 12.0;
const FOLD_FRAC: f64 = 0.34;
const FOLD_MAX: f64 = 15.0;
pub(super) fn sticky(note: &mut PlacedNode) {
let (w, h) = (note.bbox.w(), note.bbox.h());
let fold = (h * FOLD_FRAC).min(FOLD_MAX).min(w * 0.5);
let (l, t, rg, b) = (-w / 2.0, -h / 2.0, w / 2.0, h / 2.0);
let body = format!(
"M {l} {t} L {} {t} L {rg} {} L {rg} {b} L {l} {b} Z",
rg - fold,
t + fold
);
note.kind = NodeKind::Path;
note.attrs.insert("path", ResolvedValue::String(body));
let stroke = note
.attrs
.get("stroke")
.cloned()
.unwrap_or_else(|| super::live("stroke"));
let flap = format!(
"M {} {t} L {rg} {} L {} {} Z",
rg - fold,
t + fold,
rg - fold,
t + fold
);
note.children.insert(
0,
prim::path(
flap,
stroke,
Bbox {
min_x: rg - fold,
min_y: t,
max_x: rg,
max_y: t + fold,
},
),
);
}
pub(super) enum Placement {
Over(Vec<String>),
Left(String),
Right(String),
}
pub(super) fn placement(attrs: &AttrMap) -> Option<Placement> {
if let Some(v) = attrs.get("over") {
return Some(Placement::Over(idents(v)));
}
if let Some(v) = attrs.get("left") {
return first_ident(v).map(Placement::Left);
}
if let Some(v) = attrs.get("right") {
return first_ident(v).map(Placement::Right);
}
None
}
pub(super) fn centre_x(
placement: &Placement,
box_w: f64,
lifeline_x: &HashMap<String, f64>,
) -> Option<f64> {
match placement {
Placement::Over(ids) => {
let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
for id in ids {
let x = *lifeline_x.get(id)?;
lo = lo.min(x);
hi = hi.max(x);
}
(lo <= hi).then_some((lo + hi) / 2.0)
}
Placement::Left(id) => lifeline_x.get(id).map(|x| x - box_w / 2.0 - SIDE_GAP),
Placement::Right(id) => lifeline_x.get(id).map(|x| x + box_w / 2.0 + SIDE_GAP),
}
}
fn idents(v: &ResolvedValue) -> Vec<String> {
match v {
ResolvedValue::Ident(s) => vec![s.clone()],
ResolvedValue::Tuple(xs) | ResolvedValue::List(xs) => {
xs.iter().filter_map(one_ident).collect()
}
_ => Vec::new(),
}
}
fn first_ident(v: &ResolvedValue) -> Option<String> {
idents(v).into_iter().next()
}
fn one_ident(v: &ResolvedValue) -> Option<String> {
match v {
ResolvedValue::Ident(s) => Some(s.clone()),
_ => None,
}
}