diff --git a/crates/tek_core/src/component.rs b/crates/tek_core/src/component.rs new file mode 100644 index 00000000..0a52325b --- /dev/null +++ b/crates/tek_core/src/component.rs @@ -0,0 +1,7 @@ +use crate::*; + +/// A UI component. +pub trait Component: Render + Handle {} + +/// Everything that implements [Render] and [Handle] is a [Component]. +impl + Handle> Component for C {} diff --git a/crates/tek_core/src/edn.rs b/crates/tek_core/src/edn.rs new file mode 100644 index 00000000..2709043e --- /dev/null +++ b/crates/tek_core/src/edn.rs @@ -0,0 +1,14 @@ +pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; + +/// EDN parsing helper. +#[macro_export] macro_rules! edn { + ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + match $edn { $($pat => $expr),* } + }; + ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { + for $edn in $args { + edn!($edn { $($pat => $expr),* }) + } + }; +} + diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs new file mode 100644 index 00000000..d8622819 --- /dev/null +++ b/crates/tek_core/src/engine.rs @@ -0,0 +1,23 @@ +use crate::*; + +/// Entry point for main loop +pub trait App { + fn run (self, context: T) -> Usually; +} + +/// Platform backend. +pub trait Engine { + type Handled; + type Rendered; + fn setup (&mut self) -> Usually<()> { + Ok(()) + } + fn teardown (&mut self) -> Usually<()> { + Ok(()) + } + fn handle (&self, _: &mut impl Handle) + -> Usually<()> where Self: Sized; + fn render (&mut self, _: &impl Render) + -> Usually<()> where Self: Sized; + fn exited (&self) -> bool; +} diff --git a/crates/tek_core/src/event.rs b/crates/tek_core/src/event.rs new file mode 100644 index 00000000..ceeff4fb --- /dev/null +++ b/crates/tek_core/src/event.rs @@ -0,0 +1,16 @@ +#[derive(Debug)] +pub enum AppEvent { + /// Terminal input + Input(::crossterm::event::Event), + /// Update values but not the whole form. + Update, + /// Update the whole form. + Redraw, + /// Device gains focus + Focus, + /// Device loses focus + Blur, + // /// JACK notification + // Jack(JackEvent) +} + diff --git a/crates/tek_core/src/exit.rs b/crates/tek_core/src/exit.rs index 008a6170..7f0690de 100644 --- a/crates/tek_core/src/exit.rs +++ b/crates/tek_core/src/exit.rs @@ -1,3 +1,5 @@ +use crate::*; + pub trait Exit: Send { fn exited (&self) -> bool; fn exit (&mut self); @@ -18,3 +20,11 @@ pub trait Exit: Send { } } } + +/// Marker trait for [Component]s that can [Exit] +pub trait ExitableComponent: Exit + Component where E: Engine { + /// Perform type erasure for collecting heterogeneous components. + fn boxed (self) -> Box> where Self: Sized + 'static { + Box::new(self) + } +} diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index ccc04306..11a139c1 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,7 +1,7 @@ use crate::*; /// A component that may contain [Focusable] components. -pub trait Focus <'a, const N: usize, T, U>: Render<'a, T, U> + Handle { +pub trait Focus : Render + Handle { fn focus (&self) -> usize; fn focus_mut (&mut self) -> &mut usize; fn focusable (&self) -> [&dyn Focusable;N]; @@ -33,13 +33,13 @@ pub trait Focus <'a, const N: usize, T, U>: Render<'a, T, U> + Handle { } /// A component that may be focused. -pub trait Focusable<'a, T, U>: Render<'a, T, U> + Handle { +pub trait Focusable: Render + Handle { fn is_focused (&self) -> bool; fn set_focused (&mut self, focused: bool); } -impl<'a, F: Focusable<'a, T, U>, T, U> Focusable<'a, T, U> for Option - where Option: Render<'a, T, U> +impl, T, U> Focusable for Option + where Option: Render { fn is_focused (&self) -> bool { match self { diff --git a/crates/tek_core/src/handle.rs b/crates/tek_core/src/handle.rs index 0186694f..72ea71f6 100644 --- a/crates/tek_core/src/handle.rs +++ b/crates/tek_core/src/handle.rs @@ -1,114 +1,46 @@ use crate::*; -/// Spawn thread that listens for user input -pub fn input_thread ( - exited: &Arc, - device: &Arc> -) -> 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}") - } - } - }) +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &mut T) -> Perhaps; } -/// Trait for things that handle input events. -pub trait Handle: Send + Sync { - /// Handle an input event. - /// Returns Ok(true) if the device handled the event. - /// This is the mechanism which allows nesting of components;. - fn handle (&mut self, _e: &AppEvent) -> Usually { - Ok(false) +impl Handle for &mut H where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + (*self).handle(context) } } -impl Handle for &mut T { - fn handle (&mut self, e: &AppEvent) -> Usually { - (*self).handle(e) - } -} - -impl Handle for Option { - fn handle (&mut self, e: &AppEvent) -> Usually { - match self { - Some(handle) => handle.handle(e), - None => Ok(false) +impl Handle for Option where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + if let Some(ref mut handle) = self { + handle.handle(context) + } else { + Ok(None) } } } -impl Handle for Mutex { - fn handle (&mut self, e: &AppEvent) -> Usually { - self.lock().unwrap().handle(e) +impl Handle for Mutex where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + self.lock().unwrap().handle(context) } } -impl Handle for Arc> { - fn handle (&mut self, e: &AppEvent) -> Usually { - self.lock().unwrap().handle(e) +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + self.lock().unwrap().handle(context) } } -impl Handle for RwLock { - fn handle (&mut self, e: &AppEvent) -> Usually { - self.write().unwrap().handle(e) +impl Handle for RwLock where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + self.write().unwrap().handle(context) } } -impl Handle for Arc> { - fn handle (&mut self, e: &AppEvent) -> Usually { - self.write().unwrap().handle(e) +impl Handle for Arc> where H: Handle { + fn handle (&mut self, context: &mut T) -> Perhaps { + self.write().unwrap().handle(context) } } - -/// Implement the `Handle` trait. -#[macro_export] macro_rules! handle { - ($T:ty) => { - impl Handle for $T {} - }; - ($T:ty |$self:ident, $e:ident|$block:expr) => { - impl Handle for $T { - fn handle (&mut $self, $e: &AppEvent) -> Usually { - $block - } - } - }; - ($T:ty = $handle:path) => { - impl Handle for $T { - fn handle (&mut self, e: &AppEvent) -> Usually { - $handle(self, e) - } - } - } -} - -#[derive(Debug)] -pub enum AppEvent { - /// Terminal input - Input(::crossterm::event::Event), - /// Update values but not the whole form. - Update, - /// Update the whole form. - Redraw, - /// Device gains focus - Focus, - /// Device loses focus - Blur, - // /// JACK notification - // Jack(JackEvent) -} diff --git a/crates/tek_core/src/handle_keymap.rs b/crates/tek_core/src/keymap.rs similarity index 100% rename from crates/tek_core/src/handle_keymap.rs rename to crates/tek_core/src/keymap.rs diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index edd00501..3bd674c4 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -5,33 +5,16 @@ pub use midly; pub use clap; pub use std::sync::{Arc, Mutex, LockResult, RwLock, RwLockReadGuard, RwLockWriteGuard}; pub use std::collections::BTreeMap; -pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers}; -pub use ratatui::prelude::{Rect, Style, Color, Buffer}; -pub use ratatui::style::Stylize; -pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; pub use once_cell::sync::Lazy; pub use std::sync::atomic::{Ordering, AtomicBool}; pub use std::rc::Rc; pub use std::cell::RefCell; - 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 atomic_float::*; -//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}; 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 { @@ -44,38 +27,26 @@ use crossterm::terminal::{ } submod! { + component + edn + engine + event exit focus handle - handle_keymap jack_core jack_device jack_event jack_ports + keymap render render_axis render_buffer render_collect render_layered render_split - render_tui - render_tui_theme - render_tui_border - time_base - time_note - time_tick -} - -/// EDN parsing helper. -#[macro_export] macro_rules! edn { - ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - match $edn { $($pat => $expr),* } - }; - ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - for $edn in $args { - edn!($edn { $($pat => $expr),* }) - } - }; + time + tui } /// Standard result type. @@ -83,37 +54,3 @@ pub type Usually = Result>; /// Standard optional result type. pub type Perhaps = Result, Box>; - -/// A UI component. -pub trait Component<'a, T, U>: Render<'a, T, U> + Handle + Sync { - /// Perform type erasure for collecting heterogeneous components. - fn boxed (self) -> Box> where Self: Sized + 'static { - Box::new(self) - } -} - -impl<'a, C, T, U> Component<'a, T, U> for C where C: Render<'a, T, U> + Handle + Sync {} - -/// Marker trait for [Component]s that can [Exit] -pub trait ExitableComponent<'a, T, U>: Exit + Component<'a, T, U> { - /// Perform type erasure for collecting heterogeneous components. - fn boxed (self) -> Box> where Self: Sized + 'static { - Box::new(self) - } -} - -impl<'a, E: Exit + Component<'a, T, U>, T, U> ExitableComponent<'a, T, U> for E {} - -/// Run the main loop. -pub fn run (state: Arc>) -> Usually>> - where R: for <'a> Render<'a, TuiOutput<'a>, Rect> + Handle + Sized + 'static -{ - let exited = Arc::new(AtomicBool::new(false)); - let _input_thread = input_thread(&exited, &state); - terminal_setup()?; - panic_hook_setup(); - let main_thread = tui_render_thread(&exited, &state)?; - main_thread.join().expect("main thread failed"); - terminal_teardown()?; - Ok(state) -} diff --git a/crates/tek_core/src/render.rs b/crates/tek_core/src/render.rs index f7935709..1cef9691 100644 --- a/crates/tek_core/src/render.rs +++ b/crates/tek_core/src/render.rs @@ -1,16 +1,13 @@ -//! Rendering of application to display. - use crate::*; -pub(crate) use ratatui::prelude::CrosstermBackend; -/// Trait for things that are displayed to the user. -pub trait Render<'a, T, U>: Send + Sync { - fn render (&self, to: &'a mut T) -> Perhaps; +/// Render to output. +pub trait Render: Send + Sync { + fn render (&self, context: &mut T) -> Perhaps; } /// Options can be rendered optionally. -impl<'a, R, T, U> Render<'a, T, U> for Option where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for Option where R: Render { + fn render (&self, to: &mut T) -> Perhaps { match self { Some(component) => component.render(to), None => Ok(None) @@ -19,43 +16,43 @@ impl<'a, R, T, U> Render<'a, T, U> for Option where R: Render<'a, T, U> { } /// Boxed references can be rendered. -impl<'a, T, U> Render<'a, T, U> for Box + 'a> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl<'a, T, U> Render for Box + 'a> { + fn render (&self, to: &mut T) -> Perhaps { (**self).render(to) } } /// Immutable references can be rendered. -impl<'a, R, T, U> Render<'a, T, U> for &R where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for &R where R: Render { + fn render (&self, to: &mut T) -> Perhaps { (*self).render(to) } } /// Mutable references can be rendered. -impl<'a, R, T, U> Render<'a, T, U> for &mut R where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for &mut R where R: Render { + fn render (&self, to: &mut T) -> Perhaps { (**self).render(to) } } /// Counted references can be rendered. -impl<'a, R, T, U> Render<'a, T, U> for Arc where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for Arc where R: Render { + fn render (&self, to: &mut T) -> Perhaps { self.as_ref().render(to) } } /// References behind a [Mutex] can be rendered. -impl<'a, R, T, U> Render<'a, T, U> for Mutex where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for Mutex where R: Render { + fn render (&self, to: &mut T) -> Perhaps { self.lock().unwrap().render(to) } } /// References behind a [RwLock] can be rendered. -impl<'a, R, T, U> Render<'a, T, U> for RwLock where R: Render<'a, T, U> { - fn render (&self, to: &'a mut T) -> Perhaps { +impl Render for RwLock where R: Render { + fn render (&self, to: &mut T) -> Perhaps { self.read().unwrap().render(to) } } @@ -65,7 +62,7 @@ impl<'a, R, T, U> Render<'a, T, U> for RwLock where R: Render<'a, T, U> { /// Rendering unboxed closures should also be possible; /// but in practice implementing the trait for an unboxed /// `Fn` closure causes an impl conflict. -impl<'a, T, U> Render<'a, T, U> for Box Perhaps + Send + Sync + 'a> { +impl<'a, T, U> Render for Box Perhaps + Send + Sync + 'a> { fn render (&self, to: &mut T) -> Perhaps { (*self)(to) } diff --git a/crates/tek_core/src/render_layered.rs b/crates/tek_core/src/render_layered.rs index 95f1199d..ad4abc17 100644 --- a/crates/tek_core/src/render_layered.rs +++ b/crates/tek_core/src/render_layered.rs @@ -9,19 +9,19 @@ impl<'a, T, U> Layered<'a, T, U> { } impl<'a, T, U> Collect<'a, T, U> for Layered<'a, T, U> { - fn add_box (mut self, item: Box + 'a>) -> Self { + fn add_box (mut self, item: Box + 'a>) -> Self { self.0 = self.0.add_box(item); self } - fn add_ref (mut self, item: &'a dyn Render<'a, T, U>) -> Self { + fn add_ref (mut self, item: &'a dyn Render) -> Self { self.0 = self.0.add_ref(item); self } } -impl<'a, 'b> Render<'a, TuiOutput<'b>, Rect> for Layered<'a, TuiOutput<'b>, Rect> { - fn render (&self, to: &'a mut TuiOutput<'b>) -> Perhaps { - let area = to.area; +impl<'a> Render for Layered<'a, TuiContext, Rect> { + fn render (&self, to: &mut impl TuiTarget) -> Perhaps { + let area = to.area(); for layer in self.0.0.iter() { layer.render(to)?; } diff --git a/crates/tek_core/src/render_tui.rs b/crates/tek_core/src/render_tui.rs deleted file mode 100644 index 3be11af8..00000000 --- a/crates/tek_core/src/render_tui.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::*; - -pub(crate) use ratatui::buffer::Cell; -use ratatui::backend::Backend; - -pub struct TuiOutput<'a> { - pub buffer: &'a mut Buffer, - pub area: Rect -} -impl<'a> TuiOutput<'a> { - pub fn area (&'a mut self, area: Rect) -> Self { - Self { buffer: self.buffer, area } - } -} - -/// Main thread render loop -pub fn tui_render_thread (exited: &Arc, device: &Arc>) - -> Usually> -where - T: for <'a> Render<'a, TuiOutput<'a>, Rect> + 'static -{ - let exited = exited.clone(); - let device = device.clone(); - let mut backend = CrosstermBackend::new(stdout()); - let area = backend.size()?; - let mut buffers = [Buffer::empty(area), Buffer::empty(area)]; - let mut index = 0; - let sleep = Duration::from_millis(20); - Ok(spawn(move || { - loop { - if let Ok(device) = device.try_read() { - let mut target = TuiOutput { buffer: &mut buffers[index], area }; - device.render(&mut target).expect("render failed"); - let previous_buffer = &buffers[1 - index]; - let current_buffer = &buffers[index]; - let updates = previous_buffer.diff(current_buffer); - backend.draw(updates.into_iter()).expect("failed to render"); - buffers[1 - index].reset(); - index = 1 - index; - } - if exited.fetch_and(true, Ordering::Relaxed) { - break - } - std::thread::sleep(sleep); - } - })) -} - -/// 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(()) -} - -/// A simpler analog to [Render]. -pub trait Blit { - // Render something to X, Y coordinates in a buffer, ignoring width/height. - fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option