old school music tracker
1use winit::keyboard::{Key, NamedKey};
2
3use crate::{
4 EventQueue, GlobalEvent, PlaybackType,
5 coordinates::{CharPosition, CharRect, FONT_SIZE16, PixelRect},
6 draw_buffer::DrawBuffer,
7 pages::PagesEnum,
8};
9
10use super::{Dialog, DialogResponse};
11
12enum Action {
13 Menu(Menu),
14 Page(PagesEnum),
15 Playback(PlaybackType),
16 // const data. Allows Recursing this inside the GlobalEvent
17 Event(fn() -> GlobalEvent),
18 // TODO: should be removed when it's all implemented
19 NotYetImplemented,
20}
21
22// Main missing, because it cant be opened from a menu
23#[derive(Clone, Copy)]
24enum Menu {
25 File,
26 Playback,
27 Sample,
28 Instrument,
29 Settings,
30}
31
32pub struct PageMenu {
33 name: &'static str,
34 rect: CharRect,
35 selected: u8,
36 pressed: bool,
37 buttons: &'static [(&'static str, Action)],
38 #[cfg(feature = "accesskit")]
39 node_id: accesskit::NodeId,
40}
41
42impl PageMenu {
43 const BACKGROUND_COLOR: u8 = 2;
44 const TOPLEFT_COLOR: u8 = 3;
45 const BOTRIGHT_COLOR: u8 = 1;
46 const fn new(
47 name: &'static str,
48 pos: CharPosition,
49 width: u8,
50 buttons: &'static [(&'static str, Action)],
51 #[cfg(feature = "accesskit")] node_id: accesskit::NodeId,
52 ) -> Self {
53 let rect = CharRect::new(
54 pos.y(),
55 // TODO: const trait
56 pos.y() + 5 + (3 * buttons.len() as u8),
57 pos.x(),
58 pos.x() + width + 3,
59 );
60
61 Self {
62 name,
63 rect,
64 selected: 0,
65 pressed: false,
66 buttons,
67 #[cfg(feature = "accesskit")]
68 node_id,
69 }
70 }
71 fn draw_button_corners(rect: CharRect, draw_buffer: &mut DrawBuffer) {
72 // let framebuffer = &mut draw_buffer.framebuffer;
73 let pixel_rect = PixelRect::from(rect);
74 // draw top right corner
75 for y in 0..FONT_SIZE16 {
76 for x in y..FONT_SIZE16 {
77 draw_buffer.set(
78 pixel_rect.top() + y,
79 pixel_rect.right() - FONT_SIZE16 + x + 1,
80 Self::BOTRIGHT_COLOR,
81 );
82 }
83 }
84 // draw botleft corner
85 for y in 0..FONT_SIZE16 {
86 for x in 0..(FONT_SIZE16 - y) {
87 draw_buffer.set(
88 pixel_rect.bot() - y,
89 pixel_rect.left() + x,
90 Self::BOTRIGHT_COLOR,
91 );
92 }
93 }
94 }
95
96 fn draw(&self, has_child: bool, draw_buffer: &mut DrawBuffer) {
97 let top_left = self.rect.top_left();
98 let top = top_left.y();
99 let left = top_left.x();
100
101 draw_buffer.draw_rect(Self::BACKGROUND_COLOR, self.rect);
102 draw_buffer.draw_string(self.name, top_left + CharPosition::new(7, 2), 3, 2);
103 // self.draw_outer_box(draw_buffer);
104 draw_buffer.draw_out_border(self.rect, Self::TOPLEFT_COLOR, Self::TOPLEFT_COLOR, 2);
105 draw_buffer.draw_out_border(
106 CharRect::new(
107 self.rect.top() + 1,
108 self.rect.bot() - 1,
109 self.rect.left() + 1,
110 self.rect.right() - 1,
111 ),
112 Self::BOTRIGHT_COLOR,
113 Self::BOTRIGHT_COLOR,
114 1,
115 );
116 for (num, (name, _)) in self.buttons.iter().enumerate() {
117 let num = u8::try_from(num).unwrap();
118 let text_color = match self.selected == num {
119 true => 11,
120 false => 0,
121 };
122
123 draw_buffer.draw_string(
124 name,
125 top_left + CharPosition::new(4, (3 * num) + 5),
126 text_color,
127 Self::BACKGROUND_COLOR,
128 );
129 let top = top + (3 * num) + 4;
130 let (top_left, bot_right) = match (self.pressed || has_child) && self.selected == num {
131 true => (Self::BOTRIGHT_COLOR, Self::TOPLEFT_COLOR),
132 false => (Self::TOPLEFT_COLOR, Self::BOTRIGHT_COLOR),
133 };
134 let rect = CharRect::new(top, top + 2, left + 2, left + self.rect.width() - 2);
135 draw_buffer.draw_out_border(rect, top_left, bot_right, 1);
136 Self::draw_button_corners(rect, draw_buffer);
137 }
138 }
139
140 fn process_input(
141 &mut self,
142 key_event: &winit::event::KeyEvent,
143 events: &mut EventQueue<'_>,
144 ) -> (DialogResponse, Option<Menu>) {
145 if key_event.logical_key == Key::Named(NamedKey::Enter) {
146 if key_event.state.is_pressed() {
147 self.pressed = true;
148 return (DialogResponse::RequestRedraw, None);
149 } else if self.pressed {
150 self.pressed = false;
151 match &self.buttons[usize::from(self.selected)].1 {
152 Action::Menu(menu) => {
153 return (DialogResponse::RequestRedraw, Some(*menu));
154 }
155 Action::Page(page) => {
156 events.push(GlobalEvent::GoToPage(*page));
157 return (DialogResponse::Close, None);
158 }
159 Action::NotYetImplemented => {
160 eprintln!("Not yet implementes");
161 return (DialogResponse::RequestRedraw, None);
162 }
163 Action::Event(global_event) => {
164 events.push(global_event());
165 return (DialogResponse::Close, None);
166 }
167 Action::Playback(p) => {
168 events.push(GlobalEvent::Playback(*p));
169 return (DialogResponse::Close, None);
170 }
171 }
172 }
173 }
174
175 if key_event.state.is_pressed() {
176 if key_event.logical_key == Key::Named(NamedKey::ArrowUp) && self.selected > 0 {
177 self.selected -= 1;
178 self.pressed = false;
179 return (DialogResponse::RequestRedraw, None);
180 } else if key_event.logical_key == Key::Named(NamedKey::ArrowDown)
181 && usize::from(self.selected) < self.buttons.len() - 1
182 {
183 self.selected += 1;
184 self.pressed = false;
185 return (DialogResponse::RequestRedraw, None);
186 }
187 }
188
189 (DialogResponse::None, None)
190 }
191
192 #[cfg(feature = "accesskit")]
193 fn build_tree(
194 &self,
195 tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
196 ) -> crate::AccessResponse {
197 use accesskit::{Action, Node, NodeId, Rect, Role};
198
199 use crate::AccessResponse;
200 let mut root = Node::new(Role::Dialog);
201 root.set_label(self.name);
202 root.set_bounds(dbg!(Rect::from(self.rect)));
203 let selected = NodeId(u64::from(self.selected) + self.node_id.0 + 1);
204
205 // root of the sub_menu. Will be set as the child of the selected button
206 // let sub_menu = self.sub_menu.as_ref().map(|m| {
207 // let resp = m.build_tree(tree);
208
209 // selected = resp.selected;
210
211 // resp.root
212 // });
213
214 for (num, (name, _)) in self.buttons.iter().enumerate() {
215 let mut node = Node::new(Role::Button);
216 node.add_action(Action::Click);
217 node.set_keyboard_shortcut("Enter");
218 node.set_label(*name);
219 if usize::from(self.selected) == num {
220 node.set_selected(true);
221 // if let Some(id) = sub_menu {
222 // node.push_child(id);
223 // }
224 }
225 let id = NodeId(u64::try_from(num).unwrap() + self.node_id.0 + 1);
226 tree.push((id, node));
227 root.push_child(id);
228 }
229
230 tree.push((self.node_id, root));
231 AccessResponse {
232 root: self.node_id,
233 selected,
234 }
235 }
236}
237
238pub struct MainMenu {
239 main: PageMenu,
240 child: Option<PageMenu>,
241}
242
243impl Dialog for MainMenu {
244 fn draw(&self, draw_buffer: &mut DrawBuffer) {
245 self.main.draw(self.child.is_some(), draw_buffer);
246 if let Some(child) = &self.child {
247 // the child never has a child
248 child.draw(false, draw_buffer);
249 }
250 }
251
252 fn process_input(
253 &mut self,
254 key_event: &winit::event::KeyEvent,
255 _modifiers: &winit::event::Modifiers,
256 events: &mut EventQueue<'_>,
257 ) -> DialogResponse {
258 // check for close dialog
259 if key_event.state.is_pressed() && key_event.logical_key == Key::Named(NamedKey::Escape) {
260 if self.child.is_some() {
261 self.child = None;
262 events.push(GlobalEvent::ConstRedraw);
263 return DialogResponse::RequestRedraw;
264 } else {
265 return DialogResponse::Close;
266 }
267 }
268
269 if let Some(child) = &mut self.child {
270 let (resp, open_child) = child.process_input(key_event, events);
271 assert!(open_child.is_none(), "the child can't open a child");
272 resp
273 } else {
274 let (resp, open_child) = self.main.process_input(key_event, events);
275 if let Some(open_child) = open_child {
276 assert!(
277 resp == DialogResponse::RequestRedraw,
278 "child open is always in combination with redraw"
279 );
280 let child = match open_child {
281 Menu::File => PageMenu::file(),
282 Menu::Playback => PageMenu::playback(),
283 Menu::Sample => PageMenu::sample(),
284 Menu::Instrument => PageMenu::instrument(),
285 Menu::Settings => PageMenu::settings(),
286 };
287 self.child = Some(child);
288 }
289 resp
290 }
291 }
292
293 #[cfg(feature = "accesskit")]
294 fn build_tree(
295 &self,
296 _tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
297 ) -> crate::AccessResponse {
298 todo!("look at PageMenu commented out stuff");
299 }
300}
301
302impl MainMenu {
303 pub fn new() -> Self {
304 Self {
305 main: PageMenu::main(),
306 child: None,
307 }
308 }
309}
310
311impl PageMenu {
312 pub const fn main() -> Self {
313 Self::new(
314 "Main Menu",
315 CharPosition::new(6, 11),
316 29,
317 &[
318 ("File Menu...", Action::Menu(Menu::File)),
319 ("Playback Menu...", Action::Menu(Menu::Playback)),
320 (
321 "View Patterns (F2)",
322 Action::Page(PagesEnum::Pattern),
323 ),
324 ("Sample Menu...", Action::Menu(Menu::Sample)),
325 ("Instrument Menu...", Action::Menu(Menu::Instrument)),
326 (
327 "View Orders/Panning (F11)",
328 Action::Page(PagesEnum::OrderList),
329 ),
330 (
331 "View Variables (F12)",
332 Action::Page(PagesEnum::SongDirectoryConfig),
333 ),
334 ("Message Editor (Shift-F9)", Action::NotYetImplemented),
335 ("Settings Menu...", Action::Menu(Menu::Settings)),
336 ("Help! (F1)", Action::Page(PagesEnum::Help)),
337 ],
338 #[cfg(feature = "accesskit")]
339 accesskit::NodeId(1_000_000_000),
340 )
341 }
342
343 pub const fn file() -> Self {
344 Self::new(
345 "File Menu",
346 CharPosition::new(25, 13),
347 26,
348 &[
349 ("Load... (F9)", Action::NotYetImplemented),
350 ("New... (Ctrl-N)", Action::NotYetImplemented),
351 ("Save Current (Ctrl-S)", Action::NotYetImplemented),
352 ("Save As... (F10)", Action::NotYetImplemented),
353 ("Export... (Shift-F10)", Action::NotYetImplemented),
354 ("Message Log (Ctrl-F11)", Action::NotYetImplemented),
355 (
356 "Quit (Ctrl-Q)",
357 Action::Event(|| GlobalEvent::CloseRequested),
358 ),
359 ],
360 #[cfg(feature = "accesskit")]
361 accesskit::NodeId(1_000_000_100),
362 )
363 }
364
365 pub const fn playback() -> Self {
366 Self::new(
367 "Playback Menu",
368 CharPosition::new(25, 13),
369 31,
370 &[
371 ("Show Infopage (F5)", Action::NotYetImplemented),
372 (
373 "Play Song (Ctrl-F5)",
374 Action::Playback(PlaybackType::Song),
375 ),
376 (
377 "Play Pattern (F6)",
378 Action::Playback(PlaybackType::Pattern),
379 ),
380 (
381 "Play from Order (Shift-F6)",
382 Action::Playback(PlaybackType::FromOrder),
383 ),
384 ("Play from Mark/Cursor (F7)", Action::NotYetImplemented),
385 (
386 "Stop (F8)",
387 Action::Playback(PlaybackType::Stop),
388 ),
389 ("Reinit Soundcard (Ctrl-I)", Action::NotYetImplemented),
390 ("Driver Screen (Shift-F5)", Action::NotYetImplemented),
391 ("Calculate Length (Ctrl-P)", Action::NotYetImplemented),
392 ],
393 #[cfg(feature = "accesskit")]
394 accesskit::NodeId(1_000_000_200),
395 )
396 }
397
398 pub const fn sample() -> Self {
399 Self::new(
400 "Sample Menu",
401 CharPosition::new(25, 20),
402 29,
403 &[
404 (
405 "Sample List (F3)",
406 Action::Page(PagesEnum::SampleList),
407 ),
408 ("Sample Library (Ctrl-F3)", Action::NotYetImplemented),
409 ],
410 #[cfg(feature = "accesskit")]
411 accesskit::NodeId(1_000_000_300),
412 )
413 }
414
415 pub const fn instrument() -> Self {
416 Self::new(
417 "Instrument Menu",
418 CharPosition::new(20, 23),
419 33,
420 &[
421 ("Instrument List (F4)", Action::NotYetImplemented),
422 ("Instrument Library (Ctrl-F4)", Action::NotYetImplemented),
423 ],
424 #[cfg(feature = "accesskit")]
425 accesskit::NodeId(1_000_000_400),
426 )
427 }
428
429 pub const fn settings() -> Self {
430 Self::new(
431 "Settings Menu",
432 CharPosition::new(22, 25),
433 38,
434 &[
435 (
436 "Preferences (Shift-F5)",
437 Action::NotYetImplemented,
438 ),
439 (
440 "MIDI Configuration (Shift-F1)",
441 Action::NotYetImplemented,
442 ),
443 (
444 "System Configuration (Ctrl-F1)",
445 Action::NotYetImplemented,
446 ),
447 (
448 "Palette Editor (Ctrl-F12)",
449 Action::NotYetImplemented,
450 ),
451 (
452 "Font Editor (Shift-F12)",
453 Action::NotYetImplemented,
454 ),
455 (
456 "Toggle Fullscreen (Ctrl-Alt-Enter)",
457 Action::NotYetImplemented,
458 ),
459 ],
460 #[cfg(feature = "accesskit")]
461 accesskit::NodeId(1_000_000_500),
462 )
463 }
464}