Skip to main content

ui/
ui.rs

1use decay::prelude::*;
2
3fn main() {
4    decay::term::spawn_window();
5
6    App::new()
7        .plugin(CorePlugins)
8        .plugin(IntroPlugin)
9        .plugin(UiPlugin)
10        .system(Phase::Init, setup)
11        .system(Phase::Update, handle_buttons)
12        .run();
13}
14
15struct StatusLabel(Entity);
16impl Resource for StatusLabel {}
17
18struct CounterLabel(Entity);
19impl Resource for CounterLabel {}
20
21struct ClickCount(u32);
22impl Resource for ClickCount {}
23
24fn setup(world: &mut World) {
25    // Root container fills the terminal
26    let root = world.spawn((
27        Node::new(0, 0, 0, 0),
28        Container,
29        Anchor::fill(),
30    ));
31
32    // Title
33    world.spawn((
34        Node::new(0, 0, 0, 0),
35        Text::new("Decay UI Demo"),
36        UiStyle { fg: Some((100, 160, 255)), bold: true, ..UiStyle::new() },
37        Anchor { min: (0.0, 0.0), max: (0.6, 0.0), offset: (4, 1, 0, 2) },
38        Parent(root),
39    ));
40
41    // Subtitle
42    world.spawn((
43        Node::new(0, 0, 0, 0),
44        Text::new("A TUI framework built on an ECS core"),
45        UiStyle::dim(),
46        Anchor { min: (0.0, 0.0), max: (0.6, 0.0), offset: (4, 2, 0, 3) },
47        Parent(root),
48    ));
49
50    // Separator
51    world.spawn((
52        Node::new(0, 0, 0, 0),
53        Separator::new(),
54        Anchor { min: (0.0, 0.0), max: (0.6, 0.0), offset: (4, 4, 0, 5) },
55        Parent(root),
56    ));
57
58    // Status label
59    let status = world.spawn((
60        Node::new(0, 0, 0, 0),
61        Text::new("Select a button and press Enter..."),
62        Anchor { min: (0.0, 0.0), max: (0.7, 0.0), offset: (4, 6, 0, 7) },
63        Parent(root),
64    ));
65    world.insert_resource(StatusLabel(status));
66
67    // Click counter
68    let counter = world.spawn((
69        Node::new(0, 0, 0, 0),
70        Text::new("Clicks: 0"),
71        UiStyle::dim(),
72        Anchor { min: (0.0, 0.0), max: (0.7, 0.0), offset: (4, 7, 0, 8) },
73        Parent(root),
74    ));
75    world.insert_resource(CounterLabel(counter));
76    world.insert_resource(ClickCount(0));
77
78    // Buttons
79    world.spawn((
80        Node::new(0, 0, 0, 0),
81        Text::new("Say Hello"), Button, Interaction::None,
82        Anchor { min: (0.0, 0.0), max: (0.45, 0.0), offset: (4, 10, 0, 13) },
83        Parent(root),
84    ));
85
86    world.spawn((
87        Node::new(0, 0, 0, 0),
88        Text::new("Say Goodbye"), Button, Interaction::None,
89        Anchor { min: (0.0, 0.0), max: (0.45, 0.0), offset: (4, 14, 0, 17) },
90        Parent(root),
91    ));
92
93    world.spawn((
94        Node::new(0, 0, 0, 0),
95        Text::new("Reset Counter"), Button, Interaction::None,
96        Anchor { min: (0.0, 0.0), max: (0.45, 0.0), offset: (4, 18, 0, 21) },
97        Parent(root),
98    ));
99
100    // Instructions
101    world.spawn((
102        Node::new(0, 0, 0, 0),
103        Text::new("\u{2191}\u{2193}/Tab  Navigate    Enter/Space  Click    q  Quit"),
104        UiStyle::dim(),
105        Anchor { min: (0.0, 0.0), max: (0.8, 0.0), offset: (4, 22, 0, 23) },
106        Parent(root),
107    ));
108}
109
110fn handle_buttons(world: &mut World) {
111    if world
112        .resource::<Input>()
113        .is_some_and(|i| i.just_pressed(KeyCode::Char('q')) || i.just_pressed(KeyCode::Escape))
114    {
115        world.resource_mut::<AppExit>().unwrap().0 = true;
116        return;
117    }
118
119    let pressed_text: Option<String> = world
120        .query::<(&Interaction, &Text)>()
121        .find(|(_, (i, _))| **i == Interaction::Pressed)
122        .map(|(_, (_, t))| t.value.clone());
123
124    if let Some(btn_text) = pressed_text {
125        let is_reset = btn_text == "Reset Counter";
126
127        if is_reset {
128            world.resource_mut::<ClickCount>().unwrap().0 = 0;
129        } else {
130            world.resource_mut::<ClickCount>().unwrap().0 += 1;
131        }
132
133        let status_e = world.resource::<StatusLabel>().unwrap().0;
134        let msg = match btn_text.as_str() {
135            "Say Hello" => "Hello, World!",
136            "Say Goodbye" => "Goodbye, World!",
137            "Reset Counter" => "Counter reset.",
138            _ => "Unknown",
139        };
140        if let Some(text) = world.get_mut::<Text>(status_e) {
141            text.value = msg.to_string();
142        }
143
144        let count = world.resource::<ClickCount>().unwrap().0;
145        let counter_e = world.resource::<CounterLabel>().unwrap().0;
146        if let Some(text) = world.get_mut::<Text>(counter_e) {
147            text.value = format!("Clicks: {}", count);
148        }
149    }
150}