convert to workspace, add suil bindings crate

This commit is contained in:
🪞👃🪞 2024-07-24 13:21:11 +03:00
parent bd6f8ff9bf
commit dacce119c4
52 changed files with 1994 additions and 116 deletions

View file

@ -1,185 +0,0 @@
//! Prelude.
// Stdlib dependencies:
pub(crate) use std::error::Error;
pub(crate) use std::io::{stdout};
pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration;
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::sync::atomic::{Ordering, AtomicBool};
pub(crate) use std::sync::{Arc, Mutex, RwLock, LockResult, RwLockReadGuard, RwLockWriteGuard};
pub(crate) use std::path::PathBuf;
pub(crate) use std::fs::read_dir;
pub(crate) use std::ffi::OsString;
// Non-stdlib dependencies:
pub(crate) use microxdg::XdgApp;
pub(crate) use midly::{MidiMessage, live::LiveEvent, num::u7};
pub(crate) use crossterm::{ExecutableCommand};
pub(crate) 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.
#[macro_export] macro_rules! submod {
($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* };
}
/// Define and reexport public modules.
#[macro_export] macro_rules! pubmod {
($($name:ident)*) => { $(pub mod $name;)* };
}
submod!( handle midi render time );
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
/// A UI component.
pub trait Component: Render + Handle + Sync {
/// Perform type erasure for collecting heterogeneous components.
fn boxed (self) -> Box<dyn Component> where Self: Sized + 'static {
Box::new(self)
}
}
pub trait Exit: Component {
fn exited (&self) -> bool;
fn exit (&mut self);
fn boxed (self) -> Box<dyn Exit> where Self: Sized + 'static {
Box::new(self)
}
}
#[macro_export] macro_rules! exit {
($T:ty) => {
impl Exit for $T {
fn exited (&self) -> bool {
self.exited
}
fn exit (&mut self) {
self.exited = true
}
}
}
}
/// Anything that implements `Render` + `Handle` can be used as a UI component.
impl<T: Render + Handle + Sync> Component for T {}
/// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait Device: Render + Handle + Process + Send + Sync {
/// Perform type erasure for collecting heterogeneous devices.
fn boxed (self) -> Box<dyn Device> where Self: Sized + 'static {
Box::new(self)
}
}
/// All things that implement the required traits can be treated as `Device`.
impl<T: Render + Handle + Process + Send + Sync> Device for T {}
// Reexport macros:
pub use crate::{
submod, pubmod, render, handle, process, phrase, keymap, ports, exit
};
// Reexport JACK proto-lib:
pub use crate::jack::*;
/// Run the main loop.
pub fn run <T> (state: Arc<RwLock<T>>) -> Usually<Arc<RwLock<T>>>
where T: Render + Handle + Send + Sync + Sized + 'static
{
let exited = Arc::new(AtomicBool::new(false));
let _input_thread = input_thread(&exited, &state);
terminal_setup()?;
panic_hook_setup();
let main_thread = main_thread(&exited, &state)?;
main_thread.join().expect("main thread failed");
terminal_teardown()?;
Ok(state)
}
/// 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<RwLock<impl Render + Send + Sync + 'static>>
) -> Usually<JoinHandle<()>> {
let exited = exited.clone();
let device = device.clone();
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
let sleep = Duration::from_millis(20);
Ok(spawn(move || loop {
if let Ok(device) = device.try_read() {
terminal.draw(|frame|{
let area = frame.size();
let buffer = frame.buffer_mut();
device
.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<RwLock<impl Handle + Send + Sync + 'static>>
) -> JoinHandle<()> {
let poll = 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();
if let Event::Key(KeyEvent {
code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, ..
}) = event {
exited.store(true, Ordering::Relaxed);
} else if let Err(e) = device.write().unwrap().handle(&AppEvent::Input(event)) {
panic!("{e}")
}
}
})
}