20_menu_bar/
20_menu_bar.rs1use crossterm::event::KeyCode;
14use telex::prelude::*;
15use telex::Color;
16
17telex::require_api!(0, 2);
18
19fn main() {
20 telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
21}
22
23struct App;
24
25impl Component for App {
26 fn render(&self, cx: Scope) -> View {
27 let show_help = state!(cx, || false);
28
29 cx.use_command(
31 KeyBinding::key(KeyCode::F(1)),
32 with!(show_help => move || show_help.update(|v| *v = !*v)),
33 );
34
35 let active_menu = state!(cx, || Option::<usize>::None);
37 let highlighted_menu = state!(cx, || 0usize);
38 let selected_item = state!(cx, || 0usize);
39
40 let message = state!(cx, || "Use Tab to focus menu bar, then Enter to open".to_string());
42 let counter = state!(cx, || 0i32);
43
44 let handle_command = with!(message, counter, active_menu, selected_item => move |cmd_id: &'static str| {
46 let msg = match cmd_id {
47 "file.new" => "Created new file".to_string(),
48 "file.open" => "Opening file...".to_string(),
49 "file.save" => "File saved".to_string(),
50 "file.quit" => "Use Ctrl+Q to quit".to_string(),
51 "edit.undo" => "Undone".to_string(),
52 "edit.redo" => "Redone".to_string(),
53 "edit.cut" => "Cut to clipboard".to_string(),
54 "edit.copy" => "Copied to clipboard".to_string(),
55 "edit.paste" => "Pasted from clipboard".to_string(),
56 "counter.increment" => {
57 counter.update(|n| *n += 1);
58 format!("Counter: {}", counter.get())
59 }
60 "counter.decrement" => {
61 counter.update(|n| *n -= 1);
62 format!("Counter: {}", counter.get())
63 }
64 "counter.reset" => {
65 counter.set(0);
66 "Counter reset".to_string()
67 }
68 _ => format!("Unknown: {}", cmd_id),
69 };
70 message.set(msg);
71 active_menu.set(None);
73 selected_item.set(0);
74 });
75
76 let on_menu_change = with!(active_menu, highlighted_menu, selected_item => move |idx: usize| {
78 if active_menu.get() == Some(idx) {
79 active_menu.set(None);
81 } else {
82 active_menu.set(Some(idx));
83 highlighted_menu.set(idx); selected_item.set(0);
85 }
86 });
87
88 let on_highlight_change = with!(highlighted_menu => move |idx: usize| {
90 highlighted_menu.set(idx);
91 });
92
93 let on_item_change = with!(selected_item => move |idx: usize| {
95 selected_item.set(idx);
96 });
97
98 let file_menu = Menu::new("File")
100 .command_with_shortcut("file.new", "New", "Ctrl+N")
101 .command_with_shortcut("file.open", "Open", "Ctrl+O")
102 .command_with_shortcut("file.save", "Save", "Ctrl+S")
103 .separator()
104 .command_with_shortcut("file.quit", "Quit", "Ctrl+Q");
105
106 let edit_menu = Menu::new("Edit")
107 .command_with_shortcut("edit.undo", "Undo", "Ctrl+Z")
108 .command_with_shortcut("edit.redo", "Redo", "Ctrl+Y")
109 .separator()
110 .command_with_shortcut("edit.cut", "Cut", "Ctrl+X")
111 .command_with_shortcut("edit.copy", "Copy", "Ctrl+C")
112 .command_with_shortcut("edit.paste", "Paste", "Ctrl+V");
113
114 let counter_menu = Menu::new("Counter")
115 .command("counter.increment", "Increment")
116 .command("counter.decrement", "Decrement")
117 .separator()
118 .command("counter.reset", "Reset to Zero");
119
120 View::vstack()
121 .child(
122 View::menu_bar()
123 .menu(file_menu)
124 .menu(edit_menu)
125 .menu(counter_menu)
126 .active_menu(active_menu.get())
127 .highlighted_menu(highlighted_menu.get())
128 .selected_item(selected_item.get())
129 .on_select(handle_command)
130 .on_menu_change(on_menu_change)
131 .on_highlight_change(on_highlight_change)
132 .on_item_change(on_item_change)
133 .build(),
134 )
135 .child(
136 View::boxed()
137 .flex(1)
138 .border(true)
139 .padding(2)
140 .child(
141 View::vstack()
142 .spacing(1)
143 .child(View::styled_text("Menu Bar Demo").bold().build())
144 .child(
145 View::styled_text(format!("Counter: {}", counter.get()))
146 .color(Color::Cyan)
147 .bold()
148 .build(),
149 )
150 .child(
151 View::styled_text(format!("Status: {}", message.get()))
152 .dim()
153 .build(),
154 )
155 .child(View::spacer())
156 .child(View::styled_text("Keyboard Navigation:").bold().build())
157 .child(View::text(" Tab Focus menu bar"))
158 .child(View::text(" Enter Open menu / Execute item"))
159 .child(View::text(" Up/Down Navigate menu items"))
160 .child(View::text(" Left/Right Switch between menus"))
161 .child(View::text(" Escape Close menu"))
162 .child(View::text(" Ctrl+Q Quit"))
163 .child(View::text(" F1 Help"))
164 .build(),
165 )
166 .build(),
167 )
168 .child(
169 View::modal()
170 .visible(show_help.get())
171 .title("Example 20: Menu Bar")
172 .on_dismiss(with!(show_help => move || show_help.set(false)))
173 .child(
174 View::vstack()
175 .child(View::styled_text("What you're seeing").bold().build())
176 .child(View::text("• Dropdown menu bar with keyboard nav"))
177 .child(View::text("• Menu items with shortcuts"))
178 .child(View::text("• Separators in menus"))
179 .child(View::gap(1))
180 .child(View::styled_text("Key concepts").bold().build())
181 .child(View::text("• View::menu_bar() creates menu system"))
182 .child(View::text("• Menu::new().command() adds items"))
183 .child(View::text("• .command_with_shortcut() shows key hints"))
184 .child(View::text("• on_select receives command ID"))
185 .child(View::gap(1))
186 .child(View::styled_text("Try this").bold().build())
187 .child(View::text("• Tab to menu, Enter to open"))
188 .child(View::text("• Arrow keys navigate menus"))
189 .child(View::text("• Try the Counter menu"))
190 .child(View::gap(1))
191 .child(View::styled_text("Next up").bold().build())
192 .child(View::text("→ 21_toasts: toast notifications"))
193 .child(View::gap(1))
194 .child(View::styled_text("Press Escape to close").dim().build())
195 .build(),
196 )
197 .build(),
198 )
199 .build()
200 }
201}