1use 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 (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}