old school music tracker

enable playback control from page menu

+125 -110
+67 -26
src/app.rs
··· 60 60 Header(HeaderEvent), 61 61 /// also closes all dialogs 62 62 GoToPage(PagesEnum), 63 + Playback(PlaybackType), 63 64 CloseRequested, 64 65 CloseApp, 65 66 ConstRedraw, 66 67 } 67 68 69 + impl Clone for GlobalEvent { 70 + fn clone(&self) -> Self { 71 + // TODO: make this really clone, once the Dialogs are an enum instead of Box dyn 72 + match self { 73 + GlobalEvent::OpenDialog(_) => panic!("TODO: don't clone this"), 74 + GlobalEvent::Page(page_event) => GlobalEvent::Page(page_event.clone()), 75 + GlobalEvent::Header(header_event) => GlobalEvent::Header(header_event.clone()), 76 + GlobalEvent::GoToPage(pages_enum) => GlobalEvent::GoToPage(*pages_enum), 77 + GlobalEvent::CloseRequested => GlobalEvent::CloseRequested, 78 + GlobalEvent::CloseApp => GlobalEvent::CloseApp, 79 + GlobalEvent::ConstRedraw => GlobalEvent::ConstRedraw, 80 + GlobalEvent::Playback(playback_type) => GlobalEvent::Playback(*playback_type), 81 + } 82 + } 83 + } 84 + 68 85 impl Debug for GlobalEvent { 69 86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 87 let mut debug = f.debug_struct("GlobalEvent"); ··· 76 93 GlobalEvent::CloseRequested => debug.field("CloseRequested", &""), 77 94 GlobalEvent::CloseApp => debug.field("CloseApp", &""), 78 95 GlobalEvent::ConstRedraw => debug.field("ConstRedraw", &""), 96 + GlobalEvent::Playback(playback_type) => debug.field("Playback", &playback_type), 79 97 }; 80 98 debug.finish() 81 99 } 100 + } 101 + 102 + #[derive(Clone, Copy, Debug)] 103 + pub enum PlaybackType { 104 + Stop, 105 + Song, 106 + Pattern, 107 + FromOrder, 108 + FromCursor, 82 109 } 83 110 84 111 struct WorkerThreads { ··· 253 280 return; 254 281 } 255 282 256 - let message = if event.logical_key == Key::Named(NamedKey::F5) { 257 - // play song from start 258 - Some(ToWorkerMsg::Playback(PlaybackSettings::Order { 259 - idx: 0, 260 - should_loop: true, 261 - })) 283 + if event.logical_key == Key::Named(NamedKey::F5) { 284 + self.event_queue 285 + .push_back(GlobalEvent::Playback(PlaybackType::Song)); 262 286 } else if event.logical_key == Key::Named(NamedKey::F6) { 263 - Some(ToWorkerMsg::Playback(if modifiers.state().shift_key() { 264 - // play the current order 265 - self.header.play_current_order() 287 + if modifiers.state().shift_key() { 288 + self.event_queue 289 + .push_back(GlobalEvent::Playback(PlaybackType::FromOrder)); 266 290 } else { 267 - // play the current pattern 268 - self.header.play_current_pattern() 269 - })) 291 + self.event_queue 292 + .push_back(GlobalEvent::Playback(PlaybackType::Pattern)); 293 + } 270 294 // TODO: add F7 handling 271 295 } else if event.logical_key == Key::Named(NamedKey::F8) { 272 - Some(ToWorkerMsg::StopPlayback) 273 - } else { 274 - None 275 - }; 276 - if let Some(msg) = message { 277 - let result = AUDIO.lock_blocking().try_msg_worker(msg); 278 - 279 - match result { 280 - SendResult::Success => (), 281 - SendResult::BufferFull => { 282 - panic!("to worker buffer full, probably have to retry somehow") 283 - } 284 - SendResult::AudioInactive => panic!("audio should always be active"), 285 - } 296 + self.event_queue 297 + .push_back(GlobalEvent::Playback(PlaybackType::Stop)); 298 + // TODO: allow F5 to also switch to the playback page, once it exists 286 299 } else { 287 300 // key_event didn't start or stop the song, so process normally 288 301 if let Some(dialog) = dialog_manager.active_dialog_mut() { ··· 350 363 GlobalEvent::ConstRedraw => { 351 364 self.ui_pages.request_draw_const(); 352 365 _ = self.try_request_redraw(); 366 + } 367 + GlobalEvent::Playback(playback_type) => { 368 + let msg = match playback_type { 369 + PlaybackType::Song => Some(ToWorkerMsg::Playback(PlaybackSettings::Order { 370 + idx: 0, 371 + should_loop: true, 372 + })), 373 + PlaybackType::Stop => Some(ToWorkerMsg::StopPlayback), 374 + PlaybackType::Pattern => { 375 + Some(ToWorkerMsg::Playback(self.header.play_current_pattern())) 376 + } 377 + PlaybackType::FromOrder => { 378 + Some(ToWorkerMsg::Playback(self.header.play_current_order())) 379 + } 380 + PlaybackType::FromCursor => None, 381 + }; 382 + 383 + if let Some(msg) = msg { 384 + let result = AUDIO.lock_blocking().try_msg_worker(msg); 385 + 386 + match result { 387 + SendResult::Success => (), 388 + SendResult::BufferFull => { 389 + panic!("to worker buffer full, probably have to retry somehow") 390 + } 391 + SendResult::AudioInactive => panic!("audio should always be active"), 392 + } 393 + } 353 394 } 354 395 } 355 396 }
+52 -78
src/ui/dialog/page_menu.rs
··· 3 3 use winit::keyboard::{Key, NamedKey}; 4 4 5 5 use crate::{ 6 - app::GlobalEvent, 6 + app::{GlobalEvent, PlaybackType}, 7 7 coordinates::{CharPosition, CharRect, FONT_SIZE, PixelRect}, 8 8 draw_buffer::DrawBuffer, 9 9 ui::pages::PagesEnum, ··· 11 11 12 12 use super::{Dialog, DialogResponse}; 13 13 14 - enum PageOrPageMenu { 14 + enum Action { 15 15 Menu(Menu), 16 + // TODO: maybe fold this into the more general event variant 16 17 Page(PagesEnum), 17 - CloseApp, 18 - // should be removed when possible 18 + Event(GlobalEvent), 19 + // TODO: should be removed when it's all implemented 19 20 NotYetImplemented, 20 21 } 21 22 ··· 33 34 rect: CharRect, 34 35 selected: usize, 35 36 pressed: bool, 36 - buttons: &'static [(&'static str, PageOrPageMenu)], 37 + buttons: &'static [(&'static str, Action)], 37 38 sub_menu: Option<Box<Self>>, 38 39 } 39 40 ··· 58 59 Self::BOTRIGHT_COLOR, 59 60 1, 60 61 ); 61 - for (num, (name, action)) in self.buttons.iter().enumerate() { 62 + for (num, (name, _)) in self.buttons.iter().enumerate() { 62 63 let text_color = match self.selected == num { 63 64 true => 11, 64 65 false => 0, ··· 112 113 } else if self.pressed { 113 114 self.pressed = false; 114 115 match &self.buttons[self.selected].1 { 115 - PageOrPageMenu::Menu(menu) => { 116 + Action::Menu(menu) => { 116 117 let menu = match menu { 117 118 Menu::File => Self::file(), 118 119 Menu::Playback => Self::playback(), ··· 123 124 self.sub_menu = Some(Box::new(menu)); 124 125 return DialogResponse::RequestRedraw; 125 126 } 126 - PageOrPageMenu::Page(page) => { 127 + Action::Page(page) => { 127 128 event.push_back(GlobalEvent::GoToPage(*page)); 128 129 return DialogResponse::Close; 129 130 } 130 - PageOrPageMenu::NotYetImplemented => { 131 + Action::NotYetImplemented => { 131 132 println!("Not yet implementes"); 132 133 return DialogResponse::RequestRedraw; 133 134 } 134 - PageOrPageMenu::CloseApp => { 135 - event.push_back(GlobalEvent::CloseRequested); 135 + Action::Event(global_event) => { 136 + event.push_back(global_event.clone()); 136 137 return DialogResponse::Close; 137 138 } 138 139 } ··· 165 166 name: &'static str, 166 167 pos: CharPosition, 167 168 width: usize, 168 - buttons: &'static [(&'static str, PageOrPageMenu)], 169 + buttons: &'static [(&'static str, Action)], 169 170 ) -> Self { 170 171 let rect = CharRect::new( 171 172 pos.y(), ··· 208 209 CharPosition::new(6, 11), 209 210 29, 210 211 &[ 211 - ("File Menu...", PageOrPageMenu::Menu(Menu::File)), 212 - ("Playback Menu...", PageOrPageMenu::Menu(Menu::Playback)), 212 + ("File Menu...", Action::Menu(Menu::File)), 213 + ("Playback Menu...", Action::Menu(Menu::Playback)), 213 214 ( 214 215 "View Patterns (F2)", 215 - PageOrPageMenu::Page(PagesEnum::Pattern), 216 + Action::Page(PagesEnum::Pattern), 216 217 ), 217 - ("Sample Menu...", PageOrPageMenu::Menu(Menu::Sample)), 218 - ("Instrument Menu...", PageOrPageMenu::Menu(Menu::Instrument)), 218 + ("Sample Menu...", Action::Menu(Menu::Sample)), 219 + ("Instrument Menu...", Action::Menu(Menu::Instrument)), 219 220 ( 220 221 "View Orders/Panning (F11)", 221 - PageOrPageMenu::Page(PagesEnum::OrderList), 222 + Action::Page(PagesEnum::OrderList), 222 223 ), 223 224 ( 224 225 "View Variables (F12)", 225 - PageOrPageMenu::Page(PagesEnum::SongDirectoryConfig), 226 - ), 227 - ( 228 - "Message Editor (Shift-F9)", 229 - PageOrPageMenu::NotYetImplemented, 230 - ), 231 - ("Settings Menu...", PageOrPageMenu::Menu(Menu::Settings)), 232 - ( 233 - "Help! (F1)", 234 - PageOrPageMenu::Page(PagesEnum::Help), 226 + Action::Page(PagesEnum::SongDirectoryConfig), 235 227 ), 228 + ("Message Editor (Shift-F9)", Action::NotYetImplemented), 229 + ("Settings Menu...", Action::Menu(Menu::Settings)), 230 + ("Help! (F1)", Action::Page(PagesEnum::Help)), 236 231 ], 237 232 ) 238 233 } ··· 243 238 CharPosition::new(25, 13), 244 239 26, 245 240 &[ 246 - ("Load... (F9)", PageOrPageMenu::NotYetImplemented), 247 - ("New... (Ctrl-N)", PageOrPageMenu::NotYetImplemented), 248 - ("Save Current (Ctrl-S)", PageOrPageMenu::NotYetImplemented), 249 - ("Save As... (F10)", PageOrPageMenu::NotYetImplemented), 250 - ("Export... (Shift-F10)", PageOrPageMenu::NotYetImplemented), 251 - ("Message Log (Ctrl-F11)", PageOrPageMenu::NotYetImplemented), 252 - ("Quit (Ctrl-Q)", PageOrPageMenu::CloseApp), 241 + ("Load... (F9)", Action::NotYetImplemented), 242 + ("New... (Ctrl-N)", Action::NotYetImplemented), 243 + ("Save Current (Ctrl-S)", Action::NotYetImplemented), 244 + ("Save As... (F10)", Action::NotYetImplemented), 245 + ("Export... (Shift-F10)", Action::NotYetImplemented), 246 + ("Message Log (Ctrl-F11)", Action::NotYetImplemented), 247 + ( 248 + "Quit (Ctrl-Q)", 249 + Action::Event(GlobalEvent::CloseRequested), 250 + ), 253 251 ], 254 252 ) 255 253 } ··· 260 258 CharPosition::new(25, 13), 261 259 31, 262 260 &[ 263 - ( 264 - "Show Infopage (F5)", 265 - PageOrPageMenu::NotYetImplemented, 266 - ), 261 + ("Show Infopage (F5)", Action::NotYetImplemented), 267 262 ( 268 263 "Play Song (Ctrl-F5)", 269 - PageOrPageMenu::NotYetImplemented, 264 + Action::Event(GlobalEvent::Playback(PlaybackType::Song)), 270 265 ), 271 266 ( 272 267 "Play Pattern (F6)", 273 - PageOrPageMenu::NotYetImplemented, 268 + Action::Event(GlobalEvent::Playback(PlaybackType::Pattern)), 274 269 ), 275 270 ( 276 271 "Play from Order (Shift-F6)", 277 - PageOrPageMenu::NotYetImplemented, 272 + Action::Event(GlobalEvent::Playback(PlaybackType::FromOrder)), 278 273 ), 279 - ( 280 - "Play from Mark/Cursor (F7)", 281 - PageOrPageMenu::NotYetImplemented, 282 - ), 274 + ("Play from Mark/Cursor (F7)", Action::NotYetImplemented), 283 275 ( 284 276 "Stop (F8)", 285 - PageOrPageMenu::NotYetImplemented, 286 - ), 287 - ( 288 - "Reinit Soundcard (Ctrl-I)", 289 - PageOrPageMenu::NotYetImplemented, 290 - ), 291 - ( 292 - "Driver Screen (Shift-F5)", 293 - PageOrPageMenu::NotYetImplemented, 277 + Action::Event(GlobalEvent::Playback(PlaybackType::Stop)), 294 278 ), 295 - ( 296 - "Calculate Length (Ctrl-P)", 297 - PageOrPageMenu::NotYetImplemented, 298 - ), 279 + ("Reinit Soundcard (Ctrl-I)", Action::NotYetImplemented), 280 + ("Driver Screen (Shift-F5)", Action::NotYetImplemented), 281 + ("Calculate Length (Ctrl-P)", Action::NotYetImplemented), 299 282 ], 300 283 ) 301 284 } ··· 308 291 &[ 309 292 ( 310 293 "Sample List (F3)", 311 - PageOrPageMenu::Page(PagesEnum::SampleList), 312 - ), 313 - ( 314 - "Sample Library (Ctrl-F3)", 315 - PageOrPageMenu::NotYetImplemented, 294 + Action::Page(PagesEnum::SampleList), 316 295 ), 296 + ("Sample Library (Ctrl-F3)", Action::NotYetImplemented), 317 297 ], 318 298 ) 319 299 } ··· 324 304 CharPosition::new(20, 23), 325 305 33, 326 306 &[ 327 - ( 328 - "Instrument List (F4)", 329 - PageOrPageMenu::NotYetImplemented, 330 - ), 331 - ( 332 - "Instrument Library (Ctrl-F4)", 333 - PageOrPageMenu::NotYetImplemented, 334 - ), 307 + ("Instrument List (F4)", Action::NotYetImplemented), 308 + ("Instrument Library (Ctrl-F4)", Action::NotYetImplemented), 335 309 ], 336 310 ) 337 311 } ··· 344 318 &[ 345 319 ( 346 320 "Preferences (Shift-F5)", 347 - PageOrPageMenu::NotYetImplemented, 321 + Action::NotYetImplemented, 348 322 ), 349 323 ( 350 324 "MIDI Configuration (Shift-F1)", 351 - PageOrPageMenu::NotYetImplemented, 325 + Action::NotYetImplemented, 352 326 ), 353 327 ( 354 328 "System Configuration (Ctrl-F1)", 355 - PageOrPageMenu::NotYetImplemented, 329 + Action::NotYetImplemented, 356 330 ), 357 331 ( 358 332 "Palette Editor (Ctrl-F12)", 359 - PageOrPageMenu::NotYetImplemented, 333 + Action::NotYetImplemented, 360 334 ), 361 335 ( 362 336 "Font Editor (Shift-F12)", 363 - PageOrPageMenu::NotYetImplemented, 337 + Action::NotYetImplemented, 364 338 ), 365 339 ( 366 340 "Toggle Fullscreen (Ctrl-Alt-Enter)", 367 - PageOrPageMenu::NotYetImplemented, 341 + Action::NotYetImplemented, 368 342 ), 369 343 ], 370 344 )
+1 -1
src/ui/header.rs
··· 11 11 draw_buffer::DrawBuffer, 12 12 }; 13 13 14 - #[derive(Debug)] 14 + #[derive(Debug, Clone)] 15 15 pub enum HeaderEvent { 16 16 SetCursorRow(u16), 17 17 SetMaxCursorRow(u16),
+1 -1
src/ui/pages.rs
··· 126 126 SampleList, 127 127 } 128 128 129 - #[derive(Debug)] 129 + #[derive(Debug, Clone)] 130 130 pub enum PageEvent { 131 131 Sdc(SDCChange), 132 132 Pattern(PatternPageEvent),
+1 -1
src/ui/pages/order_list.rs
··· 17 17 18 18 use super::{Page, PageEvent, PageResponse}; 19 19 20 - #[derive(Debug)] 20 + #[derive(Debug, Clone)] 21 21 pub enum OrderListPageEvent { 22 22 SetVolumeCurrent(i16), 23 23 SetPanCurrent(i16),
+1 -1
src/ui/pages/pattern.rs
··· 103 103 note.and_then(Result::ok) 104 104 } 105 105 106 - #[derive(Debug)] 106 + #[derive(Debug, Clone)] 107 107 pub enum PatternPageEvent { 108 108 Loaded(Pattern, u8), 109 109 SetSampleInstr(u8),
+1 -1
src/ui/pages/sample_list.rs
··· 25 25 }, 26 26 }; 27 27 28 - #[derive(Debug)] 28 + #[derive(Debug, Clone)] 29 29 pub enum SampleListEvent { 30 30 SetSample(u8, String, SampleMetaData), 31 31 SelectSample(u8),
+1 -1
src/ui/pages/song_directory_config_page.rs
··· 29 29 // Amiga, 30 30 // } 31 31 32 - #[derive(Debug)] 32 + #[derive(Debug, Clone)] 33 33 pub enum SDCChange { 34 34 SetSongName(String), 35 35 InitialTempo(i16),