From 3ae2467accf8f025e60a7401bc8e6642d5cd3dcc Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 2 Jul 2024 22:25:33 +0300 Subject: [PATCH] trait Run --- src/core/handle.rs | 62 ---------------------- src/core/jack.rs | 62 ++++++++++++++++++++++ src/core/mod.rs | 72 +------------------------- src/core/note.rs | 0 src/core/process.rs | 0 src/core/render.rs | 5 +- src/core/run.rs | 97 +++++++++++++++++++++++++++++++++++ src/device/launcher/handle.rs | 6 ++- src/device/launcher/mod.rs | 15 +++++- src/layout/mod.rs | 4 ++ src/main.rs | 30 +++++++++-- 11 files changed, 212 insertions(+), 141 deletions(-) delete mode 100644 src/core/note.rs delete mode 100644 src/core/process.rs create mode 100644 src/core/run.rs diff --git a/src/core/handle.rs b/src/core/handle.rs index f213256a..246edd0c 100644 --- a/src/core/handle.rs +++ b/src/core/handle.rs @@ -25,65 +25,3 @@ pub enum AppEvent { /// JACK notification Jack(JackEvent) } - -#[derive(Debug)] -pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, String), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(String, bool), - PortRegistration(PortId, bool), - PortRename(PortId, String, String), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} - -pub struct Notifications(pub T); - -impl NotificationHandler for Notifications { - fn thread_init (&self, _: &Client) { - self.0(AppEvent::Jack(JackEvent::ThreadInit)); - } - - fn shutdown (&mut self, status: ClientStatus, reason: &str) { - self.0(AppEvent::Jack(JackEvent::Shutdown(status, reason.into()))); - } - - fn freewheel (&mut self, _: &Client, enabled: bool) { - self.0(AppEvent::Jack(JackEvent::Freewheel(enabled))); - } - - fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control { - self.0(AppEvent::Jack(JackEvent::SampleRate(frames))); - Control::Quit - } - - fn client_registration (&mut self, _: &Client, name: &str, reg: bool) { - self.0(AppEvent::Jack(JackEvent::ClientRegistration(name.into(), reg))); - } - - fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) { - self.0(AppEvent::Jack(JackEvent::PortRegistration(id, reg))); - } - - fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - self.0(AppEvent::Jack(JackEvent::PortRename(id, old.into(), new.into()))); - Control::Continue - } - - fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) { - self.0(AppEvent::Jack(JackEvent::PortsConnected(a, b, are))); - } - - fn graph_reorder (&mut self, _: &Client) -> Control { - self.0(AppEvent::Jack(JackEvent::GraphReorder)); - Control::Continue - } - - fn xrun (&mut self, _: &Client) -> Control { - self.0(AppEvent::Jack(JackEvent::XRun)); - Control::Continue - } -} diff --git a/src/core/jack.rs b/src/core/jack.rs index 4e877f66..8ae5ae62 100644 --- a/src/core/jack.rs +++ b/src/core/jack.rs @@ -33,3 +33,65 @@ pub use ::jack::{ TransportState, TransportStatePosition }; + +#[derive(Debug)] +pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, String), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(String, bool), + PortRegistration(PortId, bool), + PortRename(PortId, String, String), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, +} + +pub struct Notifications(pub T); + +impl NotificationHandler for Notifications { + fn thread_init (&self, _: &Client) { + self.0(AppEvent::Jack(JackEvent::ThreadInit)); + } + + fn shutdown (&mut self, status: ClientStatus, reason: &str) { + self.0(AppEvent::Jack(JackEvent::Shutdown(status, reason.into()))); + } + + fn freewheel (&mut self, _: &Client, enabled: bool) { + self.0(AppEvent::Jack(JackEvent::Freewheel(enabled))); + } + + fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control { + self.0(AppEvent::Jack(JackEvent::SampleRate(frames))); + Control::Quit + } + + fn client_registration (&mut self, _: &Client, name: &str, reg: bool) { + self.0(AppEvent::Jack(JackEvent::ClientRegistration(name.into(), reg))); + } + + fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) { + self.0(AppEvent::Jack(JackEvent::PortRegistration(id, reg))); + } + + fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(AppEvent::Jack(JackEvent::PortRename(id, old.into(), new.into()))); + Control::Continue + } + + fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + self.0(AppEvent::Jack(JackEvent::PortsConnected(a, b, are))); + } + + fn graph_reorder (&mut self, _: &Client) -> Control { + self.0(AppEvent::Jack(JackEvent::GraphReorder)); + Control::Continue + } + + fn xrun (&mut self, _: &Client) -> Control { + self.0(AppEvent::Jack(JackEvent::XRun)); + Control::Continue + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 6932aa65..afeffb8a 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -4,7 +4,7 @@ macro_rules! submod { ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; } -submod!( device handle jack keymap note port process render time ); +submod!( device handle jack keymap port render run time ); pub use std::error::Error; pub use std::io::{stdout, Stdout, Write}; @@ -31,73 +31,3 @@ pub use ::crossterm::{ pub use ::ratatui::prelude::*; pub use ::midly::{MidiMessage, live::LiveEvent, num::u7}; pub use crate::{key, keymap}; - -/// Run a device as the root of the app. -pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box> { - let device = Arc::new(Mutex::new(device)); - let exited = Arc::new(AtomicBool::new(false)); - - // Spawn input (handle) thread - let _input_thread = { - let poll = std::time::Duration::from_millis(100); - let exited = exited.clone(); - let device = device.clone(); - spawn(move || loop { - // Exit if flag is set - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - // Listen for events and send them to the main thread - if ::crossterm::event::poll(poll).is_ok() { - let event = ::crossterm::event::read().unwrap(); - match event { - Event::Key(KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - exited.store(true, Ordering::Relaxed); - }, - _ => if device.lock().unwrap().handle(&AppEvent::Input(event)).is_err() { - break - } - } - } - }) - }; - - // Set up terminal - stdout().execute(EnterAlternateScreen)?; - enable_raw_mode()?; - let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; - - // Set up panic hook - let better_panic_handler = better_panic::Settings::auto() - .verbosity(better_panic::Verbosity::Full) - .create_panic_handler(); - std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{ - stdout().execute(LeaveAlternateScreen).unwrap(); - crossterm::terminal::disable_raw_mode().unwrap(); - better_panic_handler(info); - })); - - // Main (render) loop - let sleep = std::time::Duration::from_millis(16); - loop { - terminal.draw(|frame|{ - let area = frame.size(); - let buffer = frame.buffer_mut(); - device.lock().unwrap().render(buffer, area).expect("Failed to render content."); - }).expect("Failed to render frame"); - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - std::thread::sleep(sleep); - }; - - // Cleanup - stdout().execute(LeaveAlternateScreen)?; - crossterm::terminal::disable_raw_mode()?; - - Ok(()) -} diff --git a/src/core/note.rs b/src/core/note.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/core/process.rs b/src/core/process.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/core/render.rs b/src/core/render.rs index e4dc0f98..d3ba4293 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -1,4 +1,5 @@ use crate::core::*; +use ratatui::widgets::WidgetRef; /// Trait for things that render to the display. pub trait Render { @@ -28,13 +29,13 @@ impl Render for Box { } } -impl ratatui::widgets::WidgetRef for &dyn Render { +impl WidgetRef for &dyn Render { fn render_ref (&self, area: Rect, buf: &mut Buffer) { Render::render(*self, buf, area).expect("Failed to render device."); } } -impl ratatui::widgets::WidgetRef for dyn Render { +impl WidgetRef for dyn Render { fn render_ref (&self, area: Rect, buf: &mut Buffer) { Render::render(self, buf, area).expect("Failed to render device."); } diff --git a/src/core/run.rs b/src/core/run.rs new file mode 100644 index 00000000..3a1f84bb --- /dev/null +++ b/src/core/run.rs @@ -0,0 +1,97 @@ +use crate::core::*; + +pub trait Run: Render + Handle + Send + Sized + 'static { + fn run (self) -> Usually<()> { + let device = Arc::new(Mutex::new(self)); + let exited = Arc::new(AtomicBool::new(false)); + let _input_thread = input_thread(&exited, &device); + terminal_setup()?; + panic_hook_setup(); + main_thread(&exited, &device)?; + terminal_teardown()?; + Ok(()) + } +} + +impl Run for T {} + +/// Spawn thread that listens for user input +pub fn input_thread ( + exited: &Arc, + device: &Arc> +) -> JoinHandle<()> { + let poll = std::time::Duration::from_millis(100); + let exited = exited.clone(); + let device = device.clone(); + spawn(move || loop { + // Exit if flag is set + if exited.fetch_and(true, Ordering::Relaxed) { + break + } + // Listen for events and send them to the main thread + if ::crossterm::event::poll(poll).is_ok() { + let event = ::crossterm::event::read().unwrap(); + match event { + Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + exited.store(true, Ordering::Relaxed); + }, + _ => if device.lock().unwrap().handle(&AppEvent::Input(event)).is_err() { + break + } + } + } + }) +} + +/// Set up terminal +pub fn terminal_setup () -> Usually<()> { + stdout().execute(EnterAlternateScreen)?; + enable_raw_mode()?; + Ok(()) +} +/// Set up panic hook +pub fn panic_hook_setup () { + let better_panic_handler = better_panic::Settings::auto() + .verbosity(better_panic::Verbosity::Full) + .create_panic_handler(); + std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{ + stdout().execute(LeaveAlternateScreen).unwrap(); + crossterm::terminal::disable_raw_mode().unwrap(); + better_panic_handler(info); + })); +} +/// Main thread render loop +pub fn main_thread ( + exited: &Arc, + device: &Arc> +) -> Usually<()> { + let exited = exited.clone(); + let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; + let sleep = std::time::Duration::from_millis(16); + loop { + + terminal.draw(|frame|{ + let area = frame.size(); + let buffer = frame.buffer_mut(); + device.lock().unwrap().render(buffer, area).expect("Failed to render content."); + }).expect("Failed to render frame"); + + if exited.fetch_and(true, Ordering::Relaxed) { + break + } + + std::thread::sleep(sleep); + + } + Ok(()) +} +/// Cleanup +pub fn terminal_teardown () -> Usually<()> { + stdout().execute(LeaveAlternateScreen)?; + crossterm::terminal::disable_raw_mode()?; + Ok(()) +} diff --git a/src/device/launcher/handle.rs b/src/device/launcher/handle.rs index e6b20f76..a35e82c4 100644 --- a/src/device/launcher/handle.rs +++ b/src/device/launcher/handle.rs @@ -1,8 +1,12 @@ use crate::core::*; use super::*; pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually { + //if let Some(ref modal) = state.modal { + //if modal.handle_with_state(state, event)? { + //return Ok(true) + //} + //} Ok(handle_keymap(state, event, KEYMAP)? || match state.view { - LauncherView::Modal(ref mut device) => device.handle(event)?, LauncherView::Tracks => handle_keymap(state, event, KEYMAP_TRACKS)?, LauncherView::Sequencer => { let i = state.col().saturating_sub(1); diff --git a/src/device/launcher/mod.rs b/src/device/launcher/mod.rs index 0d2da7c2..c922af20 100644 --- a/src/device/launcher/mod.rs +++ b/src/device/launcher/mod.rs @@ -18,8 +18,13 @@ pub struct Launcher { scenes: Vec, show_help: bool, view: LauncherView, + modal: Option>>, +} +pub enum LauncherView { + Tracks, + Sequencer, + Chains } -pub enum LauncherView { Tracks, Sequencer, Chains, Modal(Box) } impl LauncherView { fn is_tracks (&self) -> bool { match self { Self::Tracks => true, _ => false } @@ -70,6 +75,7 @@ impl Launcher { ]))?, ] }, show_help: true, + modal: None }).activate(client) } fn cols (&self) -> usize { @@ -209,6 +215,13 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually Usually { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index e49cae34..16d01136 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -6,6 +6,10 @@ mod lozenge; pub use self::lozenge::*; use crate::core::*; +pub trait Modal: Device { + fn handle_with_state (&self, state: &mut T, event: &AppEvent) -> Usually; +} + pub trait MaxHeight: Device { fn max_height (&self) -> u16 { u16::MAX diff --git a/src/main.rs b/src/main.rs index 392eb529..adada051 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,27 @@ pub mod layout; use crate::core::*; use crate::device::*; +mod new { + use crate::core::*; + type Phrase = (String, usize, BTreeMap>>); + type Scene = (String, Option, Vec>); + type Track = (String, usize); + #[derive(Default)] + struct App { + phrases: BTreeMap>>, + scenes: BTreeMap>>, + tracks: BTreeMap>>, + } + fn main () { + App::default().run() + } + impl App { + fn run (self) { + panic_hook_setup(); + } + } +} + macro_rules! sample { ($note:expr, $name:expr, $src:expr) => { { @@ -35,7 +56,7 @@ macro_rules! sample { }; } -fn main () -> Result<(), Box> { +fn main () -> Usually<()> { let _cli = cli::Cli::parse(); let xdg = microxdg::XdgApp::new("tek")?; crate::config::create_dirs(&xdg)?; @@ -58,7 +79,7 @@ fn main () -> Result<(), Box> { phrase }} } - let app = Launcher::new("Launcher#0", &timebase, + Launcher::new("Launcher#0", &timebase, Some(vec![ Track::new("Drums", &timebase, Some(vec![ @@ -191,6 +212,7 @@ fn main () -> Result<(), Box> { //Scene::new(&"Scene#05", &[None, None, None, None]), ]) - )?.connect(input, &output)?; - run(app) + )? + .connect(input, &output)? + .run() }