1use decay::prelude::*;
2
3fn main() {
4 decay::term::spawn_window();
5
6 App::new()
7 .plugin(CorePlugins)
8 .plugin(UiPlugin)
9 .system(Phase::Init, setup)
10 .system(Phase::Update, animate_bars)
11 .system(Phase::Update, handle_buttons)
12 .run();
13}
14
15struct Bars { cpu: Entity, mem: Entity, disk: Entity, net: Entity }
16impl Resource for Bars {}
17
18struct StatusLabel(Entity);
19impl Resource for StatusLabel {}
20
21struct FrameCount(u32);
22impl Resource for FrameCount {}
23
24fn setup(world: &mut World) {
25 world.insert_resource(FrameCount(0));
26
27 let root = world.spawn((
29 Node::new(0, 0, 0, 0),
30 Container,
31 Anchor::fill(),
32 ));
33
34 let header = world.spawn((
36 Node::new(0, 0, 0, 0),
37 Panel::titled("\u{2726} Decay Fancy UI").border(BorderStyle::Double).shadow(),
38 UiStyle::fg(90, 130, 220),
39 Anchor { min: (0.0, 0.0), max: (1.0, 0.0), offset: (2, 1, -2, 4) },
40 Parent(root),
41 ));
42
43 world.spawn((
45 Node::new(0, 0, 0, 0),
46 Text::new("zero-dependency TUI framework"),
47 UiStyle { fg: Some((130, 130, 145)), italic: true, ..UiStyle::new() },
48 TextAlign::Center,
49 Anchor { min: (0.0, 0.0), max: (1.0, 1.0), offset: (2, 1, -2, -1) },
50 Parent(header),
51 ));
52
53 world.spawn((
55 Node::new(0, 0, 0, 0),
56 Separator::labeled("System Monitor"),
57 Anchor { min: (0.0, 0.0), max: (1.0, 0.0), offset: (2, 4, -2, 5) },
58 Parent(root),
59 ));
60
61 world.spawn((
63 Node::new(0, 0, 0, 0), Text::new("CPU"), UiStyle::dim(),
64 Anchor { min: (0.0, 0.0), max: (0.0, 0.0), offset: (3, 5, 11, 6) },
65 Parent(root),
66 ));
67 world.spawn((
68 Node::new(0, 0, 0, 0), Text::new("Memory"), UiStyle::dim(),
69 Anchor { min: (0.0, 0.0), max: (0.0, 0.0), offset: (3, 6, 11, 7) },
70 Parent(root),
71 ));
72 world.spawn((
73 Node::new(0, 0, 0, 0), Text::new("Disk"), UiStyle::dim(),
74 Anchor { min: (0.0, 0.0), max: (0.0, 0.0), offset: (3, 7, 11, 8) },
75 Parent(root),
76 ));
77 world.spawn((
78 Node::new(0, 0, 0, 0), Text::new("Network"), UiStyle::dim(),
79 Anchor { min: (0.0, 0.0), max: (0.0, 0.0), offset: (3, 8, 11, 9) },
80 Parent(root),
81 ));
82
83 let cpu = world.spawn((
85 Node::new(0, 0, 0, 0),
86 ProgressBar::new(0.64).with_gradient((255, 100, 80)).with_label(),
87 Anchor { min: (0.15, 0.0), max: (0.55, 0.0), offset: (0, 5, 0, 6) },
88 Parent(root),
89 ));
90 let mem = world.spawn((
91 Node::new(0, 0, 0, 0),
92 ProgressBar::new(0.78)
93 .with_gradient((100, 180, 255))
94 .with_label()
95 .with_colors((60, 160, 240), (40, 40, 50)),
96 Anchor { min: (0.15, 0.0), max: (0.55, 0.0), offset: (0, 6, 0, 7) },
97 Parent(root),
98 ));
99 let disk = world.spawn((
100 Node::new(0, 0, 0, 0),
101 ProgressBar::new(0.21).with_label(),
102 Anchor { min: (0.15, 0.0), max: (0.55, 0.0), offset: (0, 7, 0, 8) },
103 Parent(root),
104 ));
105 let net = world.spawn((
106 Node::new(0, 0, 0, 0),
107 ProgressBar::classic(0.45)
108 .with_colors((200, 180, 60), (50, 50, 60))
109 .with_label(),
110 Anchor { min: (0.15, 0.0), max: (0.55, 0.0), offset: (0, 8, 0, 9) },
111 Parent(root),
112 ));
113 world.insert_resource(Bars { cpu, mem, disk, net });
114
115 world.spawn((
117 Node::new(0, 0, 0, 0),
118 Separator::labeled("Controls"),
119 Anchor { min: (0.0, 0.0), max: (1.0, 0.0), offset: (2, 10, -2, 11) },
120 Parent(root),
121 ));
122
123 world.spawn((
125 Node::new(0, 0, 0, 0),
126 Text::new("Dashboard"), Button, Interaction::None,
127 Anchor { min: (0.0, 0.0), max: (0.48, 0.0), offset: (3, 11, 0, 14) },
128 Parent(root),
129 ));
130 world.spawn((
131 Node::new(0, 0, 0, 0),
132 Text::new("Reports"), Button, Interaction::None,
133 Anchor { min: (0.5, 0.0), max: (1.0, 0.0), offset: (0, 11, -2, 14) },
134 Parent(root),
135 ));
136 world.spawn((
137 Node::new(0, 0, 0, 0),
138 Text::new("Settings"), Button, Interaction::None,
139 Anchor { min: (0.0, 0.0), max: (0.48, 0.0), offset: (3, 14, 0, 17) },
140 Parent(root),
141 ));
142 world.spawn((
143 Node::new(0, 0, 0, 0),
144 Text::new("Exit"), Button, Interaction::None,
145 Anchor { min: (0.5, 0.0), max: (1.0, 0.0), offset: (0, 14, -2, 17) },
146 Parent(root),
147 ));
148
149 world.spawn((
151 Node::new(0, 0, 0, 0),
152 Separator::labeled("Activity"),
153 Anchor { min: (0.0, 0.0), max: (1.0, 0.0), offset: (2, 17, -2, 18) },
154 Parent(root),
155 ));
156
157 world.spawn((
159 Node::new(0, 0, 0, 0),
160 Spinner::new(SpinnerStyle::Dots).with_label("Processing data..."),
161 UiStyle::fg(100, 200, 255),
162 Anchor { min: (0.0, 0.0), max: (0.5, 0.0), offset: (3, 18, 0, 19) },
163 Parent(root),
164 ));
165
166 world.spawn((
168 Node::new(0, 0, 0, 0),
169 AnimatedText::new("The quick brown fox jumps over the lazy dog", 15.0),
170 UiStyle::fg(180, 180, 195),
171 Anchor { min: (0.0, 0.0), max: (0.8, 0.0), offset: (3, 19, 0, 20) },
172 Parent(root),
173 ));
174
175 let status = world.spawn((
177 Node::new(0, 0, 0, 0),
178 Text::new("Select a button and press Enter..."),
179 UiStyle::dim(),
180 Anchor { min: (0.0, 0.0), max: (0.8, 0.0), offset: (3, 21, 0, 22) },
181 Parent(root),
182 ));
183 world.insert_resource(StatusLabel(status));
184
185 world.spawn((
187 Node::new(0, 0, 0, 0),
188 Text::new("\u{2191}\u{2193}/Tab Navigate Enter/Space Click q Quit"),
189 UiStyle::dim(),
190 Anchor { min: (0.0, 0.0), max: (0.8, 0.0), offset: (3, 22, 0, 23) },
191 Parent(root),
192 ));
193}
194
195fn animate_bars(world: &mut World) {
196 world.resource_mut::<FrameCount>().unwrap().0 += 1;
197 let t = world.resource::<FrameCount>().unwrap().0 as f32 * 0.02;
198
199 let (cpu_e, mem_e, disk_e, net_e) = {
200 let b = world.resource::<Bars>().unwrap();
201 (b.cpu, b.mem, b.disk, b.net)
202 };
203
204 world.get_mut::<ProgressBar>(cpu_e).unwrap().set(0.55 + 0.35 * (t * 0.7).sin());
205 world.get_mut::<ProgressBar>(mem_e).unwrap().set(0.70 + 0.20 * (t * 0.5 + 1.0).sin());
206 world.get_mut::<ProgressBar>(disk_e).unwrap().set(0.20 + 0.15 * (t * 0.3 + 2.0).sin());
207 world.get_mut::<ProgressBar>(net_e).unwrap().set(0.40 + 0.30 * (t * 0.9 + 3.0).sin());
208}
209
210fn handle_buttons(world: &mut World) {
211 if world.resource::<Input>().is_some_and(|i| {
212 i.just_pressed(KeyCode::Char('q')) || i.just_pressed(KeyCode::Escape)
213 }) {
214 world.resource_mut::<AppExit>().unwrap().0 = true;
215 return;
216 }
217
218 let pressed_text: Option<String> = world
219 .query::<(&Interaction, &Text)>()
220 .find(|(_, (i, _))| **i == Interaction::Pressed)
221 .map(|(_, (_, t))| t.value.clone());
222
223 if let Some(btn_text) = pressed_text {
224 if btn_text == "Exit" {
225 world.resource_mut::<AppExit>().unwrap().0 = true;
226 return;
227 }
228
229 let status_e = world.resource::<StatusLabel>().unwrap().0;
230 let msg = match btn_text.as_str() {
231 "Dashboard" => "\u{2713} Dashboard view activated",
232 "Reports" => "\u{2713} Reports view activated",
233 "Settings" => "\u{2713} Settings panel opened",
234 _ => "Unknown action",
235 };
236 if let Some(text) = world.get_mut::<Text>(status_e) {
237 text.value = msg.to_string();
238 }
239 }
240}