use super::{
COMPACT_BOX, COMPACT_MARGIN, COMPACT_TEXT, LABEL_SIZE, LINE, MARGIN, MAX_BOX, MIN_BOX,
SUBLABEL_SIZE, pad,
};
use crate::{
diagram::wrap,
flow::focus_classes,
metrics::TextMetrics,
spec::{FragmentSpec, MessageKind, ParticipantKind, SequenceSpec},
};
pub(super) struct Activation {
pub(super) participant: usize,
pub(super) from: usize,
pub(super) to: usize,
pub(super) level: usize,
}
pub(super) struct Model<'a> {
pub(super) sequence: &'a SequenceSpec,
pub(super) ends: Vec<(usize, usize)>,
pub(super) labels: Vec<String>,
pub(super) activations: Vec<Activation>,
pub(super) depths: Vec<usize>,
pub(super) box_widths: Vec<f64>,
pub(super) band: f64,
pub(super) compact: bool,
pub(super) margin: f64,
pub(super) has_actor: bool,
}
impl<'a> Model<'a> {
pub(super) fn new(
sequence: &'a SequenceSpec,
compact: bool,
metrics: &impl TextMetrics,
) -> Self {
let ends = sequence.ends();
let labels = sequence
.messages
.iter()
.map(|message| message.label.clone())
.collect();
let mut two_lines = false;
let box_widths = sequence
.participants
.iter()
.map(|participant| {
let mut label = metrics.width(&participant.label, LABEL_SIZE);
if compact && label > COMPACT_TEXT {
let word = participant
.label
.split(' ')
.map(|word| metrics.width(word, LABEL_SIZE))
.fold(0.0, f64::max);
let mut ignored = Vec::new();
let lines = wrap(
&participant.label,
COMPACT_TEXT.max(word),
LABEL_SIZE,
metrics,
&mut ignored,
"",
);
two_lines |= lines.len() > 1;
label = lines
.iter()
.map(|line| metrics.width(line, LABEL_SIZE))
.fold(0.0, f64::max);
}
let sublabel = participant
.sublabel
.as_ref()
.map_or(0.0, |sublabel| metrics.width(sublabel, SUBLABEL_SIZE));
let low = if compact { COMPACT_BOX } else { MIN_BOX };
(label.max(sublabel) + 2.0 * pad(compact))
.ceil()
.clamp(low, MAX_BOX)
})
.collect();
let has_sublabel = sequence
.participants
.iter()
.any(|participant| participant.sublabel.is_some());
Self {
activations: activations(sequence, &ends),
depths: (0..sequence.fragments.len())
.map(|index| sequence.depth(index))
.collect(),
ends,
labels,
sequence,
box_widths,
band: if has_sublabel { 48.0 } else { 34.0 } + if two_lines { LINE } else { 0.0 },
compact,
margin: if compact { COMPACT_MARGIN } else { MARGIN },
has_actor: sequence
.participants
.iter()
.any(|participant| participant.kind == ParticipantKind::Actor),
}
}
pub(super) fn participants(&self) -> usize {
self.sequence.participants.len()
}
pub(super) fn widest_box(&self) -> f64 {
let floor = if self.compact { COMPACT_BOX } else { MIN_BOX };
self.box_widths.iter().copied().fold(floor, f64::max)
}
pub(super) fn active(&self, participant: usize, index: usize) -> bool {
self.activations.iter().any(|activation| {
activation.participant == participant
&& activation.from <= index
&& index <= activation.to
})
}
pub(super) fn span(&self, fragment: &FragmentSpec) -> (usize, usize) {
self.ends[fragment.from..=fragment.to]
.iter()
.flat_map(|(from, to)| [*from, *to])
.fold((usize::MAX, 0), |(low, high), participant| {
(low.min(participant), high.max(participant))
})
}
}
impl Model<'_> {
pub(super) fn participant_classes(&self, participant: usize) -> String {
let mut related: Vec<usize> = self
.ends
.iter()
.filter_map(
|&(from, to)| match (from == participant, to == participant) {
(true, _) => Some(to),
(_, true) => Some(from),
_ => None,
},
)
.chain(std::iter::once(participant))
.collect();
related.sort_unstable();
related.dedup();
focus_classes(Some(participant), &related)
}
pub(super) fn message_classes(&self, index: usize) -> String {
let (from, to) = self.ends[index];
focus_classes(None, &[from.min(to), from.max(to)])
}
}
pub(super) fn activations(sequence: &SequenceSpec, ends: &[(usize, usize)]) -> Vec<Activation> {
let mut activations: Vec<Activation> = Vec::new();
let mut open: Vec<Vec<usize>> = vec![Vec::new(); sequence.participants.len()];
let mut closed = Vec::new();
for (index, (message, (from, to))) in sequence.messages.iter().zip(ends).enumerate() {
match message.kind {
MessageKind::Call if from != to => {
open[*to].push(activations.len());
activations.push(Activation {
participant: *to,
from: index,
to: index,
level: open[*to].len() - 1,
});
}
MessageKind::Reply => {
if let Some(activation) = open[*from].pop() {
activations[activation].to = index;
closed.push(activation);
}
}
MessageKind::Call | MessageKind::Async => {}
}
}
for (index, activation) in activations.iter_mut().enumerate() {
if closed.contains(&index) {
continue;
}
activation.to = ends
.iter()
.rposition(|(from, to)| {
*from == activation.participant || *to == activation.participant
})
.expect("the call that opened it involves the participant")
.max(activation.from);
}
activations
}