use crate::ast::LineStyle;
use crate::layout::PlacedNode;
use crate::layout::prim;
use crate::resolve::ResolvedLink;
use std::collections::HashMap;
const LABEL_SIZE: f64 = 13.0;
const LABEL_RISE: f64 = 5.0;
const LABEL_MARGIN: f64 = 16.0;
const HOOK_MIN: f64 = 14.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(16.0)
}
fn hook(&self) -> (f64, f64, f64) {
let s = self.clearance().max(HOOK_MIN);
(s, s, s / 2.0)
}
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 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()))
}
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<PlacedNode> {
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 stroke = p
.link
.attrs
.get("stroke")
.cloned()
.unwrap_or_else(|| super::live("stroke"));
let width = p.link.attrs.number("stroke-width").unwrap_or(1.5);
if fcx == tcx {
let (dx, dy, r) = p.hook();
let fx = endpoint_x(p.from, i, fcx + 1.0);
let mut hook = prim::line(
vec![(fx, y), (fx + dx, y), (fx + dx, y + dy), (fx, y + dy)],
stroke,
width,
);
prim::round(&mut hook, r);
style(&mut hook, p.link);
out.push(hook);
if let Some(label) = p.label() {
let size = p.label_size();
let ly = y - LABEL_RISE - size / 2.0;
let cx = fx + dx / 2.0 + prim::text_width(label, size) / 2.0;
out.push(prim::text_classed(label, cx, ly, size, "sequence-message"));
}
} else {
let fx = endpoint_x(p.from, i, tcx);
let tx = endpoint_x(p.to, i, fcx);
let mut arrow = prim::line(vec![(fx, y), (tx, y)], stroke, width);
style(&mut arrow, p.link);
out.push(arrow);
if let Some(label) = p.label() {
let size = p.label_size();
out.push(prim::text_classed(
label,
(fcx + tcx) / 2.0,
y - LABEL_RISE - size / 2.0,
size,
"sequence-message",
));
}
}
}
out
}
fn style(n: &mut PlacedNode, link: &ResolvedLink) {
n.markers = link.markers.clone();
if let Some(ss) = link.attrs.get("stroke-style") {
n.attrs.insert("stroke-style", ss.clone());
n.own_style.insert("stroke-style", ss.clone());
}
}