mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
chore: further cleanup
This commit is contained in:
parent
fe6ffea5df
commit
8d11ae87c0
5 changed files with 158 additions and 160 deletions
136
src/core.rs
136
src/core.rs
|
|
@ -1,9 +1,31 @@
|
||||||
|
// Stdlib dependencies:
|
||||||
|
pub use std::error::Error;
|
||||||
|
pub use std::io::{stdout, Stdout, Write};
|
||||||
|
pub use std::thread::{spawn, JoinHandle};
|
||||||
|
pub use std::time::Duration;
|
||||||
|
pub use std::collections::BTreeMap;
|
||||||
|
pub use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
|
||||||
|
pub use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
|
|
||||||
|
// Non-stdlib dependencies:
|
||||||
|
pub use microxdg::XdgApp;
|
||||||
|
pub use ratatui::prelude::*;
|
||||||
|
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
|
||||||
|
pub use crossterm::{ExecutableCommand, QueueableCommand};
|
||||||
|
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
||||||
|
use better_panic::{Settings, Verbosity};
|
||||||
|
use crossterm::terminal::{
|
||||||
|
EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
enable_raw_mode, disable_raw_mode
|
||||||
|
};
|
||||||
|
|
||||||
/// Define and reexport submodules.
|
/// Define and reexport submodules.
|
||||||
#[macro_export] macro_rules! submod {
|
#[macro_export] macro_rules! submod {
|
||||||
($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* };
|
($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* };
|
||||||
}
|
}
|
||||||
|
|
||||||
submod!( handle keymap midi render run time );
|
submod!( handle midi render time );
|
||||||
|
|
||||||
/// Standard result type.
|
/// Standard result type.
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
@ -36,19 +58,101 @@ pub use crate::{submod, render, handle, process, phrase, keymap, key, ports};
|
||||||
// Reexport JACK proto-lib:
|
// Reexport JACK proto-lib:
|
||||||
pub use crate::jack::*;
|
pub use crate::jack::*;
|
||||||
|
|
||||||
// Various stdlib dependencies:
|
impl<T: Render + Handle + Send + Sized + 'static> Run for T {}
|
||||||
pub use std::error::Error;
|
|
||||||
pub use std::io::{stdout, Stdout, Write};
|
|
||||||
pub use std::thread::{spawn, JoinHandle};
|
|
||||||
pub use std::time::Duration;
|
|
||||||
pub use std::collections::BTreeMap;
|
|
||||||
pub use std::sync::{Arc, Mutex, MutexGuard};
|
|
||||||
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
|
|
||||||
pub use std::sync::mpsc::{channel, Sender, Receiver};
|
|
||||||
|
|
||||||
// Various non-stdlib dependencies:
|
pub trait Run: Render + Handle + Send + Sized + 'static {
|
||||||
pub use microxdg::XdgApp;
|
fn run (self, init: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
|
||||||
pub use ratatui::prelude::*;
|
let app = Arc::new(Mutex::new(self));
|
||||||
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
|
let exited = Arc::new(AtomicBool::new(false));
|
||||||
pub use crossterm::{ExecutableCommand, QueueableCommand};
|
let _input_thread = input_thread(&exited, &app);
|
||||||
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
terminal_setup()?;
|
||||||
|
panic_hook_setup();
|
||||||
|
let main_thread = main_thread(&exited, &app)?;
|
||||||
|
if let Some(init) = init {
|
||||||
|
init(app)?;
|
||||||
|
}
|
||||||
|
main_thread.join().unwrap();
|
||||||
|
terminal_teardown()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up panic hook
|
||||||
|
pub fn panic_hook_setup () {
|
||||||
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
better_panic_handler(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up terminal
|
||||||
|
pub fn terminal_setup () -> Usually<()> {
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Cleanup
|
||||||
|
pub fn terminal_teardown () -> Usually<()> {
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
disable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main thread render loop
|
||||||
|
pub fn main_thread (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
device: &Arc<Mutex<impl Render + Send + 'static>>
|
||||||
|
) -> Usually<JoinHandle<()>> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let device = device.clone();
|
||||||
|
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||||
|
let sleep = std::time::Duration::from_millis(16);
|
||||||
|
Ok(spawn(move || 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);
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/// Spawn thread that listens for user input
|
||||||
|
pub fn input_thread (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
device: &Arc<Mutex<impl Handle + Send + 'static>>
|
||||||
|
) -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,3 +45,41 @@ pub enum AppEvent {
|
||||||
/// JACK notification
|
/// JACK notification
|
||||||
Jack(JackEvent)
|
Jack(JackEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
|
||||||
|
|
||||||
|
pub type KeyBinding<T> = (
|
||||||
|
KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>
|
||||||
|
);
|
||||||
|
|
||||||
|
pub type KeyMap<T> = [KeyBinding<T>];
|
||||||
|
|
||||||
|
pub fn handle_keymap <T> (
|
||||||
|
state: &mut T, event: &AppEvent, keymap: &KeyMap<T>,
|
||||||
|
) -> Usually<bool> {
|
||||||
|
match event {
|
||||||
|
AppEvent::Input(Event::Key(event)) => {
|
||||||
|
for (code, modifiers, _, _, command) in keymap.iter() {
|
||||||
|
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
|
||||||
|
return command(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! key {
|
||||||
|
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
|
||||||
|
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! keymap {
|
||||||
|
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
|
||||||
|
&[
|
||||||
|
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
|
||||||
|
] as &'static [KeyBinding<$T>]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
|
|
||||||
|
|
||||||
pub type KeyBinding<T> = (
|
|
||||||
KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>
|
|
||||||
);
|
|
||||||
|
|
||||||
pub type KeyMap<T> = [KeyBinding<T>];
|
|
||||||
|
|
||||||
pub fn handle_keymap <T> (
|
|
||||||
state: &mut T, event: &AppEvent, keymap: &KeyMap<T>,
|
|
||||||
) -> Usually<bool> {
|
|
||||||
match event {
|
|
||||||
AppEvent::Input(Event::Key(event)) => {
|
|
||||||
for (code, modifiers, _, _, command) in keymap.iter() {
|
|
||||||
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
|
|
||||||
return command(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! key {
|
|
||||||
($k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr) => {
|
|
||||||
(KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! keymap {
|
|
||||||
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
|
|
||||||
&[
|
|
||||||
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
|
|
||||||
] as &'static [KeyBinding<$T>]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
105
src/core/run.rs
105
src/core/run.rs
|
|
@ -1,105 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use better_panic::{Settings, Verbosity};
|
|
||||||
use crossterm::terminal::{
|
|
||||||
EnterAlternateScreen, LeaveAlternateScreen,
|
|
||||||
enable_raw_mode, disable_raw_mode
|
|
||||||
};
|
|
||||||
|
|
||||||
impl<T: Render + Handle + Send + Sized + 'static> Run for T {}
|
|
||||||
|
|
||||||
pub trait Run: Render + Handle + Send + Sized + 'static {
|
|
||||||
fn run (self, init: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
|
|
||||||
let app = Arc::new(Mutex::new(self));
|
|
||||||
let exited = Arc::new(AtomicBool::new(false));
|
|
||||||
let _input_thread = input_thread(&exited, &app);
|
|
||||||
terminal_setup()?;
|
|
||||||
panic_hook_setup();
|
|
||||||
let main_thread = main_thread(&exited, &app)?;
|
|
||||||
if let Some(init) = init {
|
|
||||||
init(app)?;
|
|
||||||
}
|
|
||||||
main_thread.join().unwrap();
|
|
||||||
terminal_teardown()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up panic hook
|
|
||||||
pub fn panic_hook_setup () {
|
|
||||||
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up terminal
|
|
||||||
pub fn terminal_setup () -> Usually<()> {
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
enable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Cleanup
|
|
||||||
pub fn terminal_teardown () -> Usually<()> {
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
disable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main thread render loop
|
|
||||||
pub fn main_thread (
|
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
device: &Arc<Mutex<impl Render + Send + 'static>>
|
|
||||||
) -> Usually<JoinHandle<()>> {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let device = device.clone();
|
|
||||||
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
|
||||||
let sleep = std::time::Duration::from_millis(16);
|
|
||||||
Ok(spawn(move || 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);
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/// Spawn thread that listens for user input
|
|
||||||
pub fn input_thread (
|
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
device: &Arc<Mutex<impl Handle + Send + 'static>>
|
|
||||||
) -> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue