mod layout;
mod node;
mod reconcile;
mod theme;
pub use layout::measure_sequence_diagram;
pub use node::SequenceDiagramNode;
pub(crate) use node::{PositionedFragment, PositionedMessage, autonumber_rect};
pub use reconcile::{reconcile_sequence_diagram, reconcile_sequence_diagram_with_width};
pub use theme::{
ActivationTheme, AutonumberTheme, FragmentGlyphs, LifelineTheme, MessageGlyphs,
SequenceDiagramTheme,
};
use std::sync::Arc;
use crate::callback::Callback;
use crate::core::element::{Element, ElementKind};
use crate::style::{BorderStyle, Length, Padding, Style};
use crate::widgets::Overflow;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ActorRef(pub Arc<str>);
impl ActorRef {
pub fn new(value: impl Into<Arc<str>>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for ActorRef {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for ActorRef {
fn from(value: String) -> Self {
Self::new(value)
}
}
impl From<Arc<str>> for ActorRef {
fn from(value: Arc<str>) -> Self {
Self::new(value)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ActorKind {
#[default]
Participant,
Actor,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum SequenceDiagramVariant {
#[default]
Boxed,
Minimal,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum MessageStyle {
#[default]
Sync,
Async,
SyncReply,
AsyncReply,
Lost,
Open,
}
impl MessageStyle {
pub const INDEX_COUNT: usize = 6;
pub const fn index(self) -> usize {
match self {
Self::Sync => 0,
Self::Async => 1,
Self::SyncReply => 2,
Self::AsyncReply => 3,
Self::Lost => 4,
Self::Open => 5,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FragmentKind {
Loop,
Alt,
Opt,
Par,
Critical,
Break,
Rect,
}
impl FragmentKind {
pub const INDEX_COUNT: usize = 7;
pub const fn index(self) -> usize {
match self {
Self::Loop => 0,
Self::Alt => 1,
Self::Opt => 2,
Self::Par => 3,
Self::Critical => 4,
Self::Break => 5,
Self::Rect => 6,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum NotePlacement {
LeftOf,
RightOf,
Over,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SequenceItemPath {
Message(usize),
SelfMessage(usize),
Participant(usize),
Note(usize),
Fragment(usize),
Divider(usize),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SequenceItemEvent {
pub path: SequenceItemPath,
pub label: Arc<str>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct SequenceMessage {
pub from: ActorRef,
pub to: ActorRef,
pub label: Arc<str>,
pub style: MessageStyle,
pub activate_target: bool,
pub deactivate_source: bool,
pub line_style: Option<Style>,
pub label_style: Option<Style>,
}
impl SequenceMessage {
pub fn new(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self {
from: from.into(),
to: to.into(),
label: label.into(),
style: MessageStyle::Sync,
activate_target: false,
deactivate_source: false,
line_style: None,
label_style: None,
}
}
pub fn sync(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::Sync)
}
pub fn async_(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::Async)
}
pub fn reply(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::SyncReply)
}
pub fn async_reply(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::AsyncReply)
}
pub fn lost(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::Lost)
}
pub fn open(
from: impl Into<ActorRef>,
to: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
Self::new(from, to, label).message_style(MessageStyle::Open)
}
pub fn message_style(mut self, style: MessageStyle) -> Self {
self.style = style;
self
}
pub fn activate_target(mut self, activate: bool) -> Self {
self.activate_target = activate;
self
}
pub fn deactivate_source(mut self, deactivate: bool) -> Self {
self.deactivate_source = deactivate;
self
}
pub fn line_style(mut self, style: Style) -> Self {
self.line_style = Some(style);
self
}
pub fn label_style(mut self, style: Style) -> Self {
self.label_style = Some(style);
self
}
}
pub type Msg = SequenceMessage;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SequenceStep {
Message(SequenceMessage),
SelfMessage {
actor: ActorRef,
label: Arc<str>,
style: Option<Style>,
},
Note {
placement: NotePlacement,
actors: Arc<[ActorRef]>,
text: Arc<str>,
style: Option<Style>,
},
Activate(ActorRef),
Deactivate(ActorRef),
FragmentBegin {
kind: FragmentKind,
label: Arc<str>,
branch_label: Option<Arc<str>>,
style: Option<Style>,
},
FragmentBranch {
kind: FragmentKind,
label: Arc<str>,
},
FragmentEnd,
Rect {
color: Style,
},
Divider(Arc<str>),
}
impl SequenceStep {
pub fn message(message: SequenceMessage) -> Self {
Self::Message(message)
}
pub fn self_msg(actor: impl Into<ActorRef>, label: impl Into<Arc<str>>) -> Self {
Self::SelfMessage {
actor: actor.into(),
label: label.into(),
style: None,
}
}
pub fn note_over(
actors: impl IntoIterator<Item = impl Into<ActorRef>>,
text: impl Into<Arc<str>>,
) -> Self {
Self::Note {
placement: NotePlacement::Over,
actors: Arc::<[ActorRef]>::from(actors.into_iter().map(Into::into).collect::<Vec<_>>()),
text: text.into(),
style: None,
}
}
pub fn note(
placement: NotePlacement,
actors: impl IntoIterator<Item = impl Into<ActorRef>>,
text: impl Into<Arc<str>>,
) -> Self {
Self::Note {
placement,
actors: Arc::<[ActorRef]>::from(actors.into_iter().map(Into::into).collect::<Vec<_>>()),
text: text.into(),
style: None,
}
}
pub fn activate(actor: impl Into<ActorRef>) -> Self {
Self::Activate(actor.into())
}
pub fn deactivate(actor: impl Into<ActorRef>) -> Self {
Self::Deactivate(actor.into())
}
pub fn fragment_begin(kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
Self::FragmentBegin {
kind,
label: label.into(),
branch_label: None,
style: None,
}
}
pub fn fragment_branch(kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
Self::FragmentBranch {
kind,
label: label.into(),
}
}
pub fn fragment_end() -> Self {
Self::FragmentEnd
}
}
pub type Step = SequenceStep;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ParticipantSpec {
pub(crate) actor: ActorRef,
pub(crate) label: Arc<str>,
pub(crate) kind: ActorKind,
}
#[derive(Clone)]
pub struct SequenceDiagram {
pub(crate) participants: Vec<ParticipantSpec>,
pub(crate) steps: Vec<SequenceStep>,
pub(crate) variant: SequenceDiagramVariant,
pub(crate) actor_glyph: Arc<str>,
pub(crate) style: Style,
pub(crate) theme: SequenceDiagramTheme,
pub(crate) border: bool,
pub(crate) border_style: BorderStyle,
pub(crate) padding: Padding,
pub(crate) width: Length,
pub(crate) height: Length,
pub(crate) max_label_cells: Option<u16>,
pub(crate) message_label_overflow: Overflow,
pub(crate) autonumber: bool,
pub(crate) repeat_participants_at_bottom: bool,
pub(crate) on_item_click: Option<Callback<SequenceItemEvent>>,
pub(crate) on_item_hover: Option<Callback<SequenceItemEvent>>,
}
impl Default for SequenceDiagram {
fn default() -> Self {
Self {
participants: Vec::new(),
steps: Vec::new(),
variant: SequenceDiagramVariant::Boxed,
actor_glyph: Arc::from("â—‹ "),
style: Style::default(),
theme: SequenceDiagramTheme::classic(),
border: false,
border_style: BorderStyle::Plain,
padding: Padding::default(),
width: Length::Auto,
height: Length::Auto,
max_label_cells: Some(32),
message_label_overflow: Overflow::Ellipsis,
autonumber: false,
repeat_participants_at_bottom: false,
on_item_click: None,
on_item_hover: None,
}
}
}
impl SequenceDiagram {
pub fn new() -> Self {
Self::default()
}
pub fn participant(mut self, actor: impl Into<ActorRef>) -> Self {
let actor = actor.into();
let label = actor.0.clone();
self.upsert_participant(actor, label, ActorKind::Participant);
self
}
pub fn participant_aliased(
mut self,
alias: impl Into<ActorRef>,
label: impl Into<Arc<str>>,
) -> Self {
self.upsert_participant(alias.into(), label.into(), ActorKind::Participant);
self
}
pub fn actor_kind(mut self, actor: impl Into<ActorRef>, kind: ActorKind) -> Self {
let actor = actor.into();
if let Some(participant) = self.participants.iter_mut().find(|p| p.actor == actor) {
participant.kind = kind;
} else {
let label = actor.0.clone();
self.upsert_participant(actor, label, kind);
}
self
}
pub fn step(mut self, step: SequenceStep) -> Self {
self.steps.push(step);
self
}
pub fn message(self, message: SequenceMessage) -> Self {
self.step(SequenceStep::Message(message))
}
pub fn self_msg(self, actor: impl Into<ActorRef>, label: impl Into<Arc<str>>) -> Self {
self.step(SequenceStep::self_msg(actor, label))
}
pub fn note_over(
self,
actors: impl IntoIterator<Item = impl Into<ActorRef>>,
text: impl Into<Arc<str>>,
) -> Self {
self.step(SequenceStep::note_over(actors, text))
}
pub fn note_left_of(self, actor: impl Into<ActorRef>, text: impl Into<Arc<str>>) -> Self {
self.note_one(NotePlacement::LeftOf, actor, text)
}
pub fn note_right_of(self, actor: impl Into<ActorRef>, text: impl Into<Arc<str>>) -> Self {
self.note_one(NotePlacement::RightOf, actor, text)
}
pub fn activate(self, actor: impl Into<ActorRef>) -> Self {
self.step(SequenceStep::Activate(actor.into()))
}
pub fn deactivate(self, actor: impl Into<ActorRef>) -> Self {
self.step(SequenceStep::Deactivate(actor.into()))
}
pub fn fragment_begin(self, kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
self.step(SequenceStep::fragment_begin(kind, label))
}
pub fn fragment_branch(self, kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
self.step(SequenceStep::FragmentBranch {
kind,
label: label.into(),
})
}
pub fn fragment_end(self) -> Self {
self.step(SequenceStep::FragmentEnd)
}
pub fn rect(self, color: Style) -> Self {
self.step(SequenceStep::Rect { color })
}
pub fn divider(self, label: impl Into<Arc<str>>) -> Self {
self.step(SequenceStep::Divider(label.into()))
}
pub fn loop_(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Loop, label, f)
}
pub fn alt(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Alt, label, f)
}
pub fn else_(self, label: impl Into<Arc<str>>) -> Self {
self.fragment_branch(FragmentKind::Alt, label)
}
pub fn par(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Par, label, f)
}
pub fn and(self, label: impl Into<Arc<str>>) -> Self {
self.fragment_branch(FragmentKind::Par, label)
}
pub fn opt(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Opt, label, f)
}
pub fn critical(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Critical, label, f)
}
pub fn break_(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
self.fragment(FragmentKind::Break, label, f)
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn participant_style(mut self, style: Style) -> Self {
self.theme.participant_style = style;
self
}
pub fn lifeline_style(mut self, style: Style) -> Self {
self.theme.lifeline.style = style;
self
}
pub fn message_label_style(mut self, style: Style) -> Self {
self.theme.message_label_style = style;
self
}
pub fn note_style(mut self, style: Style) -> Self {
self.theme.note_style = style;
self
}
pub fn fragment_style(mut self, style: Style) -> Self {
self.theme.fragment_styles.fill(style);
self
}
pub fn activation_style(mut self, style: Style) -> Self {
self.theme.activation.style = style;
self
}
pub fn item_hover_style(mut self, style: Style) -> Self {
self.theme.hover_style = style;
self
}
pub fn autonumber_style(mut self, style: Style) -> Self {
self.theme.autonumber.style = style;
self
}
pub fn theme(mut self, theme: SequenceDiagramTheme) -> Self {
self.theme = theme;
self
}
pub fn message_kind_style(mut self, kind: MessageStyle, style: Style) -> Self {
*self.theme.message_style_mut(kind) = style;
self
}
pub fn fragment_kind_style(mut self, kind: FragmentKind, style: Style) -> Self {
*self.theme.fragment_style_mut(kind) = style;
self
}
pub fn lifeline_glyph(mut self, glyph: char) -> Self {
self.theme.lifeline.glyph = glyph;
self
}
pub fn activation_glyph(mut self, glyph: char) -> Self {
self.theme.activation.fill_glyph = glyph;
self
}
pub fn autonumber_format(mut self, format: impl Into<Arc<str>>) -> Self {
self.theme.autonumber.format = format.into();
self
}
pub fn border(mut self, border: bool) -> Self {
self.border = border;
self
}
pub fn border_style(mut self, border_style: BorderStyle) -> Self {
self.border_style = border_style;
self
}
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
pub fn max_label_cells(mut self, max_label_cells: Option<u16>) -> Self {
self.max_label_cells = max_label_cells.map(|cells| cells.max(1));
self
}
pub fn message_label_overflow(mut self, overflow: Overflow) -> Self {
self.message_label_overflow = overflow;
self
}
pub fn autonumber(mut self, autonumber: bool) -> Self {
self.autonumber = autonumber;
self
}
pub fn variant(mut self, variant: SequenceDiagramVariant) -> Self {
self.variant = variant;
self
}
pub fn minimal(self) -> Self {
self.variant(SequenceDiagramVariant::Minimal)
.theme(SequenceDiagramTheme::minimal())
}
pub fn boxed(self) -> Self {
self.variant(SequenceDiagramVariant::Boxed)
.theme(SequenceDiagramTheme::classic())
}
pub fn actor_glyph(mut self, glyph: impl Into<Arc<str>>) -> Self {
self.actor_glyph = glyph.into();
self
}
pub fn repeat_participants_at_bottom(mut self, repeat: bool) -> Self {
self.repeat_participants_at_bottom = repeat;
self
}
pub fn on_item_click(mut self, cb: Callback<SequenceItemEvent>) -> Self {
self.on_item_click = Some(cb);
self
}
pub fn on_item_hover(mut self, cb: Callback<SequenceItemEvent>) -> Self {
self.on_item_hover = Some(cb);
self
}
fn fragment(
self,
kind: FragmentKind,
label: impl Into<Arc<str>>,
f: impl FnOnce(Self) -> Self,
) -> Self {
f(self.fragment_begin(kind, label)).fragment_end()
}
fn note_one(
self,
placement: NotePlacement,
actor: impl Into<ActorRef>,
text: impl Into<Arc<str>>,
) -> Self {
self.step(SequenceStep::Note {
placement,
actors: Arc::<[ActorRef]>::from(vec![actor.into()]),
text: text.into(),
style: None,
})
}
fn upsert_participant(&mut self, actor: ActorRef, label: Arc<str>, kind: ActorKind) {
if let Some(participant) = self.participants.iter_mut().find(|p| p.actor == actor) {
participant.label = label;
participant.kind = kind;
} else {
self.participants
.push(ParticipantSpec { actor, label, kind });
}
}
}
impl From<SequenceDiagram> for Element {
fn from(value: SequenceDiagram) -> Self {
Element::new(ElementKind::SequenceDiagram(Box::new(value)))
}
}