old school music tracker
at dev 779 lines 30 kB view raw
1pub mod coordinates; 2pub mod dialog; 3pub mod draw_buffer; 4pub mod header; 5pub mod pages; 6pub mod render; 7pub mod widgets; 8 9use std::{ 10 collections::VecDeque, 11 fmt::Debug, 12 num::NonZero, 13 sync::{Arc, LazyLock, OnceLock}, 14 thread::JoinHandle, 15 time::Duration, 16}; 17 18#[cfg(feature = "accesskit")] 19use accesskit::NodeId; 20use smol::{channel::Sender, lock::Mutex}; 21use torque_tracker_engine::{ 22 AudioManager, OutputConfig, PlaybackSettings, ToWorkerMsg, 23 project::song::{Song, SongOperation}, 24}; 25use winit::{ 26 application::ApplicationHandler, 27 event::{Modifiers, WindowEvent}, 28 event_loop::{ActiveEventLoop, ControlFlow, EventLoopProxy}, 29 keyboard::{Key, NamedKey}, 30 window::WindowAttributes, 31}; 32 33use cpal::{ 34 BufferSize, OutputStreamTimestamp, SupportedBufferSize, 35 traits::{DeviceTrait, HostTrait}, 36}; 37 38use pages::{ 39 AllPages, PageEvent, PageResponse, PagesEnum, order_list::OrderListPageEvent, 40 pattern::PatternPageEvent, 41}; 42 43use crate::dialog::DialogEnum; 44 45use { 46 dialog::{DialogManager, DialogResponse, confirm::ConfirmDialog, page_menu::MainMenu}, 47 draw_buffer::DrawBuffer, 48 header::{Header, HeaderEvent}, 49}; 50 51#[cfg(all(feature = "gpu_scaling", feature = "soft_scaling"))] 52use render::BothRenderBackend as RenderBackend; 53#[cfg(all(feature = "gpu_scaling", not(feature = "soft_scaling")))] 54use render::GPURenderBackend as RenderBackend; 55#[cfg(all(not(feature = "gpu_scaling"), feature = "soft_scaling"))] 56use render::SoftRenderBackend as RenderBackend; 57 58pub static EXECUTOR: smol::Executor = smol::Executor::new(); 59/// Song data 60pub static SONG_MANAGER: LazyLock<smol::lock::Mutex<AudioManager>> = 61 LazyLock::new(|| Mutex::new(AudioManager::new(Song::default()))); 62/// Sender for Song changes 63pub static SONG_OP_SEND: OnceLock<smol::channel::Sender<SongOperation>> = OnceLock::new(); 64 65/// shorter function name 66pub fn send_song_op(op: SongOperation) { 67 SONG_OP_SEND.get().unwrap().send_blocking(op).unwrap(); 68} 69 70pub enum GlobalEvent { 71 OpenDialog(DialogEnum), 72 Page(PageEvent), 73 Header(HeaderEvent), 74 /// also closes all dialogs 75 GoToPage(PagesEnum), 76 // Needed because only in the main app i know which pattern is selected, so i know what to play 77 Playback(PlaybackType), 78 #[cfg(feature = "accesskit")] 79 Accesskit(accesskit_winit::WindowEvent), 80 CloseRequested, 81 CloseApp, 82 ConstRedraw, 83} 84 85impl Debug for GlobalEvent { 86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 87 let mut debug = f.debug_struct("GlobalEvent"); 88 match self { 89 GlobalEvent::OpenDialog(_) => debug.field("OpenDialog", &"closure"), 90 GlobalEvent::Page(page_event) => debug.field("Page", page_event), 91 GlobalEvent::Header(header_event) => debug.field("Header", header_event), 92 GlobalEvent::GoToPage(pages_enum) => debug.field("GoToPage", pages_enum), 93 GlobalEvent::CloseRequested => debug.field("CloseRequested", &""), 94 GlobalEvent::CloseApp => debug.field("CloseApp", &""), 95 GlobalEvent::ConstRedraw => debug.field("ConstRedraw", &""), 96 GlobalEvent::Playback(playback_type) => debug.field("Playback", &playback_type), 97 #[cfg(feature = "accesskit")] 98 GlobalEvent::Accesskit(window_event) => debug.field("Accesskit", &window_event), 99 }; 100 debug.finish() 101 } 102} 103 104#[cfg(feature = "accesskit")] 105impl From<accesskit_winit::Event> for GlobalEvent { 106 fn from(value: accesskit_winit::Event) -> Self { 107 // ignore window id, because i only have one window 108 Self::Accesskit(value.window_event) 109 } 110} 111 112#[derive(Clone, Copy, Debug)] 113pub enum PlaybackType { 114 Stop, 115 Song, 116 Pattern, 117 FromOrder, 118 FromCursor, 119} 120 121struct WorkerThreads { 122 handles: [JoinHandle<()>; 2], 123 close_msg: [Sender<()>; 2], 124} 125 126impl WorkerThreads { 127 fn new() -> Self { 128 let (send1, recv1) = smol::channel::unbounded(); 129 let thread1 = std::thread::Builder::new() 130 .name("Background Worker 1".into()) 131 .spawn(Self::worker_task(recv1)) 132 .unwrap(); 133 let (send2, recv2) = smol::channel::unbounded(); 134 let thread2 = std::thread::Builder::new() 135 .name("Background Worker 2".into()) 136 .spawn(Self::worker_task(recv2)) 137 .unwrap(); 138 139 Self { 140 handles: [thread1, thread2], 141 close_msg: [send1, send2], 142 } 143 } 144 145 fn worker_task(recv: smol::channel::Receiver<()>) -> impl FnOnce() + Send + 'static { 146 move || { 147 smol::block_on(EXECUTOR.run(async { recv.recv().await.unwrap() })); 148 } 149 } 150 151 /// prepares the closing of the threads by signalling them to stop 152 fn send_close(&mut self) { 153 _ = self.close_msg[0].send_blocking(()); 154 _ = self.close_msg[1].send_blocking(()); 155 } 156 157 fn close_all(mut self) { 158 self.send_close(); 159 let [handle1, handle2] = self.handles; 160 handle1.join().unwrap(); 161 handle2.join().unwrap(); 162 } 163} 164 165pub struct EventQueue<'a>(&'a mut VecDeque<GlobalEvent>); 166 167impl EventQueue<'_> { 168 pub fn push(&mut self, event: GlobalEvent) { 169 self.0.push_back(event); 170 } 171} 172 173/// window with all the additional stuff 174struct Window { 175 window: Arc<winit::window::Window>, 176 render_backend: RenderBackend, 177 #[cfg(feature = "accesskit")] 178 adapter: (accesskit_winit::Adapter, accesskit::Affine), 179} 180 181pub struct App { 182 window: Option<Window>, 183 draw_buffer: DrawBuffer, 184 modifiers: Modifiers, 185 ui_pages: AllPages, 186 event_queue: VecDeque<GlobalEvent>, 187 dialog_manager: DialogManager, 188 header: Header, 189 event_loop_proxy: EventLoopProxy<GlobalEvent>, 190 worker_threads: Option<WorkerThreads>, 191 audio_stream: Option<( 192 cpal::Stream, 193 smol::Task<()>, 194 torque_tracker_engine::StreamSend, 195 )>, 196} 197 198impl ApplicationHandler<GlobalEvent> for App { 199 fn new_events(&mut self, _: &ActiveEventLoop, start_cause: winit::event::StartCause) { 200 if start_cause == winit::event::StartCause::Init { 201 LazyLock::force(&SONG_MANAGER); 202 self.worker_threads = Some(WorkerThreads::new()); 203 let (send, recv) = smol::channel::unbounded(); 204 SONG_OP_SEND.get_or_init(|| send); 205 EXECUTOR 206 .spawn(async move { 207 while let Ok(op) = recv.recv().await { 208 let mut manager = SONG_MANAGER.lock().await; 209 // if there is no active channel the buffer isn't used, so it doesn't matter that it's wrong 210 let buffer_time = manager.last_buffer_time(); 211 // spin loop to lock the song 212 let mut song = loop { 213 if let Some(song) = manager.try_edit_song() { 214 break song; 215 } 216 // smol mutex lock is held across await point 217 smol::Timer::after(buffer_time).await; 218 }; 219 // apply the received op 220 song.apply_operation(op).unwrap(); 221 // try to get more ops. This avoids repeated locking of the song when a lot of operations are 222 // in queue 223 while let Ok(op) = recv.try_recv() { 224 song.apply_operation(op).unwrap(); 225 } 226 drop(song); 227 } 228 }) 229 .detach(); 230 // spawn a task to collect sample garbage every 10 seconds 231 EXECUTOR 232 .spawn(async { 233 loop { 234 let mut lock = SONG_MANAGER.lock().await; 235 lock.collect_garbage(); 236 drop(lock); 237 smol::Timer::after(Duration::from_secs(10)).await; 238 } 239 }) 240 .detach(); 241 self.start_audio_stream(); 242 } 243 } 244 245 fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 246 self.build_window(event_loop); 247 } 248 249 fn suspended(&mut self, _: &ActiveEventLoop) { 250 // my window and GPU state have been invalidated 251 self.window = None; 252 } 253 254 fn window_event( 255 &mut self, 256 event_loop: &winit::event_loop::ActiveEventLoop, 257 _: winit::window::WindowId, 258 event: WindowEvent, 259 ) { 260 // destructure so i don't have to always type self. 261 let Self { 262 window, 263 draw_buffer, 264 modifiers, 265 ui_pages, 266 event_queue, 267 dialog_manager, 268 header, 269 event_loop_proxy: _, 270 worker_threads: _, 271 audio_stream: _, 272 } = self; 273 274 // i won't get a window_event without having a window, so unwrap is fine 275 let window = window.as_mut().unwrap(); 276 #[cfg(not(feature = "accesskit"))] 277 let Window { 278 window, 279 render_backend, 280 } = window; 281 #[cfg(feature = "accesskit")] 282 let Window { 283 window, 284 render_backend, 285 adapter, 286 } = window; 287 // don't want window to be mutable 288 let window = window.as_ref(); 289 290 #[cfg(feature = "accesskit")] 291 adapter.0.process_event(window, &event); 292 // limit the pages and widgets to only push events and not read or pop 293 let event_queue = &mut EventQueue(event_queue); 294 295 match event { 296 WindowEvent::CloseRequested => Self::close_requested(event_queue), 297 WindowEvent::Resized(physical_size) => { 298 render_backend.resize(physical_size); 299 window.request_redraw(); 300 } 301 WindowEvent::ScaleFactorChanged { 302 scale_factor: _, 303 inner_size_writer: _, 304 } => { 305 // window_state.resize(**new_inner_size); 306 // due to a version bump in winit i dont know anymore how to handle this event so i just ignore it for know and see if it makes problems in the future 307 // i have yet only received this event on linux wayland, not macos 308 println!("Window Scale Factor Changed"); 309 } 310 WindowEvent::RedrawRequested => { 311 // draw the new frame buffer 312 // TODO: split redraw header and redraw page. As soon as header gets a spectrometer this becomes important 313 header.draw(draw_buffer); 314 ui_pages.draw(draw_buffer); 315 dialog_manager.draw(draw_buffer); 316 #[cfg(feature = "accesskit")] 317 adapter.0.update_if_active(|| { 318 Self::produce_full_tree(ui_pages, header, dialog_manager, adapter.1) 319 }); 320 // notify the windowing system that drawing is done and the new buffer is about to be pushed 321 window.pre_present_notify(); 322 // push the framebuffer into GPU/softbuffer and render it onto the screen 323 render_backend.render(&draw_buffer.framebuffer, event_loop); 324 } 325 WindowEvent::KeyboardInput { 326 device_id: _, 327 event, 328 is_synthetic, 329 } => { 330 if is_synthetic { 331 return; 332 } 333 334 if event.state.is_pressed() { 335 if event.logical_key == Key::Named(NamedKey::F5) { 336 self.event_queue 337 .push_back(GlobalEvent::Playback(PlaybackType::Song)); 338 return; 339 } else if event.logical_key == Key::Named(NamedKey::F6) { 340 if modifiers.state().shift_key() { 341 self.event_queue 342 .push_back(GlobalEvent::Playback(PlaybackType::FromOrder)); 343 } else { 344 self.event_queue 345 .push_back(GlobalEvent::Playback(PlaybackType::Pattern)); 346 } 347 return; 348 } else if event.logical_key == Key::Named(NamedKey::F8) { 349 self.event_queue 350 .push_back(GlobalEvent::Playback(PlaybackType::Stop)); 351 return; 352 } 353 } 354 // key_event didn't start or stop the song, so process normally 355 if let Some(dialog) = dialog_manager.active_dialog_mut() { 356 match dialog.process_input(&event, modifiers, event_queue) { 357 DialogResponse::Close => { 358 dialog_manager.close_dialog(); 359 // if i close a pop_up i need to redraw the const part of the page as the pop-up overlapped it probably 360 ui_pages.request_draw_const(); 361 window.request_redraw(); 362 } 363 DialogResponse::RequestRedraw => window.request_redraw(), 364 DialogResponse::None => (), 365 } 366 } else { 367 if event.state.is_pressed() && event.logical_key == Key::Named(NamedKey::Escape) 368 { 369 event_queue.push(GlobalEvent::OpenDialog(dialog::DialogEnum::Main( 370 MainMenu::new(), 371 ))); 372 } 373 374 match ui_pages.process_key_event(&self.modifiers, &event, event_queue) { 375 PageResponse::RequestRedraw => window.request_redraw(), 376 PageResponse::None => (), 377 } 378 } 379 } 380 // not sure if i need it just to make sure i always have all current modifiers to be used with keyboard events 381 WindowEvent::ModifiersChanged(new_modifiers) => *modifiers = new_modifiers, 382 383 _ => (), 384 } 385 386 while let Some(event) = self.event_queue.pop_front() { 387 self.user_event(event_loop, event); 388 } 389 } 390 391 /// i may need to add the ability for events to add events to the event queue, but that should be possible 392 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: GlobalEvent) { 393 let event_queue = &mut EventQueue(&mut self.event_queue); 394 match event { 395 GlobalEvent::OpenDialog(dialog) => { 396 self.dialog_manager.open_dialog(dialog); 397 _ = self.try_request_redraw(); 398 } 399 GlobalEvent::Page(c) => match self.ui_pages.process_page_event(c, event_queue) { 400 PageResponse::RequestRedraw => _ = self.try_request_redraw(), 401 PageResponse::None => (), 402 }, 403 GlobalEvent::Header(header_event) => { 404 self.header.process_event(header_event); 405 _ = self.try_request_redraw(); 406 } 407 GlobalEvent::GoToPage(pages_enum) => { 408 self.dialog_manager.close_all(); 409 self.ui_pages.switch_page(pages_enum); 410 _ = self.try_request_redraw(); 411 } 412 GlobalEvent::CloseApp => event_loop.exit(), 413 GlobalEvent::CloseRequested => Self::close_requested(event_queue), 414 GlobalEvent::ConstRedraw => { 415 self.ui_pages.request_draw_const(); 416 _ = self.try_request_redraw(); 417 } 418 GlobalEvent::Playback(playback_type) => { 419 let msg = match playback_type { 420 PlaybackType::Song => Some(ToWorkerMsg::Playback(PlaybackSettings::Order { 421 idx: 0, 422 should_loop: true, 423 })), 424 PlaybackType::Stop => Some(ToWorkerMsg::StopPlayback), 425 PlaybackType::Pattern => { 426 Some(ToWorkerMsg::Playback(self.header.play_current_pattern())) 427 } 428 PlaybackType::FromOrder => { 429 Some(ToWorkerMsg::Playback(self.header.play_current_order())) 430 } 431 PlaybackType::FromCursor => None, 432 }; 433 434 if let Some(msg) = msg { 435 self.audio_stream 436 .as_mut() 437 .expect( 438 "audio stream should always be active, should still handle this error", 439 ) 440 .2 441 .try_msg_worker(msg) 442 .expect("buffer full. either increase size or retry somehow") 443 } 444 } 445 #[cfg(feature = "accesskit")] 446 GlobalEvent::Accesskit(window_event) => { 447 use accesskit_winit::WindowEvent; 448 match window_event { 449 WindowEvent::InitialTreeRequested => { 450 // there probably should always be a window 451 // make sure we always respond to this event. I hope it can't be send when there is no window 452 let window = self.window.as_mut().unwrap(); 453 window.adapter.0.update_if_active(|| { 454 Self::produce_full_tree( 455 &self.ui_pages, 456 &self.header, 457 &self.dialog_manager, 458 window.adapter.1, 459 ) 460 }); 461 } 462 WindowEvent::ActionRequested(event) => { 463 match event.target.0 / Self::ID_LEVEL_1 { 464 Self::HEADER_ID => todo!("decide if the header should be interactive"), 465 Self::PAGE_ID => { 466 let event = { 467 let mut event = event; 468 // information about lvl 1 was processed, so remove it 469 event.target.0 -= Self::PAGE_ID * Self::ID_LEVEL_1; 470 event 471 }; 472 match self.ui_pages.process_accesskit_event(event) { 473 PageResponse::RequestRedraw => { 474 self.try_request_redraw().unwrap() 475 } 476 PageResponse::None => (), 477 } 478 } 479 Self::DIALOG_ID => (), 480 // should i ignore invalid IDs? probably not as that would be a bug either in accesskit or my code 481 _ => unreachable!(), 482 } 483 } // i don't have any extra state for accessability so i don't need to cleanup anything 484 WindowEvent::AccessibilityDeactivated => (), 485 } 486 } 487 } 488 } 489 490 fn exiting(&mut self, _: &ActiveEventLoop) { 491 if let Some(workers) = self.worker_threads.take() { 492 // wait for all the threads to close 493 workers.close_all(); 494 } 495 if self.audio_stream.is_some() { 496 self.close_audio_stream(); 497 } 498 } 499} 500 501impl App { 502 pub fn new(proxy: EventLoopProxy<GlobalEvent>) -> Self { 503 Self { 504 window: None, 505 draw_buffer: DrawBuffer::default(), 506 modifiers: Modifiers::default(), 507 ui_pages: AllPages::new(proxy.clone()), 508 dialog_manager: DialogManager::new(), 509 header: Header::default(), 510 event_loop_proxy: proxy, 511 worker_threads: None, 512 audio_stream: None, 513 event_queue: VecDeque::with_capacity(3), 514 } 515 } 516 517 // TODO: should this be its own function?? or is there something better 518 fn close_requested(events: &mut EventQueue<'_>) { 519 events.push(GlobalEvent::OpenDialog(DialogEnum::Confirm( 520 ConfirmDialog::new( 521 "Close Torque Tracker?", 522 Some(|| GlobalEvent::CloseApp), 523 None, 524 ), 525 ))); 526 } 527 528 /// tries to request a redraw. if there currently is no window this fails 529 fn try_request_redraw(&self) -> Result<(), ()> { 530 if let Some(window) = &self.window { 531 window.window.request_redraw(); 532 Ok(()) 533 } else { 534 Err(()) 535 } 536 } 537 538 fn build_window(&mut self, event_loop: &ActiveEventLoop) { 539 self.window.get_or_insert_with(|| { 540 let mut attributes = WindowAttributes::default(); 541 attributes.active = true; 542 attributes.resizable = true; 543 attributes.resize_increments = None; 544 attributes.title = String::from("Torque Tracker"); 545 // for accesskit 546 attributes.visible = false; 547 548 let window = Arc::new(event_loop.create_window(attributes).unwrap()); 549 let render_backend = 550 RenderBackend::new(window.clone(), render::palettes::Palette::CAMOUFLAGE); 551 552 #[cfg(feature = "accesskit")] 553 let adapter = { 554 accesskit_winit::Adapter::with_event_loop_proxy( 555 event_loop, 556 &window, 557 self.event_loop_proxy.clone(), 558 ) 559 }; 560 561 window.set_visible(true); 562 let size = render_backend.get_size(); 563 Window { 564 window, 565 render_backend, 566 #[cfg(feature = "accesskit")] 567 adapter: (adapter, create_transform(size)), 568 } 569 }); 570 } 571 572 // TODO: make this configurable 573 fn start_audio_stream(&mut self) { 574 use cpal::StreamInstant; 575 576 assert!(self.audio_stream.is_none()); 577 let host = cpal::default_host(); 578 let device = host.default_output_device().unwrap(); 579 let default_config = device.default_output_config().unwrap(); 580 let (config, buffer_size) = { 581 let mut config = default_config.config(); 582 let buffer_size = { 583 let default = default_config.buffer_size(); 584 match default { 585 SupportedBufferSize::Unknown => 1024, 586 SupportedBufferSize::Range { min, max } => u32::min(u32::max(1024, *min), *max), 587 } 588 }; 589 config.buffer_size = BufferSize::Fixed(buffer_size); 590 (config, buffer_size) 591 }; 592 let mut guard = SONG_MANAGER.lock_blocking(); 593 let (mut worker, buffer_time, status, stream_send) = guard 594 .get_callback::<f32, cpal::OutputStreamTimestamp>( 595 OutputConfig { 596 buffer_size, 597 channel_count: NonZero::new(config.channels).unwrap(), 598 sample_rate: NonZero::new(config.sample_rate.0).unwrap(), 599 interpolation: torque_tracker_engine::audio_processing::Interpolation::Linear, 600 }, 601 cpal::OutputStreamTimestamp { 602 callback: cpal::StreamInstant::new(0, 0), 603 playback: cpal::StreamInstant::new(0, 0), 604 }, 605 ); 606 // keep the guard as short as possible to not block the async threads 607 drop(guard); 608 let stream = device 609 .build_output_stream( 610 &config, 611 move |data, info| { 612 worker(data, info.timestamp()); 613 }, 614 |err| eprintln!("audio stream err: {err:?}"), 615 None, 616 ) 617 .unwrap(); 618 // spawn a task to process the audio playback status updates 619 let proxy = self.event_loop_proxy.clone(); 620 let task = EXECUTOR.spawn(async move { 621 let buffer_time = buffer_time; 622 let mut status_recv = status; 623 // maybe also send the timestamp every second or so 624 let mut old_playback = None; 625 let mut old_timestamp = OutputStreamTimestamp { 626 callback: StreamInstant::new(0, 0), 627 playback: StreamInstant::new(0, 0), 628 }; 629 loop { 630 smol::Timer::after(buffer_time).await; 631 let Some(read) = status_recv.try_get() else { 632 // we had a lock for way too long, so now we are behing and have to wait for the writer to finish 633 // writing once. Then locking will succeed again. 634 continue; 635 }; 636 let playback = read.0; 637 let timestamp = read.1; 638 // only react on status changes. could at some point be made more granular 639 if playback != old_playback { 640 old_playback = playback; 641 // println!("playback status: {status:?}"); 642 let pos = playback.map(|s| s.position); 643 proxy 644 .send_event(GlobalEvent::Header(HeaderEvent::SetPlayback(pos))) 645 .unwrap(); 646 let pos = playback.map(|s| (s.position.pattern, s.position.row)); 647 proxy 648 .send_event(GlobalEvent::Page(PageEvent::Pattern( 649 PatternPageEvent::PlaybackPosition(pos), 650 ))) 651 .unwrap(); 652 // does a map flatten. idk why it's called and_then 653 let pos = playback.and_then(|s| s.position.order); 654 proxy 655 .send_event(GlobalEvent::Page(PageEvent::OrderList( 656 OrderListPageEvent::SetPlayback(pos), 657 ))) 658 .unwrap(); 659 } 660 661 // also check for changed timestamp 662 if timestamp != old_timestamp { 663 old_timestamp = timestamp; 664 // maybe at some point use the data 665 } 666 } 667 }); 668 self.audio_stream = Some((stream, task, stream_send)); 669 } 670 671 fn close_audio_stream(&mut self) { 672 let (stream, task, mut stream_send) = self.audio_stream.take().unwrap(); 673 // stop playback 674 _ = stream_send.try_msg_worker(ToWorkerMsg::StopPlayback); 675 _ = stream_send.try_msg_worker(ToWorkerMsg::StopLiveNote); 676 // kill the task. using `cancel` doesn't make sense because it doesn't finishe anyways 677 drop(task); 678 // lastly kill the audio stream 679 drop(stream); 680 } 681} 682 683#[cfg(feature = "accesskit")] 684impl App { 685 // Node ID Layout: 686 // xxx_xxx_xxx_xxx_xxx 687 // In Widget lvl 4 688 // In Page/Dialog/Header lvl 3 689 // Which Page/Header/Dialog lvl 2 690 // Page, Dialog or Header lvl 1 691 // Root 692 const ROOT_ID: NodeId = NodeId(1_000_000_000_000); 693 pub const ID_LEVEL_1: u64 = 1_000_000_000; 694 pub const ID_LEVEL_2: u64 = 1_000_000; 695 pub const ID_LEVEL_3: u64 = 1_000; 696 pub const ID_LEVEL_4: u64 = 1; // do i need this? 697 698 pub const HEADER_ID: u64 = 1; 699 pub const PAGE_ID: u64 = 2; 700 pub const DIALOG_ID: u64 = 3; 701 702 fn produce_full_tree( 703 pages: &AllPages, 704 header: &Header, 705 dialogs: &DialogManager, 706 transform: accesskit::Affine, 707 ) -> accesskit::TreeUpdate { 708 use accesskit::TreeUpdate; 709 use accesskit::{Node, Role, Tree}; 710 711 let tree = Tree { 712 root: Self::ROOT_ID, 713 toolkit_name: Some(String::from("Torque Tracker Custom")), 714 toolkit_version: None, 715 }; 716 let mut root_node = Node::new(Role::Window); 717 let mut nodes = Vec::new(); 718 root_node.set_label("Torque Tracker"); 719 root_node.set_language("English"); 720 let header_id = header.build_tree(&mut nodes); 721 root_node.push_child(header_id); 722 root_node.set_transform(transform); 723 724 let resp = pages.build_tree(&mut nodes); 725 let mut focused = resp.selected; 726 root_node.push_child(resp.root); 727 728 if let Some(dialog) = dialogs.active_dialog() { 729 let resp = dialog.build_tree(&mut nodes); 730 root_node.push_child(resp.root); 731 focused = resp.selected; 732 } 733 734 nodes.push((Self::ROOT_ID, root_node)); 735 TreeUpdate { 736 nodes, 737 tree: Some(tree), 738 focus: focused, 739 } 740 } 741 742 fn process_accesskit_event(&mut self, event: accesskit::ActionRequest) {} 743} 744 745impl Drop for App { 746 fn drop(&mut self) { 747 if self.audio_stream.is_some() { 748 self.close_audio_stream(); 749 } 750 } 751} 752 753#[cfg(feature = "accesskit")] 754pub struct AccessResponse { 755 pub root: accesskit::NodeId, 756 pub selected: accesskit::NodeId, 757} 758 759#[cfg(feature = "accesskit")] 760fn create_transform(size: winit::dpi::PhysicalSize<u32>) -> accesskit::Affine { 761 accesskit::Affine::scale_non_uniform( 762 size.width as f64 / crate::coordinates::WINDOW_SIZE_CHARS.0 as f64, 763 size.height as f64 / crate::coordinates::WINDOW_SIZE_CHARS.1 as f64, 764 ) 765} 766 767fn main() { 768 let event_loop = winit::event_loop::EventLoop::<GlobalEvent>::with_user_event() 769 .build() 770 .unwrap(); 771 event_loop.set_control_flow(ControlFlow::Wait); 772 // i don't need any raw device events. Keyboard and Mouse coming as window events are enough 773 event_loop.listen_device_events(winit::event_loop::DeviceEvents::Never); 774 let event_loop_proxy = event_loop.create_proxy(); 775 let mut app = App::new(event_loop_proxy); 776 app.header.draw_constant(&mut app.draw_buffer); 777 778 event_loop.run_app(&mut app).unwrap(); 779}