A simple TUI Library written in Rust
1//! Deployment pipeline — animated tree showing Build → Test → Deploy phases.
2
3use std::thread;
4use std::time::Duration;
5
6use sly::block::Block;
7use sly::color::Color;
8use sly::style::{Span, Style};
9use sly::terminal;
10use sly::view::View;
11use sly::widgets::{
12 spinner,
13 tree::{self, NodeState, TreeNode},
14};
15
16fn main() {
17 println!("SLY — Deployment Pipeline");
18 println!();
19
20 let width = terminal::get_size()
21 .map(|(c, _)| c as usize)
22 .unwrap_or(80)
23 .min(70);
24
25 // Node indices:
26 // 0 = pipeline root
27 // 1 = Build 2 = install 3 = compile 4 = bundle
28 // 5 = Test 6 = unit 7 = integration
29 // 8 = Deploy 9 = push 10 = rollout
30 let mut states = [NodeState::Pending; 11];
31 let spin = spinner::Spinner::dots().label("Running pipeline…");
32
33 let initial = Block::vstack(vec![spin.frame(0), build_tree(&states)], 1);
34 let mut view = View::create(&initial, width);
35
36 // Transitions: list of (node_idx, new_state) applied at the end of each pause.
37 let transitions: Vec<(Vec<(usize, NodeState)>, u64)> = vec![
38 (vec![(0, NodeState::Running), (1, NodeState::Running)], 200),
39 (vec![(2, NodeState::Running)], 600),
40 (vec![(2, NodeState::Done), (3, NodeState::Running)], 700),
41 (vec![(3, NodeState::Done), (4, NodeState::Running)], 600),
42 (
43 vec![
44 (4, NodeState::Done),
45 (1, NodeState::Done),
46 (5, NodeState::Running),
47 ],
48 350,
49 ),
50 (vec![(6, NodeState::Running)], 600),
51 (vec![(6, NodeState::Done), (7, NodeState::Running)], 750),
52 (
53 vec![
54 (7, NodeState::Done),
55 (5, NodeState::Done),
56 (8, NodeState::Running),
57 ],
58 350,
59 ),
60 (vec![(9, NodeState::Running)], 600),
61 (vec![(9, NodeState::Done), (10, NodeState::Running)], 800),
62 (
63 vec![
64 (10, NodeState::Done),
65 (8, NodeState::Done),
66 (0, NodeState::Done),
67 ],
68 300,
69 ),
70 ];
71
72 let mut tick: usize = 0;
73 for (changes, ms) in &transitions {
74 for _ in 0..4 {
75 thread::sleep(Duration::from_millis(ms / 4));
76 view.update(&Block::vstack(
77 vec![spin.frame(tick), build_tree(&states)],
78 1,
79 ));
80 tick += 1;
81 }
82 for &(idx, state) in changes {
83 states[idx] = state;
84 }
85 view.update(&Block::vstack(
86 vec![spin.frame(tick), build_tree(&states)],
87 1,
88 ));
89 tick += 1;
90 }
91
92 let done = Block::text(vec![vec![Span::styled(
93 "✓ Deployment complete",
94 Style::new().bold().fg(Color::Green),
95 )]]);
96 view.update(&Block::vstack(vec![done, build_tree(&states)], 1));
97 view.finish();
98 println!();
99}
100
101fn node_label(text: &str, state: NodeState) -> Vec<Span> {
102 let style = match state {
103 NodeState::Running => Style::new().bold().fg(Color::Cyan),
104 NodeState::Done => Style::new().fg(Color::Green),
105 NodeState::Failed => Style::new().bold().fg(Color::Red),
106 _ => Style::new().dim(),
107 };
108 vec![Span::styled(text.to_string(), style)]
109}
110
111fn build_tree(s: &[NodeState; 11]) -> Block {
112 let nodes = vec![TreeNode::with_children(
113 node_label("Deploy pipeline", s[0]),
114 s[0],
115 vec![
116 TreeNode::with_children(
117 node_label("Build", s[1]),
118 s[1],
119 vec![
120 TreeNode::new(node_label("Install deps", s[2]), s[2]),
121 TreeNode::new(node_label("Compile", s[3]), s[3]),
122 TreeNode::new(node_label("Bundle assets", s[4]), s[4]),
123 ],
124 ),
125 TreeNode::with_children(
126 node_label("Test", s[5]),
127 s[5],
128 vec![
129 TreeNode::new(node_label("Unit tests", s[6]), s[6]),
130 TreeNode::new(node_label("Integration tests", s[7]), s[7]),
131 ],
132 ),
133 TreeNode::with_children(
134 node_label("Deploy", s[8]),
135 s[8],
136 vec![
137 TreeNode::new(node_label("Push image", s[9]), s[9]),
138 TreeNode::new(node_label("Rollout", s[10]), s[10]),
139 ],
140 ),
141 ],
142 )];
143 tree::to_block(&nodes)
144}