Skip to main content

minimal/
minimal.rs

1//! The smallest useful tuile app: a switch and an input with focus, mouse and animation.
2//!
3//! ```sh
4//! cargo run -p tuile --example minimal
5//! ```
6
7use tuile::prelude::*;
8
9#[derive(Clone, Copy, PartialEq)]
10enum Id {
11    Dark,
12    Name,
13    Save,
14}
15
16struct Demo {
17    dark: SwitchState,
18    name: InputState,
19    save: ButtonState,
20    focus: Focus<Id>,
21    toasts: Toaster,
22}
23
24impl App for Demo {
25    fn draw(&mut self, frame: &mut Frame, now: Instant) {
26        let area = frame.area();
27        let buf = frame.buffer_mut();
28        let th = theme::current();
29        fill(buf, area, th.background);
30        let card = center(area, 46, 13);
31        let inner = Border::Round.draw_titled(
32            buf,
33            card,
34            th.border_blurred,
35            th.background,
36            "tuile",
37            Alignment::Left,
38        );
39        let [a, b, c] = Layout::vertical([
40            Constraint::Length(3),
41            Constraint::Length(3),
42            Constraint::Length(3),
43        ])
44        .areas(pad(inner, 1, 0));
45
46        Switch::new()
47            .label("Dark mode")
48            .focused(self.focus.is(Id::Dark))
49            .now(now)
50            .render(a, buf, &mut self.dark);
51        Input::new()
52            .placeholder("Your name")
53            .focused(self.focus.is(Id::Name))
54            .now(now)
55            .render(b, buf, &mut self.name);
56        Button::new("Save")
57            .variant(Variant::Primary)
58            .focused(self.focus.is(Id::Save))
59            .now(now)
60            .render(Rect { width: 16, ..c }, buf, &mut self.save);
61        put(
62            buf,
63            c.x + 18,
64            c.y + 1,
65            "Tab: focus · Enter: save · ^c: quit",
66            c.width.saturating_sub(18),
67            st(th.text_muted, th.background),
68        );
69
70        ToastStack::new()
71            .now(now)
72            .render(area, buf, &mut self.toasts);
73    }
74
75    fn update(&mut self, now: Instant) {
76        self.toasts.tick(now);
77    }
78
79    fn event(&mut self, ev: Event, _now: Instant) -> Flow {
80        if let Event::Key(k) = &ev {
81            if ctrl(k, 'c') {
82                return Flow::Quit;
83            }
84            if self.focus.handle_key(*k).is_consumed() {
85                return Flow::Continue;
86            }
87        }
88        let out = match (&ev, self.focus.current()) {
89            (Event::Key(_), Some(Id::Dark)) => self.dark.handle(&ev),
90            (Event::Key(_), Some(Id::Name)) => self.name.handle(&ev),
91            (Event::Key(_), Some(Id::Save)) => self.save.handle(&ev),
92            // every widget sees every mouse event and checks its own rect
93            (Event::Mouse(_), _) => {
94                let mut o = self.dark.handle(&ev);
95                if o.is_changed() {
96                    self.focus.set(Id::Dark);
97                }
98                let n = self.name.handle(&ev);
99                if n.is_consumed() {
100                    self.focus.set(Id::Name);
101                }
102                let s = self.save.handle(&ev);
103                if s.is_changed() {
104                    self.focus.set(Id::Save);
105                }
106                o |= n | s;
107                o
108            }
109            _ => Outcome::Ignored,
110        };
111        if out.is_changed() {
112            if self.focus.is(Id::Dark) {
113                theme::set_by_name(if self.dark.on {
114                    "textual-dark"
115                } else {
116                    "textual-light"
117                });
118            }
119            if self.focus.is(Id::Save) {
120                self.toasts.success(format!("Saved {:?}", self.name.value));
121            }
122        }
123        Flow::Continue
124    }
125
126    fn animating(&self, now: Instant) -> bool {
127        self.dark.animating(now) || self.save.animating(now) || self.toasts.animating(now)
128    }
129}
130
131fn main() -> std::io::Result<()> {
132    theme::set_by_name("textual-dark");
133    run(&mut Demo {
134        dark: SwitchState::new(true),
135        name: InputState::new(),
136        save: ButtonState::new(),
137        focus: Focus::new([Id::Dark, Id::Name, Id::Save]),
138        toasts: Toaster::new(),
139    })
140}