Skip to main content

tui_lipan/widgets/flowchart/
mod.rs

1//! Mermaid-style flowchart widget.
2
3mod layout;
4mod node;
5mod reconcile;
6mod theme;
7
8pub use layout::measure_flowchart;
9pub(crate) use node::FlowchartItemEvent;
10pub use node::FlowchartNode;
11pub(crate) use node::PositionedEdge;
12pub(crate) use node::flowchart_local_content_point;
13pub use reconcile::reconcile_flowchart;
14pub use theme::FlowchartTheme;
15
16use std::collections::HashMap;
17use std::sync::Arc;
18
19use crate::callback::Callback;
20use crate::core::element::{Element, ElementKind};
21use crate::style::{BorderStyle, Length, Padding, Style};
22
23/// Direction used to lay out a [`Flowchart`].
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
25pub enum FlowDirection {
26    /// Sources are above targets.
27    #[default]
28    TopDown,
29    /// Sources are below targets.
30    BottomUp,
31    /// Sources are left of targets.
32    LeftRight,
33    /// Sources are right of targets.
34    RightLeft,
35}
36
37/// Mermaid flowchart node shape.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
39pub enum NodeShape {
40    /// Rectangle: `[text]`.
41    #[default]
42    Rect,
43    /// Rounded rectangle: `(text)`.
44    Round,
45    /// Stadium: `([text])`.
46    Stadium,
47    /// Subroutine: `[[text]]`.
48    Subroutine,
49    /// Cylinder/database: `[(text)]`.
50    Cylinder,
51    /// Circle: `((text))`.
52    Circle,
53    /// Asymmetric: `>text]`.
54    Asymmetric,
55    /// Diamond: `{text}`.
56    Diamond,
57    /// Hexagon: `{{text}}`.
58    Hexagon,
59    /// Parallelogram: `[/text/]`.
60    Parallelogram,
61    /// Alternate parallelogram: `[\text\]`.
62    ParallelogramAlt,
63    /// Trapezoid: `[/text\]`.
64    Trapezoid,
65    /// Alternate trapezoid: `[\text/]`.
66    TrapezoidAlt,
67    /// Double circle: `(((text)))`.
68    DoubleCircle,
69}
70
71/// Flowchart edge stroke style.
72#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
73pub enum EdgeStyle {
74    /// Solid single-cell stroke.
75    #[default]
76    Solid,
77    /// Dashed stroke.
78    Dashed,
79    /// Thick stroke.
80    Thick,
81    /// Hidden edge that still participates in layout.
82    Invisible,
83}
84
85/// Flowchart edge arrowhead style.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
87pub enum EdgeArrow {
88    /// No arrowhead.
89    None,
90    /// Open arrowhead.
91    Open,
92    /// Filled arrowhead.
93    #[default]
94    Filled,
95    /// Cross marker.
96    Cross,
97    /// Circle marker.
98    Circle,
99}
100
101/// Stable identifier for a flowchart node or subgraph.
102#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
103pub struct NodeId(Arc<str>);
104
105impl NodeId {
106    /// Create an identifier.
107    pub fn new(id: impl Into<Arc<str>>) -> Self {
108        Self(id.into())
109    }
110
111    /// Return the identifier as a string slice.
112    pub fn as_str(&self) -> &str {
113        &self.0
114    }
115}
116
117impl From<&str> for NodeId {
118    fn from(value: &str) -> Self {
119        Self::new(value)
120    }
121}
122
123impl From<String> for NodeId {
124    fn from(value: String) -> Self {
125        Self::new(Arc::<str>::from(value))
126    }
127}
128
129impl From<Arc<str>> for NodeId {
130    fn from(value: Arc<str>) -> Self {
131        Self::new(value)
132    }
133}
134
135impl AsRef<str> for NodeId {
136    fn as_ref(&self) -> &str {
137        self.as_str()
138    }
139}
140
141impl std::fmt::Display for NodeId {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        f.write_str(self.as_str())
144    }
145}
146
147/// A directed flowchart edge.
148#[derive(Clone, Debug, PartialEq, Eq, Hash)]
149pub struct Edge {
150    /// Source node id.
151    pub from: NodeId,
152    /// Target node id.
153    pub to: NodeId,
154    /// Optional edge label.
155    pub label: Option<Arc<str>>,
156    /// Stroke style.
157    pub style: EdgeStyle,
158    /// Arrowhead at the source side.
159    pub head_from: EdgeArrow,
160    /// Arrowhead at the target side.
161    pub head_to: EdgeArrow,
162    /// Optional stroke style override.
163    pub line_style: Option<Style>,
164    /// Optional label style override.
165    pub label_style: Option<Style>,
166}
167
168impl Edge {
169    /// Create a solid directed edge.
170    pub fn solid(from: impl Into<NodeId>, to: impl Into<NodeId>) -> Self {
171        Self::new(from, to, EdgeStyle::Solid)
172    }
173
174    /// Create a dashed directed edge.
175    pub fn dashed(from: impl Into<NodeId>, to: impl Into<NodeId>) -> Self {
176        Self::new(from, to, EdgeStyle::Dashed)
177    }
178
179    /// Create a thick directed edge.
180    pub fn thick(from: impl Into<NodeId>, to: impl Into<NodeId>) -> Self {
181        Self::new(from, to, EdgeStyle::Thick)
182    }
183
184    /// Create an invisible layout edge.
185    pub fn invisible(from: impl Into<NodeId>, to: impl Into<NodeId>) -> Self {
186        Self::new(from, to, EdgeStyle::Invisible).arrow_to(EdgeArrow::None)
187    }
188
189    /// Create an edge with a specific style.
190    pub fn new(from: impl Into<NodeId>, to: impl Into<NodeId>, style: EdgeStyle) -> Self {
191        Self {
192            from: from.into(),
193            to: to.into(),
194            label: None,
195            style,
196            head_from: EdgeArrow::None,
197            head_to: EdgeArrow::Filled,
198            line_style: None,
199            label_style: None,
200        }
201    }
202
203    /// Set the edge label.
204    pub fn label(mut self, label: impl Into<Arc<str>>) -> Self {
205        self.label = Some(label.into());
206        self
207    }
208
209    /// Set the source-side arrowhead.
210    pub fn arrow_from(mut self, arrow: EdgeArrow) -> Self {
211        self.head_from = arrow;
212        self
213    }
214
215    /// Set the target-side arrowhead.
216    pub fn arrow_to(mut self, arrow: EdgeArrow) -> Self {
217        self.head_to = arrow;
218        self
219    }
220
221    /// Set both arrowheads.
222    pub fn arrows(mut self, from: EdgeArrow, to: EdgeArrow) -> Self {
223        self.head_from = from;
224        self.head_to = to;
225        self
226    }
227
228    /// Override the edge line style.
229    pub fn line_style(mut self, style: Style) -> Self {
230        self.line_style = Some(style);
231        self
232    }
233
234    /// Override the edge label style.
235    pub fn label_style(mut self, style: Style) -> Self {
236        self.label_style = Some(style);
237        self
238    }
239}
240
241/// Event payload for node pointer interactions.
242#[derive(Clone, Debug, PartialEq, Eq, Hash)]
243pub struct FlowchartNodeEvent {
244    /// Target node id.
245    pub id: NodeId,
246    /// Target node label.
247    pub label: Arc<str>,
248}
249
250/// Event payload for edge pointer interactions.
251#[derive(Clone, Debug, PartialEq, Eq, Hash)]
252pub struct FlowchartEdgeEvent {
253    /// Source node id.
254    pub from: NodeId,
255    /// Target node id.
256    pub to: NodeId,
257    /// Optional edge label.
258    pub label: Option<Arc<str>>,
259}
260
261/// Event payload for subgraph pointer interactions.
262#[derive(Clone, Debug, PartialEq, Eq, Hash)]
263pub struct FlowchartSubgraphEvent {
264    /// Target subgraph id.
265    pub id: NodeId,
266    /// Target subgraph label.
267    pub label: Arc<str>,
268}
269
270/// Stable path identifying an item in a [`Flowchart`].
271#[derive(Clone, Debug, PartialEq, Eq, Hash)]
272pub enum FlowchartItemPath {
273    /// A node id.
274    Node(NodeId),
275    /// Edge index in insertion order.
276    Edge(usize),
277    /// A subgraph id.
278    Subgraph(NodeId),
279}
280
281#[derive(Clone, Debug, PartialEq, Eq, Hash)]
282pub(crate) struct FlowchartNodeSpec {
283    pub(crate) id: NodeId,
284    pub(crate) label: Arc<str>,
285    pub(crate) shape: NodeShape,
286    pub(crate) style: Style,
287    pub(crate) hover_style: Style,
288    pub(crate) parent: Option<NodeId>,
289}
290
291#[derive(Clone, Debug, PartialEq, Eq, Hash)]
292pub(crate) struct FlowchartSubgraphSpec {
293    pub(crate) id: NodeId,
294    pub(crate) label: Arc<str>,
295    pub(crate) parent: Option<NodeId>,
296    pub(crate) style: Style,
297}
298
299/// Direct-paint Mermaid-style flowchart visualization.
300#[derive(Clone)]
301pub struct Flowchart {
302    pub(crate) direction: FlowDirection,
303    pub(crate) nodes: Arc<[FlowchartNodeSpec]>,
304    pub(crate) edges: Arc<[Edge]>,
305    pub(crate) subgraphs: Arc<[FlowchartSubgraphSpec]>,
306    pub(crate) class_defs: Arc<HashMap<Arc<str>, Style>>,
307    pub(crate) class_assignments: Arc<HashMap<NodeId, Arc<str>>>,
308    pub(crate) style: Style,
309    pub(crate) node_style: Style,
310    pub(crate) edge_style: Style,
311    pub(crate) subgraph_style: Style,
312    pub(crate) label_style: Style,
313    pub(crate) item_hover_style: Style,
314    pub(crate) border: bool,
315    pub(crate) border_style: BorderStyle,
316    pub(crate) padding: Padding,
317    pub(crate) node_gap: u16,
318    pub(crate) layer_gap: u16,
319    pub(crate) subgraph_padding: Padding,
320    pub(crate) max_node_width: u16,
321    pub(crate) theme: FlowchartTheme,
322    pub(crate) on_node_click: Option<Callback<FlowchartNodeEvent>>,
323    pub(crate) on_edge_click: Option<Callback<FlowchartEdgeEvent>>,
324    pub(crate) on_subgraph_click: Option<Callback<FlowchartSubgraphEvent>>,
325    pub(crate) on_node_hover: Option<Callback<FlowchartNodeEvent>>,
326    pub(crate) on_edge_hover: Option<Callback<FlowchartEdgeEvent>>,
327    pub(crate) on_subgraph_hover: Option<Callback<FlowchartSubgraphEvent>>,
328    /// Requested width.
329    pub(crate) width: Length,
330    /// Requested height.
331    pub(crate) height: Length,
332}
333
334impl Default for Flowchart {
335    fn default() -> Self {
336        Self {
337            direction: FlowDirection::TopDown,
338            nodes: Arc::new([]),
339            edges: Arc::new([]),
340            subgraphs: Arc::new([]),
341            class_defs: Arc::new(HashMap::new()),
342            class_assignments: Arc::new(HashMap::new()),
343            style: Style::default(),
344            node_style: Style::default(),
345            edge_style: Style::default(),
346            subgraph_style: Style::default(),
347            label_style: Style::default(),
348            item_hover_style: Style::default(),
349            border: false,
350            border_style: BorderStyle::Plain,
351            padding: Padding::default(),
352            node_gap: 4,
353            layer_gap: 3,
354            subgraph_padding: (1, 2).into(),
355            max_node_width: 24,
356            theme: FlowchartTheme::classic(),
357            on_node_click: None,
358            on_edge_click: None,
359            on_subgraph_click: None,
360            on_node_hover: None,
361            on_edge_hover: None,
362            on_subgraph_hover: None,
363            width: Length::Auto,
364            height: Length::Auto,
365        }
366    }
367}
368
369impl Flowchart {
370    /// Create an empty flowchart in the given direction.
371    pub fn new(direction: FlowDirection) -> Self {
372        Self {
373            direction,
374            ..Self::default()
375        }
376    }
377
378    /// Add or replace a node.
379    pub fn node(
380        mut self,
381        id: impl Into<NodeId>,
382        label: impl Into<Arc<str>>,
383        shape: NodeShape,
384    ) -> Self {
385        self.push_node(
386            id.into(),
387            label.into(),
388            shape,
389            None,
390            Style::default(),
391            Style::default(),
392        );
393        self
394    }
395
396    /// Add a node with a style override.
397    pub fn styled_node(
398        mut self,
399        id: impl Into<NodeId>,
400        label: impl Into<Arc<str>>,
401        shape: NodeShape,
402        style: Style,
403    ) -> Self {
404        self.push_node(
405            id.into(),
406            label.into(),
407            shape,
408            None,
409            style,
410            Style::default(),
411        );
412        self
413    }
414
415    /// Set the hover style patched onto an existing node id.
416    pub fn node_hover_style(mut self, id: impl Into<NodeId>, style: Style) -> Self {
417        let id = id.into();
418        let mut nodes = self.nodes.to_vec();
419        if let Some(node) = nodes.iter_mut().find(|node| node.id == id) {
420            node.hover_style = style;
421        }
422        self.nodes = nodes.into();
423        self
424    }
425
426    /// Add an edge.
427    pub fn edge(mut self, edge: Edge) -> Self {
428        let mut edges = self.edges.to_vec();
429        edges.push(edge);
430        self.edges = edges.into();
431        self
432    }
433
434    /// Add a nested subgraph through a closure builder.
435    pub fn subgraph(
436        mut self,
437        id: impl Into<NodeId>,
438        label: impl Into<Arc<str>>,
439        build: impl FnOnce(FlowchartSubgraphBuilder) -> FlowchartSubgraphBuilder,
440    ) -> Self {
441        let id = id.into();
442        let label = label.into();
443        let builder = build(FlowchartSubgraphBuilder::new(id.clone()));
444        self.push_subgraph(id.clone(), label, None, Style::default());
445        for node in builder.nodes {
446            let parent = node.parent.or_else(|| Some(id.clone()));
447            self.push_node(
448                node.id,
449                node.label,
450                node.shape,
451                parent,
452                node.style,
453                node.hover_style,
454            );
455        }
456        for mut subgraph in builder.subgraphs {
457            if subgraph.parent.is_none() {
458                subgraph.parent = Some(id.clone());
459            }
460            self.push_subgraph(subgraph.id, subgraph.label, subgraph.parent, subgraph.style);
461        }
462        let mut edges = self.edges.to_vec();
463        edges.extend(builder.edges);
464        self.edges = edges.into();
465        self
466    }
467
468    /// Define a named class style.
469    pub fn class_def(mut self, name: impl Into<Arc<str>>, style: Style) -> Self {
470        let mut class_defs = (*self.class_defs).clone();
471        class_defs.insert(name.into(), style);
472        self.class_defs = Arc::new(class_defs);
473        self
474    }
475
476    /// Assign a class to a node or subgraph id.
477    pub fn assign_class(mut self, id: impl Into<NodeId>, class: impl Into<Arc<str>>) -> Self {
478        let mut assignments = (*self.class_assignments).clone();
479        assignments.insert(id.into(), class.into());
480        self.class_assignments = Arc::new(assignments);
481        self
482    }
483
484    /// Set the base flowchart style.
485    pub fn style(mut self, style: Style) -> Self {
486        self.style = style;
487        self
488    }
489
490    /// Set the default node style.
491    pub fn node_style(mut self, style: Style) -> Self {
492        self.node_style = style;
493        self
494    }
495
496    /// Set the default edge style.
497    pub fn edge_style(mut self, style: Style) -> Self {
498        self.edge_style = style;
499        self
500    }
501
502    /// Set the default subgraph style.
503    pub fn subgraph_style(mut self, style: Style) -> Self {
504        self.subgraph_style = style;
505        self
506    }
507
508    /// Set the default edge-label style.
509    pub fn label_style(mut self, style: Style) -> Self {
510        self.label_style = style;
511        self
512    }
513
514    /// Set the item hover overlay style.
515    pub fn item_hover_style(mut self, style: Style) -> Self {
516        self.item_hover_style = style;
517        self
518    }
519
520    /// Set diagram-local glyph theme.
521    pub fn theme(mut self, theme: FlowchartTheme) -> Self {
522        self.theme = theme;
523        self
524    }
525
526    /// Enable or disable the outer border.
527    pub fn border(mut self, border: bool) -> Self {
528        self.border = border;
529        self
530    }
531
532    /// Set outer border style.
533    pub fn border_style(mut self, border_style: BorderStyle) -> Self {
534        self.border_style = border_style;
535        self
536    }
537
538    /// Set padding inside the optional outer border.
539    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
540        self.padding = padding.into();
541        self
542    }
543
544    /// Set horizontal gap between nodes in a layer.
545    pub fn node_gap(mut self, gap: u16) -> Self {
546        self.node_gap = gap;
547        self
548    }
549
550    /// Set gap between layers.
551    pub fn layer_gap(mut self, gap: u16) -> Self {
552        self.layer_gap = gap;
553        self
554    }
555
556    /// Set padding around subgraph contents.
557    pub fn subgraph_padding(mut self, padding: impl Into<Padding>) -> Self {
558        self.subgraph_padding = padding.into();
559        self
560    }
561
562    /// Set maximum node label width before wrapping.
563    pub fn max_node_width(mut self, width: u16) -> Self {
564        self.max_node_width = width.max(1);
565        self
566    }
567
568    /// Set requested width.
569    pub fn width(mut self, width: Length) -> Self {
570        self.width = width;
571        self
572    }
573
574    /// Set requested height.
575    pub fn height(mut self, height: Length) -> Self {
576        self.height = height;
577        self
578    }
579
580    /// Set a callback for node clicks.
581    pub fn on_node_click(mut self, cb: Callback<FlowchartNodeEvent>) -> Self {
582        self.on_node_click = Some(cb);
583        self
584    }
585
586    /// Set a callback for edge clicks.
587    pub fn on_edge_click(mut self, cb: Callback<FlowchartEdgeEvent>) -> Self {
588        self.on_edge_click = Some(cb);
589        self
590    }
591
592    /// Set a callback for subgraph header clicks.
593    pub fn on_subgraph_click(mut self, cb: Callback<FlowchartSubgraphEvent>) -> Self {
594        self.on_subgraph_click = Some(cb);
595        self
596    }
597
598    /// Set a callback for node hover transitions.
599    pub fn on_node_hover(mut self, cb: Callback<FlowchartNodeEvent>) -> Self {
600        self.on_node_hover = Some(cb);
601        self
602    }
603
604    /// Set a callback for edge hover transitions.
605    pub fn on_edge_hover(mut self, cb: Callback<FlowchartEdgeEvent>) -> Self {
606        self.on_edge_hover = Some(cb);
607        self
608    }
609
610    /// Set a callback for subgraph header hover transitions.
611    pub fn on_subgraph_hover(mut self, cb: Callback<FlowchartSubgraphEvent>) -> Self {
612        self.on_subgraph_hover = Some(cb);
613        self
614    }
615
616    fn push_node(
617        &mut self,
618        id: NodeId,
619        label: Arc<str>,
620        shape: NodeShape,
621        parent: Option<NodeId>,
622        style: Style,
623        hover_style: Style,
624    ) {
625        let mut nodes = self.nodes.to_vec();
626        if let Some(existing) = nodes.iter_mut().find(|node| node.id == id) {
627            *existing = FlowchartNodeSpec {
628                id,
629                label,
630                shape,
631                style,
632                hover_style,
633                parent,
634            };
635        } else {
636            nodes.push(FlowchartNodeSpec {
637                id,
638                label,
639                shape,
640                style,
641                hover_style,
642                parent,
643            });
644        }
645        self.nodes = nodes.into();
646    }
647
648    fn push_subgraph(&mut self, id: NodeId, label: Arc<str>, parent: Option<NodeId>, style: Style) {
649        let mut subgraphs = self.subgraphs.to_vec();
650        if let Some(existing) = subgraphs.iter_mut().find(|subgraph| subgraph.id == id) {
651            *existing = FlowchartSubgraphSpec {
652                id,
653                label,
654                parent,
655                style,
656            };
657        } else {
658            subgraphs.push(FlowchartSubgraphSpec {
659                id,
660                label,
661                parent,
662                style,
663            });
664        }
665        self.subgraphs = subgraphs.into();
666    }
667}
668
669/// Builder passed to [`Flowchart::subgraph`].
670pub struct FlowchartSubgraphBuilder {
671    parent: NodeId,
672    nodes: Vec<FlowchartNodeSpec>,
673    edges: Vec<Edge>,
674    subgraphs: Vec<FlowchartSubgraphSpec>,
675}
676
677impl FlowchartSubgraphBuilder {
678    fn new(parent: NodeId) -> Self {
679        Self {
680            parent,
681            nodes: Vec::new(),
682            edges: Vec::new(),
683            subgraphs: Vec::new(),
684        }
685    }
686
687    /// Add a node inside this subgraph.
688    pub fn node(
689        mut self,
690        id: impl Into<NodeId>,
691        label: impl Into<Arc<str>>,
692        shape: NodeShape,
693    ) -> Self {
694        self.nodes.push(FlowchartNodeSpec {
695            id: id.into(),
696            label: label.into(),
697            shape,
698            style: Style::default(),
699            hover_style: Style::default(),
700            parent: Some(self.parent.clone()),
701        });
702        self
703    }
704
705    /// Add a styled node inside this subgraph.
706    pub fn styled_node(
707        mut self,
708        id: impl Into<NodeId>,
709        label: impl Into<Arc<str>>,
710        shape: NodeShape,
711        style: Style,
712    ) -> Self {
713        self.nodes.push(FlowchartNodeSpec {
714            id: id.into(),
715            label: label.into(),
716            shape,
717            style,
718            hover_style: Style::default(),
719            parent: Some(self.parent.clone()),
720        });
721        self
722    }
723
724    /// Set the hover style patched onto an existing node inside this subgraph builder.
725    pub fn node_hover_style(mut self, id: impl Into<NodeId>, style: Style) -> Self {
726        let id = id.into();
727        if let Some(node) = self.nodes.iter_mut().find(|node| node.id == id) {
728            node.hover_style = style;
729        }
730        self
731    }
732
733    /// Add an edge inside this subgraph.
734    pub fn edge(mut self, edge: Edge) -> Self {
735        self.edges.push(edge);
736        self
737    }
738
739    /// Add a nested subgraph.
740    pub fn subgraph(
741        mut self,
742        id: impl Into<NodeId>,
743        label: impl Into<Arc<str>>,
744        build: impl FnOnce(FlowchartSubgraphBuilder) -> FlowchartSubgraphBuilder,
745    ) -> Self {
746        let id = id.into();
747        let nested = build(FlowchartSubgraphBuilder::new(id.clone()));
748        self.subgraphs.push(FlowchartSubgraphSpec {
749            id: id.clone(),
750            label: label.into(),
751            parent: Some(self.parent.clone()),
752            style: Style::default(),
753        });
754        self.nodes.extend(nested.nodes);
755        self.edges.extend(nested.edges);
756        self.subgraphs
757            .extend(nested.subgraphs.into_iter().map(|mut sub| {
758                if sub.parent.is_none() {
759                    sub.parent = Some(id.clone());
760                }
761                sub
762            }));
763        self
764    }
765}
766
767impl From<Flowchart> for Element {
768    fn from(value: Flowchart) -> Self {
769        Element::new(ElementKind::Flowchart(Box::new(value)))
770    }
771}