Skip to main content

tui_lipan/widgets/class_diagram/
mod.rs

1//! Static UML class diagram widget.
2
3mod layout;
4mod node;
5mod reconcile;
6mod theme;
7
8pub use layout::measure_class_diagram;
9pub use node::ClassDiagramNode;
10pub use reconcile::reconcile_class_diagram;
11pub use theme::ClassDiagramTheme;
12
13use std::sync::Arc;
14
15use crate::core::element::{Element, ElementKind};
16use crate::style::{BorderStyle, Length, Padding, Style};
17
18/// UML visibility of a [`ClassMember`], rendered as a `+ - # ~` prefix.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
20pub enum ClassVisibility {
21    /// Public (`+`).
22    #[default]
23    Public,
24    /// Private (`-`).
25    Private,
26    /// Protected (`#`).
27    Protected,
28    /// Package/internal (`~`).
29    Package,
30}
31
32/// The kind of a [`ClassRelation`], controlling the edge/arrowhead style.
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
34pub enum ClassRelationKind {
35    /// Plain association (solid line).
36    #[default]
37    Association,
38    /// Dependency (dashed line, open arrow).
39    Dependency,
40    /// Inheritance / generalization (solid line, hollow triangle).
41    Inheritance,
42    /// Interface realization (dashed line, hollow triangle).
43    Realization,
44    /// Composition (filled diamond).
45    Composition,
46    /// Aggregation (hollow diamond).
47    Aggregation,
48}
49
50/// A single attribute or method of a [`ClassSpec`].
51#[derive(Clone, Debug, PartialEq, Eq, Hash)]
52pub struct ClassMember {
53    /// Visibility prefix.
54    pub visibility: ClassVisibility,
55    /// Member name.
56    pub name: Arc<str>,
57    /// Type (for attributes) or signature (for methods), if any.
58    pub ty: Option<Arc<str>>,
59}
60
61/// A class box with its attributes and methods.
62#[derive(Clone, Debug, PartialEq, Eq, Hash)]
63pub struct ClassSpec {
64    /// Class name.
65    pub name: Arc<str>,
66    /// Attribute (field) members.
67    pub attributes: Vec<ClassMember>,
68    /// Method members.
69    pub methods: Vec<ClassMember>,
70}
71
72/// A relationship between two classes, with kind, multiplicities, and label.
73#[derive(Clone, Debug, PartialEq, Eq, Hash)]
74pub struct ClassRelation {
75    /// Source class name.
76    pub from: Arc<str>,
77    /// Target class name.
78    pub to: Arc<str>,
79    /// Relationship kind.
80    pub kind: ClassRelationKind,
81    /// Optional multiplicity shown at the `from` end.
82    pub multiplicity_from: Option<Arc<str>>,
83    /// Optional multiplicity shown at the `to` end.
84    pub multiplicity_to: Option<Arc<str>>,
85    /// Optional edge label.
86    pub label: Option<Arc<str>>,
87}
88
89/// A static UML class diagram laid out automatically from classes and relations.
90/// Build it with the chaining setters and convert into an [`Element`].
91#[derive(Clone)]
92pub struct ClassDiagram {
93    pub(crate) classes: Arc<[ClassSpec]>,
94    pub(crate) relations: Arc<[ClassRelation]>,
95    pub(crate) style: Style,
96    pub(crate) class_style: Style,
97    pub(crate) edge_style: Style,
98    pub(crate) label_style: Style,
99    pub(crate) border_style: BorderStyle,
100    pub(crate) padding: Padding,
101    pub(crate) node_padding: Padding,
102    pub(crate) layer_gap: u16,
103    pub(crate) node_gap: u16,
104    pub(crate) max_node_width: u16,
105    pub(crate) theme: ClassDiagramTheme,
106    pub(crate) width: Length,
107    pub(crate) height: Length,
108}
109
110impl Default for ClassDiagram {
111    fn default() -> Self {
112        Self {
113            classes: Arc::new([]),
114            relations: Arc::new([]),
115            style: Style::default(),
116            class_style: Style::default(),
117            edge_style: Style::default(),
118            label_style: Style::default(),
119            border_style: BorderStyle::Plain,
120            padding: Padding::default(),
121            node_padding: (0, 1).into(),
122            layer_gap: 4,
123            node_gap: 4,
124            max_node_width: 32,
125            theme: ClassDiagramTheme::default(),
126            width: Length::Auto,
127            height: Length::Auto,
128        }
129    }
130}
131
132impl ClassDiagram {
133    /// Creates an empty diagram with default styling.
134    pub fn new() -> Self {
135        Self::default()
136    }
137    /// Replaces the class set with `classes`.
138    pub fn classes(mut self, classes: impl IntoIterator<Item = ClassSpec>) -> Self {
139        self.classes = classes.into_iter().collect::<Vec<_>>().into();
140        self
141    }
142    /// Replaces the relation set with `relations`.
143    pub fn relations(mut self, relations: impl IntoIterator<Item = ClassRelation>) -> Self {
144        self.relations = relations.into_iter().collect::<Vec<_>>().into();
145        self
146    }
147    /// Appends an empty class by name. See [`attribute`](Self::attribute) and
148    /// [`method`](Self::method) to add members.
149    pub fn class(mut self, name: impl Into<Arc<str>>) -> Self {
150        let mut v = self.classes.to_vec();
151        v.push(ClassSpec::new(name));
152        self.classes = v.into();
153        self
154    }
155    /// Adds an attribute to the named class, creating the class if it does not
156    /// yet exist.
157    pub fn attribute(
158        mut self,
159        class: impl AsRef<str>,
160        visibility: ClassVisibility,
161        name: impl Into<Arc<str>>,
162        ty: impl Into<Arc<str>>,
163    ) -> Self {
164        self.update_class(class.as_ref(), |c| {
165            c.attributes.push(ClassMember {
166                visibility,
167                name: name.into(),
168                ty: Some(ty.into()),
169            })
170        });
171        self
172    }
173    /// Adds a method to the named class, creating the class if it does not yet
174    /// exist.
175    pub fn method(
176        mut self,
177        class: impl AsRef<str>,
178        visibility: ClassVisibility,
179        name: impl Into<Arc<str>>,
180        sig: impl Into<Arc<str>>,
181    ) -> Self {
182        self.update_class(class.as_ref(), |c| {
183            c.methods.push(ClassMember {
184                visibility,
185                name: name.into(),
186                ty: Some(sig.into()),
187            })
188        });
189        self
190    }
191    /// Adds a relationship between two classes with the given kind, multiplicities,
192    /// and optional label.
193    pub fn relation(
194        mut self,
195        from: impl Into<Arc<str>>,
196        to: impl Into<Arc<str>>,
197        kind: ClassRelationKind,
198        multiplicity_from: impl Into<Option<Arc<str>>>,
199        multiplicity_to: impl Into<Option<Arc<str>>>,
200        label: impl Into<Option<Arc<str>>>,
201    ) -> Self {
202        let mut v = self.relations.to_vec();
203        v.push(ClassRelation {
204            from: from.into(),
205            to: to.into(),
206            kind,
207            multiplicity_from: multiplicity_from.into(),
208            multiplicity_to: multiplicity_to.into(),
209            label: label.into(),
210        });
211        self.relations = v.into();
212        self
213    }
214    /// Sets the base style of the diagram container.
215    pub fn style(mut self, style: Style) -> Self {
216        self.style = style;
217        self
218    }
219    /// Sets the style applied to class boxes.
220    pub fn class_style(mut self, style: Style) -> Self {
221        self.class_style = style;
222        self
223    }
224    /// Sets the style applied to relation edges.
225    pub fn edge_style(mut self, style: Style) -> Self {
226        self.edge_style = style;
227        self
228    }
229    /// Sets the style applied to edge labels.
230    pub fn label_style(mut self, style: Style) -> Self {
231        self.label_style = style;
232        self
233    }
234    /// Sets the border line style for class boxes.
235    pub fn border_style(mut self, style: BorderStyle) -> Self {
236        self.border_style = style;
237        self
238    }
239    /// Sets the outer padding of the diagram.
240    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
241        self.padding = padding.into();
242        self
243    }
244    /// Sets the inner padding of each class box.
245    pub fn node_padding(mut self, padding: impl Into<Padding>) -> Self {
246        self.node_padding = padding.into();
247        self
248    }
249    /// Caps the rendered width of a class box (minimum 1).
250    pub fn max_node_width(mut self, width: u16) -> Self {
251        self.max_node_width = width.max(1);
252        self
253    }
254    /// Sets the width of the diagram container.
255    pub fn width(mut self, width: Length) -> Self {
256        self.width = width;
257        self
258    }
259    /// Sets the height of the diagram container.
260    pub fn height(mut self, height: Length) -> Self {
261        self.height = height;
262        self
263    }
264
265    fn update_class(&mut self, name: &str, f: impl FnOnce(&mut ClassSpec)) {
266        let mut classes = self.classes.to_vec();
267        let index = classes
268            .iter()
269            .position(|c| c.name.as_ref() == name)
270            .unwrap_or_else(|| {
271                classes.push(ClassSpec::new(name.to_owned()));
272                classes.len() - 1
273            });
274        f(&mut classes[index]);
275        self.classes = classes.into();
276    }
277}
278
279impl ClassSpec {
280    /// Creates a class with the given name and no members.
281    pub fn new(name: impl Into<Arc<str>>) -> Self {
282        Self {
283            name: name.into(),
284            attributes: Vec::new(),
285            methods: Vec::new(),
286        }
287    }
288    /// Appends an attribute member and returns the spec for chaining.
289    pub fn attribute(
290        mut self,
291        visibility: ClassVisibility,
292        name: impl Into<Arc<str>>,
293        ty: impl Into<Arc<str>>,
294    ) -> Self {
295        self.attributes.push(ClassMember {
296            visibility,
297            name: name.into(),
298            ty: Some(ty.into()),
299        });
300        self
301    }
302    /// Appends a method member and returns the spec for chaining.
303    pub fn method(
304        mut self,
305        visibility: ClassVisibility,
306        name: impl Into<Arc<str>>,
307        sig: impl Into<Arc<str>>,
308    ) -> Self {
309        self.methods.push(ClassMember {
310            visibility,
311            name: name.into(),
312            ty: Some(sig.into()),
313        });
314        self
315    }
316}
317
318impl From<ClassDiagram> for Element {
319    fn from(value: ClassDiagram) -> Self {
320        Element::new(ElementKind::ClassDiagram(Box::new(value)))
321    }
322}