old school music tracker
1use std::str::from_utf8;
2use std::{array, io::Write};
3
4use torque_tracker_engine::project::song::{Pan, SongOperation};
5use torque_tracker_engine::{file::impulse_format::header::PatternOrder, project::song::Song};
6use winit::keyboard::{Key, ModifiersState, NamedKey};
7
8use crate::header::HeaderEvent;
9use crate::widgets::{NextWidget, Widget, WidgetResponse};
10use crate::{EventQueue, GlobalEvent, send_song_op};
11use crate::{
12 coordinates::{CharPosition, CharRect},
13 widgets::slider::Slider,
14};
15
16use super::{Page, PageEvent, PageResponse};
17
18#[derive(Debug, Clone)]
19pub enum OrderListPageEvent {
20 SetVolumeCurrent(u8),
21 SetPanCurrent(u8),
22 SetPlayback(Option<u16>),
23}
24
25#[derive(Debug, Copy, Clone)]
26pub enum Mode {
27 Panning,
28 Volume,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32enum Cursor {
33 Order,
34 VolPan(u8),
35}
36
37#[derive(Debug)]
38struct OrderCursor {
39 order: u16,
40 // 0 - 2 valid
41 digit: u8,
42}
43
44#[derive(Debug)]
45pub struct OrderListPage {
46 mode: Mode,
47 cursor: Cursor,
48 order_cursor: OrderCursor,
49 order_draw: u16,
50 order_playback: Option<u16>,
51 pattern_order: [PatternOrder; Song::MAX_ORDERS],
52 volume: [Slider<0, 64>; 64],
53 pan: [Slider<0, 64>; 64],
54}
55
56impl OrderListPage {
57 const PAGE_ID: u64 = 11_000_000_000;
58
59 pub fn new() -> Self {
60 Self {
61 cursor: Cursor::Order,
62 mode: Mode::Panning,
63 order_cursor: OrderCursor { order: 0, digit: 0 },
64 order_draw: 0,
65 pattern_order: [PatternOrder::EndOfSong; Song::MAX_ORDERS],
66 order_playback: None,
67 volume: array::from_fn(|idx| {
68 let idx = u8::try_from(idx).unwrap();
69 let pos = if idx >= 32 {
70 CharPosition::new(65, 15 + idx - 32)
71 } else {
72 CharPosition::new(31, 15 + idx)
73 };
74 Slider::new(
75 64,
76 pos,
77 8,
78 NextWidget::default(),
79 |value| {
80 GlobalEvent::Page(PageEvent::OrderList(
81 OrderListPageEvent::SetVolumeCurrent(value.try_into().unwrap()),
82 ))
83 },
84 #[cfg(feature = "accesskit")]
85 (
86 accesskit::NodeId(Self::PAGE_ID + u64::from(idx)),
87 format!("Volume Channel {idx}").into_boxed_str(),
88 ),
89 )
90 }),
91 pan: array::from_fn(|idx| {
92 let idx = u8::try_from(idx).unwrap();
93 let pos = if idx >= 32 {
94 CharPosition::new(65, 15 + idx - 32)
95 } else {
96 CharPosition::new(31, 15 + idx)
97 };
98 Slider::new(
99 32,
100 pos,
101 8,
102 NextWidget::default(),
103 |value| {
104 GlobalEvent::Page(PageEvent::OrderList(OrderListPageEvent::SetPanCurrent(
105 u8::try_from(value).unwrap(),
106 )))
107 },
108 #[cfg(feature = "accesskit")]
109 (
110 accesskit::NodeId(Self::PAGE_ID + u64::from(idx)),
111 format!("Pan Channel {idx}").into_boxed_str(),
112 ),
113 )
114 }),
115 }
116 }
117
118 pub fn process_event(&mut self, event: OrderListPageEvent) -> PageResponse {
119 match event {
120 OrderListPageEvent::SetVolumeCurrent(vol) => {
121 let cursor = match self.cursor {
122 Cursor::Order => unreachable!(
123 "when a set volume event is created a volume slider has to be selected"
124 ),
125 Cursor::VolPan(c) => c,
126 };
127 self.volume[usize::from(cursor - 1)]
128 .try_set(i16::from(vol))
129 .expect("the value was created from the slider, so it has to fit.");
130 send_song_op(SongOperation::SetVolume(cursor - 1, vol));
131 }
132 OrderListPageEvent::SetPanCurrent(pan) => {
133 let cursor = match self.cursor {
134 Cursor::Order => unreachable!(
135 "when a set pan event is created a pan slider has to be selected"
136 ),
137 Cursor::VolPan(c) => c,
138 };
139 self.pan[usize::from(cursor - 1)]
140 .try_set(i16::from(pan))
141 .expect("the event was created from the slider, so has to fit.");
142 send_song_op(SongOperation::SetPan(cursor - 1, Pan::Value(pan)));
143 }
144 OrderListPageEvent::SetPlayback(o) => self.order_playback = o,
145 };
146 PageResponse::RequestRedraw
147 }
148
149 pub fn switch_mode(&mut self) {
150 self.mode = match self.mode {
151 Mode::Panning => Mode::Volume,
152 Mode::Volume => Mode::Panning,
153 }
154 }
155
156 pub fn reset_mode(&mut self) {
157 self.mode = Mode::Panning
158 }
159
160 pub fn mode(&self) -> Mode {
161 self.mode
162 }
163
164 fn send_order_position(&self, events: &mut EventQueue<'_>) {
165 events.push(GlobalEvent::Header(HeaderEvent::SetOrder(
166 self.order_cursor.order,
167 )));
168 }
169
170 fn send_order_len(&self, events: &mut EventQueue<'_>) {
171 let order_len = self
172 .pattern_order
173 .iter()
174 .take_while(|o| **o != PatternOrder::EndOfSong)
175 .count();
176 events.push(GlobalEvent::Header(HeaderEvent::SetOrderLen(
177 u16::try_from(order_len).unwrap(),
178 )));
179 }
180
181 fn order_cursor_up(&mut self, count: u16, events: &mut EventQueue<'_>) -> PageResponse {
182 debug_assert!(count != 0, "why would you do this");
183 if self.order_cursor.order == 0 {
184 return PageResponse::None;
185 }
186
187 self.order_cursor.order = self.order_cursor.order.saturating_sub(count);
188 self.order_draw = self.order_draw.min(self.order_cursor.order);
189 self.send_order_position(events);
190 PageResponse::RequestRedraw
191 }
192
193 fn order_cursor_down(&mut self, count: u16, events: &mut EventQueue<'_>) -> PageResponse {
194 debug_assert!(count != 0, "why would you do this");
195 if self.order_cursor.order == 255 {
196 return PageResponse::None;
197 }
198
199 self.order_cursor.order = self.order_cursor.order.saturating_add(count);
200 self.order_draw = self
201 .order_draw
202 .max(self.order_cursor.order.saturating_sub(31));
203 self.send_order_position(events);
204 PageResponse::RequestRedraw
205 }
206}
207
208impl Page for OrderListPage {
209 fn draw(&mut self, draw_buffer: &mut crate::draw_buffer::DrawBuffer) {
210 fn write_pattern_order(order: PatternOrder, buf: &mut [u8; 3]) -> &str {
211 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(buf);
212 match order {
213 PatternOrder::Number(n) => write!(&mut curse, "{:03}", n).unwrap(),
214 PatternOrder::EndOfSong => write!(&mut curse, "---").unwrap(),
215 PatternOrder::SkipOrder => write!(&mut curse, "+++").unwrap(),
216 }
217 from_utf8(buf).unwrap()
218 }
219
220 // draw heading for vol pan columns
221 match self.mode {
222 Mode::Panning => {
223 draw_buffer.draw_string("L M R", CharPosition::new(31, 14), 0, 3);
224 draw_buffer.draw_string("L M R", CharPosition::new(65, 14), 0, 3);
225 }
226 Mode::Volume => {
227 draw_buffer.draw_string(" Volumes ", CharPosition::new(31, 14), 0, 3);
228 draw_buffer.draw_string(" Volumes ", CharPosition::new(65, 14), 0, 3);
229 }
230 }
231
232 const ORDER_BASE_POS: CharPosition = CharPosition::new(2, 15);
233 let mut buf = [0; 3];
234 for (pos, order) in (self.order_draw..=self.order_draw + 31).enumerate() {
235 let pos = u8::try_from(pos).unwrap();
236 // row index
237 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
238 write!(&mut curse, "{:03}", order).unwrap();
239 let text_color = if Some(order) == self.order_playback {
240 3
241 } else {
242 0
243 };
244 draw_buffer.draw_string(
245 from_utf8(&buf).unwrap(),
246 ORDER_BASE_POS + CharPosition::new(0, pos),
247 text_color,
248 2,
249 );
250 // row value
251 draw_buffer.draw_string(
252 write_pattern_order(self.pattern_order[usize::from(order)], &mut buf),
253 ORDER_BASE_POS + CharPosition::new(4, pos),
254 2,
255 0,
256 );
257 }
258
259 // draw channel and numbers
260 // TODO: make the channel number strings const
261 const CHANNEL_BASE_LEFT: CharPosition = CharPosition::new(20, 15);
262 const CHANNEL_BASE_RIGHT: CharPosition = CharPosition::new(54, 15);
263 const CHANNEL: &str = "Channel";
264 let mut buf = [0; 2];
265 for row in 0..32 {
266 draw_buffer.draw_string(CHANNEL, CHANNEL_BASE_LEFT + CharPosition::new(0, row), 0, 2);
267 draw_buffer.draw_string(
268 CHANNEL,
269 CHANNEL_BASE_RIGHT + CharPosition::new(0, row),
270 0,
271 2,
272 );
273 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
274 write!(&mut curse, "{:02}", row + 1).unwrap();
275 draw_buffer.draw_string(
276 from_utf8(&buf).unwrap(),
277 CHANNEL_BASE_LEFT + CharPosition::new(8, row),
278 0,
279 2,
280 );
281 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
282 write!(&mut curse, "{:02}", row + 33).unwrap();
283 draw_buffer.draw_string(
284 from_utf8(&buf).unwrap(),
285 CHANNEL_BASE_RIGHT + CharPosition::new(8, row),
286 0,
287 2,
288 );
289 }
290
291 // draw cursor
292 match self.cursor {
293 Cursor::Order => {
294 let mut buf = [0; 3];
295 let row = write_pattern_order(
296 self.pattern_order[usize::from(self.order_cursor.order)],
297 &mut buf,
298 );
299 draw_buffer.draw_char(
300 font8x8::UnicodeFonts::get(
301 &font8x8::BASIC_FONTS,
302 row.chars()
303 .nth(usize::from(self.order_cursor.digit))
304 .unwrap(),
305 )
306 .unwrap(),
307 ORDER_BASE_POS
308 + CharPosition::new(
309 4 + self.order_cursor.digit,
310 u8::try_from(self.order_cursor.order - self.order_draw).unwrap(),
311 ),
312 0,
313 3,
314 );
315 }
316 Cursor::VolPan(c) => {
317 let mut buf = [0; 2];
318 let mut curse: std::io::Cursor<&mut [u8]> = std::io::Cursor::new(&mut buf);
319 write!(&mut curse, "{:02}", c).unwrap();
320 let pos = if c <= 32 {
321 CHANNEL_BASE_LEFT + CharPosition::new(0, c - 1)
322 } else {
323 CHANNEL_BASE_RIGHT + CharPosition::new(0, c - 33)
324 };
325 draw_buffer.draw_string(CHANNEL, pos, 3, 2);
326 draw_buffer.draw_string(
327 from_utf8(&buf).unwrap(),
328 pos + CharPosition::new(8, 0),
329 3,
330 2,
331 );
332 }
333 }
334
335 // draw sliders
336 match self.mode {
337 Mode::Panning => {
338 for (idx, pan) in self.pan.iter().enumerate() {
339 pan.draw(
340 draw_buffer,
341 self.cursor == Cursor::VolPan(u8::try_from(idx + 1).unwrap()),
342 );
343 }
344 }
345 Mode::Volume => {
346 for (idx, vol) in self.volume.iter().enumerate() {
347 vol.draw(
348 draw_buffer,
349 self.cursor == Cursor::VolPan(u8::try_from(idx + 1).unwrap()),
350 );
351 }
352 }
353 }
354 }
355
356 fn draw_constant(&mut self, draw_buffer: &mut crate::draw_buffer::DrawBuffer) {
357 // background fill
358 draw_buffer.draw_rect(2, CharRect::PAGE_AREA);
359 // box around order list
360 draw_buffer.draw_in_box(CharRect::new(14, 14 + 33, 5, 9), 2, 1, 3, 2);
361 // boxes aroung pan or vol
362 draw_buffer.draw_in_box(CharRect::new(13, 14 + 33, 30, 30 + 10), 2, 3, 3, 2);
363 draw_buffer.draw_in_box(CharRect::new(13, 14 + 33, 64, 64 + 10), 2, 3, 3, 2);
364 }
365
366 fn process_key_event(
367 &mut self,
368 modifiers: &winit::event::Modifiers,
369 key_event: &winit::event::KeyEvent,
370 events: &mut EventQueue<'_>,
371 ) -> PageResponse {
372 match self.cursor {
373 Cursor::Order => {
374 if key_event.logical_key == Key::Named(NamedKey::Tab)
375 && key_event.state.is_pressed()
376 {
377 if modifiers.state() == ModifiersState::SHIFT {
378 self.cursor = Cursor::VolPan(33);
379 return PageResponse::RequestRedraw;
380 } else if modifiers.state().is_empty() {
381 self.cursor = Cursor::VolPan(1);
382 return PageResponse::RequestRedraw;
383 }
384 }
385 if modifiers.state().is_empty() && key_event.state.is_pressed() {
386 if key_event.logical_key == Key::Named(NamedKey::ArrowUp) {
387 return self.order_cursor_up(1, events);
388 } else if key_event.logical_key == Key::Named(NamedKey::ArrowDown) {
389 return self.order_cursor_down(1, events);
390 } else if key_event.logical_key == Key::Named(NamedKey::PageDown) {
391 return self.order_cursor_down(16, events);
392 } else if key_event.logical_key == Key::Named(NamedKey::PageUp) {
393 return self.order_cursor_up(16, events);
394 } else if key_event.logical_key == Key::Named(NamedKey::ArrowLeft) {
395 self.order_cursor.digit = if self.order_cursor.digit == 0 {
396 2
397 } else {
398 self.order_cursor.digit - 1
399 };
400 return PageResponse::RequestRedraw;
401 } else if key_event.logical_key == Key::Named(NamedKey::ArrowRight) {
402 self.order_cursor.digit = if self.order_cursor.digit == 2 {
403 0
404 } else {
405 self.order_cursor.digit + 1
406 };
407 return PageResponse::RequestRedraw;
408 } else if key_event.logical_key == Key::Named(NamedKey::Tab) {
409 self.cursor = Cursor::VolPan(1); // go to channel 1
410 return PageResponse::RequestRedraw;
411 } else if let Key::Character(text) = &key_event.logical_key {
412 let mut chars = text.chars();
413 let first = chars.next();
414 assert!(chars.next().is_none());
415 match first {
416 Some('+') => {
417 self.pattern_order[usize::from(self.order_cursor.order)] =
418 PatternOrder::SkipOrder;
419 send_song_op(SongOperation::SetOrder(
420 self.order_cursor.order,
421 PatternOrder::SkipOrder,
422 ));
423 self.order_cursor_down(1, events);
424 self.order_cursor.digit = 0;
425 return PageResponse::RequestRedraw;
426 }
427 Some('-') | Some('.') => {
428 self.pattern_order[usize::from(self.order_cursor.order)] =
429 PatternOrder::EndOfSong;
430 send_song_op(SongOperation::SetOrder(
431 self.order_cursor.order,
432 PatternOrder::EndOfSong,
433 ));
434 self.order_cursor_down(1, events);
435 self.order_cursor.digit = 0;
436 return PageResponse::RequestRedraw;
437 }
438 Some(num) if num.is_ascii_digit() => {
439 let mut buf = [0; 1];
440 let string = num.encode_utf8(&mut buf);
441 let num: u8 = string.parse().unwrap();
442 assert!(num <= 9);
443 let cursor = usize::from(self.order_cursor.order);
444 let new_num =
445 match (self.pattern_order[cursor], self.order_cursor.digit) {
446 // hooooly shit, i love match
447 (PatternOrder::Number(old_num), 0) => u8::try_from(
448 (u16::from(num) * 100 + u16::from(old_num % 100))
449 .min(199),
450 )
451 .unwrap(),
452 (PatternOrder::Number(old_num), 1) => {
453 num * 10 + 100 * (old_num / 100) + old_num % 10
454 }
455 (PatternOrder::Number(old_num), 2) => {
456 num + 10 * (old_num / 10)
457 }
458 (PatternOrder::Number(_), _) => unreachable!(),
459 (_, 0) => num.checked_mul(100).unwrap_or(199).min(199),
460 (_, 1) => 10 * num,
461 (_, 2) => num,
462 _ => unreachable!(),
463 };
464 self.pattern_order[cursor] = PatternOrder::Number(new_num);
465 send_song_op(SongOperation::SetOrder(
466 self.order_cursor.order,
467 PatternOrder::Number(new_num),
468 ));
469 match self.order_cursor.digit {
470 0 => self.order_cursor.digit = 1,
471 1 => self.order_cursor.digit = 2,
472 2 => {
473 self.order_cursor.digit = 0;
474 self.order_cursor_down(1, events);
475 }
476 _ => unreachable!(),
477 }
478 self.send_order_len(events);
479 return PageResponse::RequestRedraw;
480 }
481 _ => return PageResponse::None,
482 }
483 }
484 }
485 }
486 Cursor::VolPan(c) => {
487 if key_event.logical_key == Key::Named(NamedKey::Tab)
488 && key_event.state.is_pressed()
489 {
490 if modifiers.state() == ModifiersState::SHIFT {
491 if c <= 32 {
492 self.cursor = Cursor::Order;
493 return PageResponse::RequestRedraw;
494 } else {
495 self.cursor = Cursor::VolPan(c - 32);
496 return PageResponse::RequestRedraw;
497 }
498 } else if modifiers.state().is_empty() {
499 if c <= 32 {
500 self.cursor = Cursor::VolPan(c + 32);
501 return PageResponse::RequestRedraw;
502 } else {
503 self.cursor = Cursor::Order;
504 return PageResponse::RequestRedraw;
505 }
506 }
507 return PageResponse::None;
508 }
509 if modifiers.state().is_empty() && key_event.state.is_pressed() {
510 if key_event.logical_key == Key::Named(NamedKey::ArrowUp) {
511 if c == 1 {
512 return PageResponse::None;
513 } else {
514 self.cursor = Cursor::VolPan(c - 1);
515 return PageResponse::RequestRedraw;
516 }
517 } else if key_event.logical_key == Key::Named(NamedKey::ArrowDown) {
518 if c == 64 {
519 return PageResponse::None;
520 } else {
521 self.cursor = Cursor::VolPan(c + 1);
522 return PageResponse::RequestRedraw;
523 }
524 }
525 }
526
527 match self.mode {
528 Mode::Panning => {
529 let slider = &mut self.pan[usize::from(c - 1)];
530 match slider.process_input(modifiers, key_event, events) {
531 // i catch these earlier
532 WidgetResponse::SwitchFocus(_) => return PageResponse::None,
533 WidgetResponse::RequestRedraw(changed) => {
534 if changed {
535 send_song_op(SongOperation::SetPan(
536 c,
537 Pan::Value(u8::try_from(slider.get()).unwrap()),
538 ));
539 }
540 return PageResponse::RequestRedraw;
541 }
542 WidgetResponse::None => return PageResponse::None,
543 }
544 }
545 Mode::Volume => {
546 let slider = &mut self.volume[usize::from(c - 1)];
547 match slider.process_input(modifiers, key_event, events) {
548 // i catch these earlier
549 WidgetResponse::SwitchFocus(_) => return PageResponse::None,
550 WidgetResponse::RequestRedraw(changed) => {
551 if changed {
552 send_song_op(SongOperation::SetVolume(
553 c,
554 u8::try_from(slider.get()).unwrap(),
555 ));
556 }
557 return PageResponse::RequestRedraw;
558 }
559 WidgetResponse::None => return PageResponse::None,
560 }
561 }
562 }
563 }
564 }
565 PageResponse::None
566 }
567
568 #[cfg(feature = "accesskit")]
569 fn build_tree(
570 &self,
571 tree: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
572 ) -> crate::AccessResponse {
573 todo!()
574 }
575}