Skip to main content

tui_lipan/widgets/sequence_diagram/
mod.rs

1//! Mermaid-style UML sequence diagram widget.
2
3mod layout;
4mod node;
5mod reconcile;
6mod theme;
7
8pub use layout::measure_sequence_diagram;
9pub use node::SequenceDiagramNode;
10pub(crate) use node::{PositionedFragment, PositionedMessage, autonumber_rect};
11pub use reconcile::{reconcile_sequence_diagram, reconcile_sequence_diagram_with_width};
12pub use theme::{
13    ActivationTheme, AutonumberTheme, FragmentGlyphs, LifelineTheme, MessageGlyphs,
14    SequenceDiagramTheme,
15};
16
17use std::sync::Arc;
18
19use crate::callback::Callback;
20use crate::core::element::{Element, ElementKind};
21use crate::style::{BorderStyle, Length, Padding, Style};
22use crate::widgets::Overflow;
23
24/// Stable actor key used by messages and notes.
25#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub struct ActorRef(pub Arc<str>);
27
28impl ActorRef {
29    /// Create an actor reference from an alias/key.
30    pub fn new(value: impl Into<Arc<str>>) -> Self {
31        Self(value.into())
32    }
33
34    /// Return the actor alias/key.
35    pub fn as_str(&self) -> &str {
36        &self.0
37    }
38}
39
40impl From<&str> for ActorRef {
41    fn from(value: &str) -> Self {
42        Self::new(value)
43    }
44}
45
46impl From<String> for ActorRef {
47    fn from(value: String) -> Self {
48        Self::new(value)
49    }
50}
51
52impl From<Arc<str>> for ActorRef {
53    fn from(value: Arc<str>) -> Self {
54        Self::new(value)
55    }
56}
57
58/// Participant header rendering kind.
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
60pub enum ActorKind {
61    /// Rectangular participant box.
62    #[default]
63    Participant,
64    /// Actor/stick-figure header.
65    Actor,
66}
67
68/// Sequence diagram rendering style.
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
70pub enum SequenceDiagramVariant {
71    /// Mermaid-style boxed participant headers.
72    #[default]
73    Boxed,
74    /// Compact headers with lifeline tee joints and no participant boxes.
75    Minimal,
76}
77
78/// Message arrow style.
79#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
80pub enum MessageStyle {
81    /// Solid line, filled arrow head.
82    #[default]
83    Sync,
84    /// Solid line, open arrow head.
85    Async,
86    /// Dashed reply with filled arrow head.
87    SyncReply,
88    /// Dashed reply with open arrow head.
89    AsyncReply,
90    /// Lost message terminator.
91    Lost,
92    /// Open message terminator.
93    Open,
94}
95
96impl MessageStyle {
97    /// Number of distinct message styles (size of the per-style theme table).
98    pub const INDEX_COUNT: usize = 6;
99
100    /// Dense `0..INDEX_COUNT` index for this style, used to key theme tables.
101    pub const fn index(self) -> usize {
102        match self {
103            Self::Sync => 0,
104            Self::Async => 1,
105            Self::SyncReply => 2,
106            Self::AsyncReply => 3,
107            Self::Lost => 4,
108            Self::Open => 5,
109        }
110    }
111}
112
113/// Fragment block type.
114#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
115pub enum FragmentKind {
116    /// Repeated block (`loop`).
117    Loop,
118    /// Conditional alternatives (`alt` / `else`).
119    Alt,
120    /// Optional block (`opt`).
121    Opt,
122    /// Parallel branches (`par` / `and`).
123    Par,
124    /// Critical region (`critical`).
125    Critical,
126    /// Break block (`break`).
127    Break,
128    /// Plain background rectangle grouping (`rect`).
129    Rect,
130}
131
132impl FragmentKind {
133    /// Number of distinct fragment kinds (size of the per-kind theme table).
134    pub const INDEX_COUNT: usize = 7;
135
136    /// Dense `0..INDEX_COUNT` index for this kind, used to key theme tables.
137    pub const fn index(self) -> usize {
138        match self {
139            Self::Loop => 0,
140            Self::Alt => 1,
141            Self::Opt => 2,
142            Self::Par => 3,
143            Self::Critical => 4,
144            Self::Break => 5,
145            Self::Rect => 6,
146        }
147    }
148}
149
150/// Note placement relative to actor columns.
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152pub enum NotePlacement {
153    /// To the left of a single actor column.
154    LeftOf,
155    /// To the right of a single actor column.
156    RightOf,
157    /// Spanning over one or more actor columns.
158    Over,
159}
160
161/// Stable item path returned by hit testing and pointer callbacks.
162///
163/// Each variant carries the zero-based index of the item within its category, in
164/// the order the steps were added.
165#[derive(Clone, Debug, PartialEq, Eq, Hash)]
166pub enum SequenceItemPath {
167    /// A message arrow between two actors.
168    Message(usize),
169    /// A self-message loop on one actor.
170    SelfMessage(usize),
171    /// A participant header.
172    Participant(usize),
173    /// A note box.
174    Note(usize),
175    /// A fragment block.
176    Fragment(usize),
177    /// A divider line.
178    Divider(usize),
179}
180
181/// Event payload for sequence diagram item interactions.
182#[derive(Clone, Debug, PartialEq, Eq, Hash)]
183pub struct SequenceItemEvent {
184    /// Path identifying the interacted item.
185    pub path: SequenceItemPath,
186    /// Display label of the interacted item.
187    pub label: Arc<str>,
188}
189
190/// Message between two actors.
191#[derive(Clone, Debug, PartialEq, Eq, Hash)]
192pub struct SequenceMessage {
193    /// Source actor.
194    pub from: ActorRef,
195    /// Target actor.
196    pub to: ActorRef,
197    /// Message label drawn on the arrow.
198    pub label: Arc<str>,
199    /// Arrow style.
200    pub style: MessageStyle,
201    /// Whether arrival activates (starts an activation bar on) the target.
202    pub activate_target: bool,
203    /// Whether sending deactivates (ends the activation bar on) the source.
204    pub deactivate_source: bool,
205    /// Optional per-message override for the arrow line style.
206    pub line_style: Option<Style>,
207    /// Optional per-message override for the label style.
208    pub label_style: Option<Style>,
209}
210
211impl SequenceMessage {
212    /// Creates a [`Sync`](MessageStyle::Sync) message with the given endpoints and label.
213    pub fn new(
214        from: impl Into<ActorRef>,
215        to: impl Into<ActorRef>,
216        label: impl Into<Arc<str>>,
217    ) -> Self {
218        Self {
219            from: from.into(),
220            to: to.into(),
221            label: label.into(),
222            style: MessageStyle::Sync,
223            activate_target: false,
224            deactivate_source: false,
225            line_style: None,
226            label_style: None,
227        }
228    }
229
230    /// Creates a [`Sync`](MessageStyle::Sync) message (solid line, filled arrow).
231    pub fn sync(
232        from: impl Into<ActorRef>,
233        to: impl Into<ActorRef>,
234        label: impl Into<Arc<str>>,
235    ) -> Self {
236        Self::new(from, to, label).message_style(MessageStyle::Sync)
237    }
238
239    /// Creates an [`Async`](MessageStyle::Async) message (solid line, open arrow).
240    pub fn async_(
241        from: impl Into<ActorRef>,
242        to: impl Into<ActorRef>,
243        label: impl Into<Arc<str>>,
244    ) -> Self {
245        Self::new(from, to, label).message_style(MessageStyle::Async)
246    }
247
248    /// Creates a [`SyncReply`](MessageStyle::SyncReply) message (dashed reply, filled arrow).
249    pub fn reply(
250        from: impl Into<ActorRef>,
251        to: impl Into<ActorRef>,
252        label: impl Into<Arc<str>>,
253    ) -> Self {
254        Self::new(from, to, label).message_style(MessageStyle::SyncReply)
255    }
256
257    /// Creates an [`AsyncReply`](MessageStyle::AsyncReply) message (dashed reply, open arrow).
258    pub fn async_reply(
259        from: impl Into<ActorRef>,
260        to: impl Into<ActorRef>,
261        label: impl Into<Arc<str>>,
262    ) -> Self {
263        Self::new(from, to, label).message_style(MessageStyle::AsyncReply)
264    }
265
266    /// Creates a [`Lost`](MessageStyle::Lost) message (terminator marker).
267    pub fn lost(
268        from: impl Into<ActorRef>,
269        to: impl Into<ActorRef>,
270        label: impl Into<Arc<str>>,
271    ) -> Self {
272        Self::new(from, to, label).message_style(MessageStyle::Lost)
273    }
274
275    /// Creates an [`Open`](MessageStyle::Open) message (open terminator marker).
276    pub fn open(
277        from: impl Into<ActorRef>,
278        to: impl Into<ActorRef>,
279        label: impl Into<Arc<str>>,
280    ) -> Self {
281        Self::new(from, to, label).message_style(MessageStyle::Open)
282    }
283
284    /// Sets the arrow style.
285    pub fn message_style(mut self, style: MessageStyle) -> Self {
286        self.style = style;
287        self
288    }
289
290    /// Sets whether arrival activates the target.
291    pub fn activate_target(mut self, activate: bool) -> Self {
292        self.activate_target = activate;
293        self
294    }
295
296    /// Sets whether sending deactivates the source.
297    pub fn deactivate_source(mut self, deactivate: bool) -> Self {
298        self.deactivate_source = deactivate;
299        self
300    }
301
302    /// Overrides the arrow line style for this message.
303    pub fn line_style(mut self, style: Style) -> Self {
304        self.line_style = Some(style);
305        self
306    }
307
308    /// Overrides the label style for this message.
309    pub fn label_style(mut self, style: Style) -> Self {
310        self.label_style = Some(style);
311        self
312    }
313}
314
315/// Compatibility alias for applications that use Mermaid naming.
316pub type Msg = SequenceMessage;
317
318/// One flat sequence diagram command.
319///
320/// Steps are stored in order; fragment blocks are delimited by
321/// [`FragmentBegin`](Self::FragmentBegin)/[`FragmentEnd`](Self::FragmentEnd) pairs
322/// rather than nested values.
323#[derive(Clone, Debug, PartialEq, Eq, Hash)]
324pub enum SequenceStep {
325    /// A message between two actors.
326    Message(SequenceMessage),
327    /// A self-message loop on a single actor.
328    SelfMessage {
329        /// Actor the message loops on.
330        actor: ActorRef,
331        /// Message label.
332        label: Arc<str>,
333        /// Optional style override.
334        style: Option<Style>,
335    },
336    /// A note attached to one or more actors.
337    Note {
338        /// Placement relative to the actor column(s).
339        placement: NotePlacement,
340        /// Actors the note spans/attaches to.
341        actors: Arc<[ActorRef]>,
342        /// Note text.
343        text: Arc<str>,
344        /// Optional style override.
345        style: Option<Style>,
346    },
347    /// Begins an activation bar on the given actor.
348    Activate(ActorRef),
349    /// Ends an activation bar on the given actor.
350    Deactivate(ActorRef),
351    /// Opens a fragment block.
352    FragmentBegin {
353        /// Fragment kind.
354        kind: FragmentKind,
355        /// Fragment title label.
356        label: Arc<str>,
357        /// Optional first-branch label (e.g. the `alt` condition).
358        branch_label: Option<Arc<str>>,
359        /// Optional style override.
360        style: Option<Style>,
361    },
362    /// Starts a new branch within the open fragment (e.g. `else`, `and`).
363    FragmentBranch {
364        /// Fragment kind the branch belongs to.
365        kind: FragmentKind,
366        /// Branch label.
367        label: Arc<str>,
368    },
369    /// Closes the open fragment block.
370    FragmentEnd,
371    /// Draws a background rectangle behind subsequent steps until balanced.
372    Rect {
373        /// Background fill style.
374        color: Style,
375    },
376    /// A labelled divider line spanning the diagram width.
377    Divider(Arc<str>),
378}
379
380impl SequenceStep {
381    /// Wraps a [`SequenceMessage`] into a [`Message`](Self::Message) step.
382    pub fn message(message: SequenceMessage) -> Self {
383        Self::Message(message)
384    }
385    /// Builds a [`SelfMessage`](Self::SelfMessage) step.
386    pub fn self_msg(actor: impl Into<ActorRef>, label: impl Into<Arc<str>>) -> Self {
387        Self::SelfMessage {
388            actor: actor.into(),
389            label: label.into(),
390            style: None,
391        }
392    }
393    /// Builds a [`Note`](Self::Note) step placed over the given actors.
394    pub fn note_over(
395        actors: impl IntoIterator<Item = impl Into<ActorRef>>,
396        text: impl Into<Arc<str>>,
397    ) -> Self {
398        Self::Note {
399            placement: NotePlacement::Over,
400            actors: Arc::<[ActorRef]>::from(actors.into_iter().map(Into::into).collect::<Vec<_>>()),
401            text: text.into(),
402            style: None,
403        }
404    }
405    /// Builds a [`Note`](Self::Note) step with explicit placement.
406    pub fn note(
407        placement: NotePlacement,
408        actors: impl IntoIterator<Item = impl Into<ActorRef>>,
409        text: impl Into<Arc<str>>,
410    ) -> Self {
411        Self::Note {
412            placement,
413            actors: Arc::<[ActorRef]>::from(actors.into_iter().map(Into::into).collect::<Vec<_>>()),
414            text: text.into(),
415            style: None,
416        }
417    }
418    /// Builds an [`Activate`](Self::Activate) step.
419    pub fn activate(actor: impl Into<ActorRef>) -> Self {
420        Self::Activate(actor.into())
421    }
422    /// Builds a [`Deactivate`](Self::Deactivate) step.
423    pub fn deactivate(actor: impl Into<ActorRef>) -> Self {
424        Self::Deactivate(actor.into())
425    }
426    /// Builds a [`FragmentBegin`](Self::FragmentBegin) step.
427    pub fn fragment_begin(kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
428        Self::FragmentBegin {
429            kind,
430            label: label.into(),
431            branch_label: None,
432            style: None,
433        }
434    }
435    /// Builds a [`FragmentBranch`](Self::FragmentBranch) step.
436    pub fn fragment_branch(kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
437        Self::FragmentBranch {
438            kind,
439            label: label.into(),
440        }
441    }
442    /// Builds a [`FragmentEnd`](Self::FragmentEnd) step.
443    pub fn fragment_end() -> Self {
444        Self::FragmentEnd
445    }
446}
447
448/// Compatibility alias for concise examples.
449pub type Step = SequenceStep;
450
451#[derive(Clone, Debug, PartialEq, Eq, Hash)]
452pub(crate) struct ParticipantSpec {
453    pub(crate) actor: ActorRef,
454    pub(crate) label: Arc<str>,
455    pub(crate) kind: ActorKind,
456}
457
458/// Direct-paint UML sequence diagram widget.
459#[derive(Clone)]
460pub struct SequenceDiagram {
461    pub(crate) participants: Vec<ParticipantSpec>,
462    pub(crate) steps: Vec<SequenceStep>,
463    pub(crate) variant: SequenceDiagramVariant,
464    pub(crate) actor_glyph: Arc<str>,
465    pub(crate) style: Style,
466    pub(crate) theme: SequenceDiagramTheme,
467    pub(crate) border: bool,
468    pub(crate) border_style: BorderStyle,
469    pub(crate) padding: Padding,
470    pub(crate) width: Length,
471    pub(crate) height: Length,
472    pub(crate) max_label_cells: Option<u16>,
473    pub(crate) message_label_overflow: Overflow,
474    pub(crate) autonumber: bool,
475    pub(crate) repeat_participants_at_bottom: bool,
476    pub(crate) on_item_click: Option<Callback<SequenceItemEvent>>,
477    pub(crate) on_item_hover: Option<Callback<SequenceItemEvent>>,
478}
479
480impl Default for SequenceDiagram {
481    fn default() -> Self {
482        Self {
483            participants: Vec::new(),
484            steps: Vec::new(),
485            variant: SequenceDiagramVariant::Boxed,
486            actor_glyph: Arc::from("○ "),
487            style: Style::default(),
488            theme: SequenceDiagramTheme::classic(),
489            border: false,
490            border_style: BorderStyle::Plain,
491            padding: Padding::default(),
492            width: Length::Auto,
493            height: Length::Auto,
494            max_label_cells: Some(32),
495            message_label_overflow: Overflow::Ellipsis,
496            autonumber: false,
497            repeat_participants_at_bottom: false,
498            on_item_click: None,
499            on_item_hover: None,
500        }
501    }
502}
503
504impl SequenceDiagram {
505    /// Creates an empty diagram with the default (boxed/classic) theme.
506    pub fn new() -> Self {
507        Self::default()
508    }
509
510    /// Declares a participant whose label equals its key. Re-declaring updates it.
511    pub fn participant(mut self, actor: impl Into<ActorRef>) -> Self {
512        let actor = actor.into();
513        let label = actor.0.clone();
514        self.upsert_participant(actor, label, ActorKind::Participant);
515        self
516    }
517
518    /// Declares a participant with a separate alias (key) and display label.
519    pub fn participant_aliased(
520        mut self,
521        alias: impl Into<ActorRef>,
522        label: impl Into<Arc<str>>,
523    ) -> Self {
524        self.upsert_participant(alias.into(), label.into(), ActorKind::Participant);
525        self
526    }
527
528    /// Sets the header rendering kind for an actor, declaring it if needed.
529    pub fn actor_kind(mut self, actor: impl Into<ActorRef>, kind: ActorKind) -> Self {
530        let actor = actor.into();
531        if let Some(participant) = self.participants.iter_mut().find(|p| p.actor == actor) {
532            participant.kind = kind;
533        } else {
534            let label = actor.0.clone();
535            self.upsert_participant(actor, label, kind);
536        }
537        self
538    }
539
540    /// Appends a raw [`SequenceStep`].
541    pub fn step(mut self, step: SequenceStep) -> Self {
542        self.steps.push(step);
543        self
544    }
545    /// Appends a message step.
546    pub fn message(self, message: SequenceMessage) -> Self {
547        self.step(SequenceStep::Message(message))
548    }
549    /// Appends a self-message step.
550    pub fn self_msg(self, actor: impl Into<ActorRef>, label: impl Into<Arc<str>>) -> Self {
551        self.step(SequenceStep::self_msg(actor, label))
552    }
553    /// Appends a note spanning over the given actors.
554    pub fn note_over(
555        self,
556        actors: impl IntoIterator<Item = impl Into<ActorRef>>,
557        text: impl Into<Arc<str>>,
558    ) -> Self {
559        self.step(SequenceStep::note_over(actors, text))
560    }
561    /// Appends a note to the left of an actor.
562    pub fn note_left_of(self, actor: impl Into<ActorRef>, text: impl Into<Arc<str>>) -> Self {
563        self.note_one(NotePlacement::LeftOf, actor, text)
564    }
565    /// Appends a note to the right of an actor.
566    pub fn note_right_of(self, actor: impl Into<ActorRef>, text: impl Into<Arc<str>>) -> Self {
567        self.note_one(NotePlacement::RightOf, actor, text)
568    }
569    /// Appends an activate step for an actor.
570    pub fn activate(self, actor: impl Into<ActorRef>) -> Self {
571        self.step(SequenceStep::Activate(actor.into()))
572    }
573    /// Appends a deactivate step for an actor.
574    pub fn deactivate(self, actor: impl Into<ActorRef>) -> Self {
575        self.step(SequenceStep::Deactivate(actor.into()))
576    }
577    /// Opens a fragment block of the given kind. Prefer the scoped helpers
578    /// ([`loop_`](Self::loop_), [`alt`](Self::alt), …) when possible.
579    pub fn fragment_begin(self, kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
580        self.step(SequenceStep::fragment_begin(kind, label))
581    }
582    /// Starts a new branch within the open fragment.
583    pub fn fragment_branch(self, kind: FragmentKind, label: impl Into<Arc<str>>) -> Self {
584        self.step(SequenceStep::FragmentBranch {
585            kind,
586            label: label.into(),
587        })
588    }
589    /// Closes the open fragment block.
590    pub fn fragment_end(self) -> Self {
591        self.step(SequenceStep::FragmentEnd)
592    }
593    /// Appends a background rectangle step.
594    pub fn rect(self, color: Style) -> Self {
595        self.step(SequenceStep::Rect { color })
596    }
597    /// Appends a labelled divider step.
598    pub fn divider(self, label: impl Into<Arc<str>>) -> Self {
599        self.step(SequenceStep::Divider(label.into()))
600    }
601
602    /// Adds a `loop` fragment whose body is built by `f`.
603    pub fn loop_(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
604        self.fragment(FragmentKind::Loop, label, f)
605    }
606    /// Adds an `alt` fragment whose first branch body is built by `f`. Use
607    /// [`else_`](Self::else_) inside `f` to start alternative branches.
608    pub fn alt(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
609        self.fragment(FragmentKind::Alt, label, f)
610    }
611    /// Starts an `else` branch within an open `alt` fragment.
612    pub fn else_(self, label: impl Into<Arc<str>>) -> Self {
613        self.fragment_branch(FragmentKind::Alt, label)
614    }
615    /// Adds a `par` fragment whose first branch body is built by `f`. Use
616    /// [`and`](Self::and) inside `f` to start parallel branches.
617    pub fn par(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
618        self.fragment(FragmentKind::Par, label, f)
619    }
620    /// Starts an `and` branch within an open `par` fragment.
621    pub fn and(self, label: impl Into<Arc<str>>) -> Self {
622        self.fragment_branch(FragmentKind::Par, label)
623    }
624    /// Adds an `opt` fragment whose body is built by `f`.
625    pub fn opt(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
626        self.fragment(FragmentKind::Opt, label, f)
627    }
628    /// Adds a `critical` fragment whose body is built by `f`.
629    pub fn critical(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
630        self.fragment(FragmentKind::Critical, label, f)
631    }
632    /// Adds a `break` fragment whose body is built by `f`.
633    pub fn break_(self, label: impl Into<Arc<str>>, f: impl FnOnce(Self) -> Self) -> Self {
634        self.fragment(FragmentKind::Break, label, f)
635    }
636
637    /// Sets the base style of the diagram container.
638    pub fn style(mut self, style: Style) -> Self {
639        self.style = style;
640        self
641    }
642    /// Sets the style of participant header boxes.
643    pub fn participant_style(mut self, style: Style) -> Self {
644        self.theme.participant_style = style;
645        self
646    }
647    /// Sets the style of lifelines.
648    pub fn lifeline_style(mut self, style: Style) -> Self {
649        self.theme.lifeline.style = style;
650        self
651    }
652    /// Sets the default style of message labels.
653    pub fn message_label_style(mut self, style: Style) -> Self {
654        self.theme.message_label_style = style;
655        self
656    }
657    /// Sets the style of note boxes.
658    pub fn note_style(mut self, style: Style) -> Self {
659        self.theme.note_style = style;
660        self
661    }
662    /// Sets a single style for all fragment kinds.
663    pub fn fragment_style(mut self, style: Style) -> Self {
664        self.theme.fragment_styles.fill(style);
665        self
666    }
667    /// Sets the style of activation bars.
668    pub fn activation_style(mut self, style: Style) -> Self {
669        self.theme.activation.style = style;
670        self
671    }
672    /// Sets the style applied to a hovered item.
673    pub fn item_hover_style(mut self, style: Style) -> Self {
674        self.theme.hover_style = style;
675        self
676    }
677    /// Sets the style of autonumber badges.
678    pub fn autonumber_style(mut self, style: Style) -> Self {
679        self.theme.autonumber.style = style;
680        self
681    }
682    /// Replaces the entire theme.
683    pub fn theme(mut self, theme: SequenceDiagramTheme) -> Self {
684        self.theme = theme;
685        self
686    }
687    /// Overrides the style for a single message kind.
688    pub fn message_kind_style(mut self, kind: MessageStyle, style: Style) -> Self {
689        *self.theme.message_style_mut(kind) = style;
690        self
691    }
692    /// Overrides the style for a single fragment kind.
693    pub fn fragment_kind_style(mut self, kind: FragmentKind, style: Style) -> Self {
694        *self.theme.fragment_style_mut(kind) = style;
695        self
696    }
697    /// Sets the glyph used to draw lifelines.
698    pub fn lifeline_glyph(mut self, glyph: char) -> Self {
699        self.theme.lifeline.glyph = glyph;
700        self
701    }
702    /// Sets the glyph used to fill activation bars.
703    pub fn activation_glyph(mut self, glyph: char) -> Self {
704        self.theme.activation.fill_glyph = glyph;
705        self
706    }
707    /// Sets the autonumber badge format string.
708    pub fn autonumber_format(mut self, format: impl Into<Arc<str>>) -> Self {
709        self.theme.autonumber.format = format.into();
710        self
711    }
712    /// Toggles the outer border around the diagram.
713    pub fn border(mut self, border: bool) -> Self {
714        self.border = border;
715        self
716    }
717    /// Sets the outer border line style.
718    pub fn border_style(mut self, border_style: BorderStyle) -> Self {
719        self.border_style = border_style;
720        self
721    }
722    /// Sets the outer padding of the diagram.
723    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
724        self.padding = padding.into();
725        self
726    }
727    /// Sets the width of the diagram container.
728    pub fn width(mut self, width: Length) -> Self {
729        self.width = width;
730        self
731    }
732    /// Sets the height of the diagram container.
733    pub fn height(mut self, height: Length) -> Self {
734        self.height = height;
735        self
736    }
737    /// Caps the cell width of message labels (`None` = unbounded; minimum 1).
738    pub fn max_label_cells(mut self, max_label_cells: Option<u16>) -> Self {
739        self.max_label_cells = max_label_cells.map(|cells| cells.max(1));
740        self
741    }
742    /// Sets how over-long message labels are handled.
743    pub fn message_label_overflow(mut self, overflow: Overflow) -> Self {
744        self.message_label_overflow = overflow;
745        self
746    }
747    /// Toggles automatic numbering of messages.
748    pub fn autonumber(mut self, autonumber: bool) -> Self {
749        self.autonumber = autonumber;
750        self
751    }
752    /// Sets the rendering variant without changing the theme.
753    pub fn variant(mut self, variant: SequenceDiagramVariant) -> Self {
754        self.variant = variant;
755        self
756    }
757    /// Switches to the [`Minimal`](SequenceDiagramVariant::Minimal) variant and its theme.
758    pub fn minimal(self) -> Self {
759        self.variant(SequenceDiagramVariant::Minimal)
760            .theme(SequenceDiagramTheme::minimal())
761    }
762    /// Switches to the [`Boxed`](SequenceDiagramVariant::Boxed) variant and the classic theme.
763    pub fn boxed(self) -> Self {
764        self.variant(SequenceDiagramVariant::Boxed)
765            .theme(SequenceDiagramTheme::classic())
766    }
767    /// Sets the glyph prefix used for actor-kind headers.
768    pub fn actor_glyph(mut self, glyph: impl Into<Arc<str>>) -> Self {
769        self.actor_glyph = glyph.into();
770        self
771    }
772    /// Repeats the participant headers at the bottom of the diagram.
773    pub fn repeat_participants_at_bottom(mut self, repeat: bool) -> Self {
774        self.repeat_participants_at_bottom = repeat;
775        self
776    }
777    /// Sets a callback invoked when an item is clicked.
778    pub fn on_item_click(mut self, cb: Callback<SequenceItemEvent>) -> Self {
779        self.on_item_click = Some(cb);
780        self
781    }
782    /// Sets a callback invoked when an item is hovered.
783    pub fn on_item_hover(mut self, cb: Callback<SequenceItemEvent>) -> Self {
784        self.on_item_hover = Some(cb);
785        self
786    }
787
788    fn fragment(
789        self,
790        kind: FragmentKind,
791        label: impl Into<Arc<str>>,
792        f: impl FnOnce(Self) -> Self,
793    ) -> Self {
794        f(self.fragment_begin(kind, label)).fragment_end()
795    }
796
797    fn note_one(
798        self,
799        placement: NotePlacement,
800        actor: impl Into<ActorRef>,
801        text: impl Into<Arc<str>>,
802    ) -> Self {
803        self.step(SequenceStep::Note {
804            placement,
805            actors: Arc::<[ActorRef]>::from(vec![actor.into()]),
806            text: text.into(),
807            style: None,
808        })
809    }
810
811    fn upsert_participant(&mut self, actor: ActorRef, label: Arc<str>, kind: ActorKind) {
812        if let Some(participant) = self.participants.iter_mut().find(|p| p.actor == actor) {
813            participant.label = label;
814            participant.kind = kind;
815        } else {
816            self.participants
817                .push(ParticipantSpec { actor, label, kind });
818        }
819    }
820}
821
822impl From<SequenceDiagram> for Element {
823    fn from(value: SequenceDiagram) -> Self {
824        Element::new(ElementKind::SequenceDiagram(Box::new(value)))
825    }
826}