use crate::ast::LineStyle;
use crate::layout::ir::{RoutedLink, RoutedText, SEQUENCE_MESSAGE_CLASS};
use crate::layout::prim;
use crate::ledger::consts::DEFAULT_CLEARANCE;
use crate::resolve::{AttrMap, ResolvedLink};
use crate::routing::straight;
use std::collections::HashMap;
pub(crate) const LABEL_SIZE: f64 = 13.0;
const LABEL_RISE: f64 = 5.0;
const LABEL_MARGIN: f64 = 16.0;
const HOOK_BAR_EDGE: f64 = 5.0;
const HOOK_MARGIN: f64 = 10.0;
pub(super) struct Pair<'a> {
pub from: &'a str,
pub to: &'a str,
link: &'a ResolvedLink,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub(super) enum Kind {
Call,
Return,
Async,
Self_,
}
impl Pair<'_> {
fn label(&self) -> Option<&str> {
self.link.texts.first().map(|t| t.text.as_str())
}
pub(super) fn span(&self) -> crate::span::Span {
self.link.span
}
pub(super) fn ends(&self) -> (&str, &str) {
(self.from, self.to)
}
pub(super) fn is_self(&self) -> bool {
self.from == self.to
}
fn clearance(&self) -> f64 {
self.link
.attrs
.number("clearance")
.unwrap_or(DEFAULT_CLEARANCE)
}
fn hook(&self) -> (f64, f64) {
let s = self.clearance().max(straight::HOOK_MIN);
(s, s)
}
pub(super) fn hook_reach(&self) -> f64 {
if self.is_self() {
HOOK_BAR_EDGE + self.hook().0
} else {
0.0
}
}
pub(super) fn hook_drop(&self) -> f64 {
if self.is_self() { self.hook().1 } else { 0.0 }
}
fn text_at(&self, cx: f64, cy: f64) -> Option<RoutedText> {
let label = self.label()?;
Some(RoutedText {
content: label.to_owned(),
position: (cx, cy),
tangent: (1.0, 0.0),
attrs: AttrMap::new(),
class: SEQUENCE_MESSAGE_CLASS,
})
}
fn label_size(&self) -> f64 {
LABEL_SIZE
}
fn label_width(&self) -> f64 {
self.label().map_or(0.0, |l| {
prim::text_width(
l,
self.label_size(),
crate::font::Font::of(&self.link.attrs),
)
})
}
pub(super) fn kind(&self) -> Kind {
if self.is_self() {
Kind::Self_
} else {
match self.link.line {
LineStyle::Wavy => Kind::Async,
LineStyle::Dashed => Kind::Return,
_ => Kind::Call,
}
}
}
}
pub(super) fn pairs<'a>(messages: &[&'a ResolvedLink]) -> Vec<Pair<'a>> {
let mut out = Vec::new();
for w in messages {
for win in w.endpoints.windows(2) {
out.push(Pair {
from: leaf(&win[0].path),
to: leaf(&win[1].path),
link: w,
});
}
}
out
}
fn leaf(path: &str) -> &str {
path.rsplit('.').next().unwrap_or(path)
}
pub(super) fn columns(widths: &[f64], ids: &[&str], pairs: &[Pair], gap_col: f64) -> Vec<f64> {
let n = widths.len();
if n == 0 {
return Vec::new();
}
let col: HashMap<&str, usize> = ids.iter().enumerate().map(|(i, &id)| (id, i)).collect();
let half = |i: usize| widths[i] / 2.0;
let mut gaps = vec![gap_col; n.saturating_sub(1)];
for p in pairs {
let (Some(&a), Some(&b)) = (col.get(p.from), col.get(p.to)) else {
continue;
};
if a == b {
if a + 1 < n {
let need = p.hook_reach() + HOOK_MARGIN;
let dist = half(a) + gaps[a] + half(a + 1);
if need > dist {
gaps[a] += need - dist;
}
}
continue;
}
let (lo, hi) = (a.min(b), a.max(b));
let mut dist = half(lo) + half(hi);
(lo + 1..hi).for_each(|k| dist += widths[k]);
gaps[lo..hi].iter().for_each(|g| dist += g);
let needed = p.label_width() + LABEL_MARGIN;
if needed > dist {
let add = (needed - dist) / (hi - lo) as f64;
gaps[lo..hi].iter_mut().for_each(|g| *g += add);
}
}
let mut centres = Vec::with_capacity(n);
let mut x = 0.0;
for (i, &w) in widths.iter().enumerate() {
x += w / 2.0;
centres.push(x);
x += w / 2.0;
if let Some(g) = gaps.get(i) {
x += g;
}
}
let shift = x / 2.0;
centres.iter().map(|c| c - shift).collect()
}
pub(super) fn draw(
pairs: &[Pair],
lifeline_x: &HashMap<String, f64>,
endpoint_x: impl Fn(&str, usize, f64) -> f64,
row_y: impl Fn(usize) -> f64,
) -> Vec<RoutedLink> {
let mut out = Vec::new();
for (i, p) in pairs.iter().enumerate() {
let (Some(&fcx), Some(&tcx)) = (lifeline_x.get(p.from), lifeline_x.get(p.to)) else {
continue;
};
let y = row_y(i);
let size = p.label_size();
let ly = y - LABEL_RISE - size / 2.0;
let (path, text) = if fcx == tcx {
let (dx, dy) = p.hook();
let fx = endpoint_x(p.from, i, fcx + 1.0);
let cx = fx + dx / 2.0 + p.label_width() / 2.0;
(straight::hook(fx, y, y + dy, dx), p.text_at(cx, ly))
} else {
let fx = endpoint_x(p.from, i, tcx);
let tx = endpoint_x(p.to, i, fcx);
(vec![(fx, y), (tx, y)], p.text_at((fcx + tcx) / 2.0, ly))
};
out.push(straight::wire(
path,
text.into_iter().collect(),
(p.from, p.to),
(p.from, p.to),
p.link.markers.clone(),
&p.link.attrs,
&p.link.applied_styles,
p.link.span,
));
}
out
}