old school music tracker
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}