mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
convert to workspace, add suil bindings crate
This commit is contained in:
parent
bd6f8ff9bf
commit
dacce119c4
52 changed files with 1994 additions and 116 deletions
185
src/core.rs
185
src/core.rs
|
|
@ -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}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue