tui_lipan/widgets/state_diagram/
mod.rs1mod layout;
4mod node;
5mod reconcile;
6mod theme;
7
8pub use layout::measure_state_diagram;
9pub use node::StateDiagramNode;
10pub use reconcile::reconcile_state_diagram;
11pub use theme::StateDiagramTheme;
12
13use crate::core::element::{Element, ElementKind};
14use crate::style::{BorderStyle, Length, Padding, Style};
15use std::sync::Arc;
16
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
19pub enum StateKind {
20 #[default]
22 State,
23 Start,
25 End,
27 Choice,
29 Fork,
31 Join,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37pub struct StateSpec {
38 pub id: Arc<str>,
40 pub label: Arc<str>,
42 pub kind: StateKind,
44 pub entry: Option<Arc<str>>,
46 pub exit: Option<Arc<str>>,
48}
49impl StateSpec {
50 pub fn new(id: impl Into<Arc<str>>) -> Self {
52 let id = id.into();
53 Self {
54 label: id.clone(),
55 id,
56 kind: StateKind::State,
57 entry: None,
58 exit: None,
59 }
60 }
61 pub fn label(mut self, label: impl Into<Arc<str>>) -> Self {
63 self.label = label.into();
64 self
65 }
66 pub fn kind(mut self, kind: StateKind) -> Self {
68 self.kind = kind;
69 self
70 }
71 pub fn entry(mut self, entry: impl Into<Arc<str>>) -> Self {
73 self.entry = Some(entry.into());
74 self
75 }
76 pub fn exit(mut self, exit: impl Into<Arc<str>>) -> Self {
78 self.exit = Some(exit.into());
79 self
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq, Hash)]
85pub struct StateTransition {
86 pub from: Arc<str>,
88 pub to: Arc<str>,
90 pub label: Option<Arc<str>>,
92 pub guard: Option<Arc<str>>,
94}
95
96#[derive(Clone)]
99pub struct StateDiagram {
100 pub(crate) states: Arc<[StateSpec]>,
101 pub(crate) transitions: Arc<[StateTransition]>,
102 pub(crate) style: Style,
103 pub(crate) state_style: Style,
104 pub(crate) edge_style: Style,
105 pub(crate) label_style: Style,
106 pub(crate) border_style: BorderStyle,
107 pub(crate) padding: Padding,
108 pub(crate) node_padding: Padding,
109 pub(crate) layer_gap: u16,
110 pub(crate) node_gap: u16,
111 pub(crate) max_node_width: u16,
112 pub(crate) theme: StateDiagramTheme,
113 pub(crate) width: Length,
114 pub(crate) height: Length,
115}
116
117impl Default for StateDiagram {
118 fn default() -> Self {
119 Self {
120 states: Arc::new([]),
121 transitions: Arc::new([]),
122 style: Style::default(),
123 state_style: Style::default(),
124 edge_style: Style::default(),
125 label_style: Style::default(),
126 border_style: BorderStyle::Rounded,
127 padding: Padding::default(),
128 node_padding: (0, 1).into(),
129 layer_gap: 1,
130 node_gap: 4,
131 max_node_width: 32,
132 theme: StateDiagramTheme::default(),
133 width: Length::Auto,
134 height: Length::Auto,
135 }
136 }
137}
138
139impl StateDiagram {
140 pub fn new() -> Self {
142 Self::default()
143 }
144 pub fn states(mut self, states: impl IntoIterator<Item = StateSpec>) -> Self {
146 self.states = states.into_iter().collect::<Vec<_>>().into();
147 self
148 }
149 pub fn transitions(mut self, transitions: impl IntoIterator<Item = StateTransition>) -> Self {
151 self.transitions = transitions.into_iter().collect::<Vec<_>>().into();
152 self
153 }
154 pub fn state(mut self, id: impl Into<Arc<str>>) -> Self {
156 let mut v = self.states.to_vec();
157 v.push(StateSpec::new(id));
158 self.states = v.into();
159 self
160 }
161 pub fn choice(mut self, id: impl Into<Arc<str>>) -> Self {
163 let mut v = self.states.to_vec();
164 v.push(StateSpec::new(id).kind(StateKind::Choice));
165 self.states = v.into();
166 self
167 }
168 pub fn fork(mut self, id: impl Into<Arc<str>>) -> Self {
170 let mut v = self.states.to_vec();
171 v.push(StateSpec::new(id).kind(StateKind::Fork));
172 self.states = v.into();
173 self
174 }
175 pub fn join(mut self, id: impl Into<Arc<str>>) -> Self {
177 let mut v = self.states.to_vec();
178 v.push(StateSpec::new(id).kind(StateKind::Join));
179 self.states = v.into();
180 self
181 }
182 pub fn transition(
184 mut self,
185 from: impl Into<Arc<str>>,
186 to: impl Into<Arc<str>>,
187 label: impl Into<Option<Arc<str>>>,
188 ) -> Self {
189 let mut v = self.transitions.to_vec();
190 v.push(StateTransition {
191 from: from.into(),
192 to: to.into(),
193 label: label.into(),
194 guard: None,
195 });
196 self.transitions = v.into();
197 self
198 }
199 pub fn start_to(mut self, to: impl Into<Arc<str>>) -> Self {
201 let start = Arc::<str>::from("[*]");
202 if !self.states.iter().any(|s| s.id == start) {
203 let mut states = self.states.to_vec();
204 states.push(StateSpec::new(start.clone()).kind(StateKind::Start));
205 self.states = states.into();
206 }
207 self.transition(start, to, None::<Arc<str>>)
208 }
209 pub fn end_from(mut self, from: impl Into<Arc<str>>) -> Self {
211 let end = Arc::<str>::from("[end]");
212 if !self.states.iter().any(|s| s.id == end) {
213 let mut states = self.states.to_vec();
214 states.push(StateSpec::new(end.clone()).kind(StateKind::End));
215 self.states = states.into();
216 }
217 self.transition(from, end, None::<Arc<str>>)
218 }
219 pub fn style(mut self, style: Style) -> Self {
221 self.style = style;
222 self
223 }
224 pub fn state_style(mut self, style: Style) -> Self {
226 self.state_style = style;
227 self
228 }
229 pub fn edge_style(mut self, style: Style) -> Self {
231 self.edge_style = style;
232 self
233 }
234 pub fn label_style(mut self, style: Style) -> Self {
236 self.label_style = style;
237 self
238 }
239 pub fn border_style(mut self, style: BorderStyle) -> Self {
241 self.border_style = style;
242 self
243 }
244 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
246 self.padding = padding.into();
247 self
248 }
249 pub fn node_padding(mut self, padding: impl Into<Padding>) -> Self {
251 self.node_padding = padding.into();
252 self
253 }
254 pub fn max_node_width(mut self, width: u16) -> Self {
256 self.max_node_width = width.max(1);
257 self
258 }
259 pub fn width(mut self, width: Length) -> Self {
261 self.width = width;
262 self
263 }
264 pub fn height(mut self, height: Length) -> Self {
266 self.height = height;
267 self
268 }
269}
270
271impl From<StateDiagram> for Element {
272 fn from(value: StateDiagram) -> Self {
273 Element::new(ElementKind::StateDiagram(Box::new(value)))
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn default_layer_gap_is_compact() {
283 assert_eq!(StateDiagram::default().layer_gap, 1);
284 }
285}