diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs index b9d07797..3134fdd2 100644 --- a/crates/tek_core/src/focus.rs +++ b/crates/tek_core/src/focus.rs @@ -1,17 +1,17 @@ use crate::*; /// A component that may contain [Focusable] components. -pub trait Focus : Render + Handle { +pub trait Focus : Render + Handle { fn focus (&self) -> usize; fn focus_mut (&mut self) -> &mut usize; - fn focusable (&self) -> [&dyn Focusable;N]; - fn focusable_mut (&mut self) -> [&mut dyn Focusable;N]; + fn focusable (&self) -> [&dyn Focusable;N]; + fn focusable_mut (&mut self) -> [&mut dyn Focusable;N]; - fn focused (&self) -> &dyn Focusable { + fn focused (&self) -> &dyn Focusable { let focus = self.focus(); self.focusable()[focus] } - fn focused_mut (&mut self) -> &mut dyn Focusable { + fn focused_mut (&mut self) -> &mut dyn Focusable { let focus = self.focus(); self.focusable_mut()[focus] } @@ -33,12 +33,14 @@ pub trait Focus : Render + Handle { } /// A component that may be focused. -pub trait Focusable: Render + Handle { +pub trait Focusable: Render + Handle { fn is_focused (&self) -> bool; fn set_focused (&mut self, focused: bool); } -impl Focusable for Option { +impl, T, U> Focusable for Option + where Option: Render +{ fn is_focused (&self) -> bool { match self { Some(focusable) => focusable.is_focused(), diff --git a/crates/tek_core/src/jack_core.rs b/crates/tek_core/src/jack_core.rs index 904a5b40..56889364 100644 --- a/crates/tek_core/src/jack_core.rs +++ b/crates/tek_core/src/jack_core.rs @@ -1,19 +1,19 @@ use crate::{*, jack::*}; /// A UI component that may be associated with a JACK client by the `Jack` factory. -pub trait Device: Render + Handle + Process + Send + Sync { +pub trait Device: Render + Handle + Process + Send + Sync { /// Perform type erasure for collecting heterogeneous devices. - fn boxed (self) -> Box where Self: Sized + 'static { + fn boxed (self) -> Box> where Self: Sized + 'static { Box::new(self) } } /// All things that implement the required traits can be treated as `Device`. -impl Device for T {} +impl Device for D where D: Render + Handle + Process {} -impl Render for Box { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - (**self).render(b, a) +impl Render for Box> { + fn render (&self, to: &mut T) -> Result, Box> { + (**self).render(to) } } @@ -143,10 +143,8 @@ impl Jack { )?.0, }) } - pub fn run ( - self, state: impl FnOnce(JackPorts)->Box - ) - -> Usually + pub fn run (self, state: impl FnOnce(JackPorts)->Box) -> Usually> + where D: Device + Process + Sized + 'static { let owned_ports = JackPorts { audio_ins: register_ports(&self.client, self.audio_ins, AudioIn)?, @@ -162,7 +160,7 @@ impl Jack { .map(|p|Ok(p.name()?)).collect::>>()?; let audio_ins = owned_ports.audio_ins.values() .map(|p|Ok(p.name()?)).collect::>>()?; - let state = Arc::new(RwLock::new(state(owned_ports) as Box)); + let state = Arc::new(RwLock::new(state(owned_ports) as Box>)); let client = self.client.activate_async( Notifications(Box::new({ let _state = state.clone(); diff --git a/crates/tek_core/src/jack_device.rs b/crates/tek_core/src/jack_device.rs index b021ef72..b2bce35f 100644 --- a/crates/tek_core/src/jack_device.rs +++ b/crates/tek_core/src/jack_device.rs @@ -1,39 +1,51 @@ use crate::{*, jack::*}; /// A [Device] bound to a JACK client and a set of ports. -pub struct JackDevice { +pub struct JackDevice { /// The active JACK client of this device. pub client: DynamicAsyncClient, /// The device state, encapsulated for sharing between threads. - pub state: Arc>>, + pub state: Arc>>>, /// Unowned copies of the device's JACK ports, for connecting to the device. /// The "real" readable/writable `Port`s are owned by the `state`. pub ports: UnownedJackPorts, } -impl std::fmt::Debug for JackDevice { +impl std::fmt::Debug for JackDevice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("JackDevice").field("ports", &self.ports).finish() } } -render!(JackDevice |self, buf, area| self.state.read().unwrap().render(buf, area)); -handle!(JackDevice |self, event| self.state.write().unwrap().handle(event)); -ports!(JackDevice { - audio: { - ins: |s|Ok(s.ports.audio_ins.values().collect()), - outs: |s|Ok(s.ports.audio_outs.values().collect()), +impl<'a, T, U> Render for JackDevice { + fn render (&self, to: &mut T) -> Perhaps { + self.state.read().unwrap().render(to) } - midi: { - ins: |s|Ok(s.ports.midi_ins.values().collect()), - outs: |s|Ok(s.ports.midi_outs.values().collect()), +} +impl Handle for JackDevice { + fn handle (&mut self, event: &AppEvent) -> Usually { + self.state.write().unwrap().handle(event) } -}); -impl JackDevice { +} +impl Ports for JackDevice { + fn audio_ins (&self) -> Usually>> { + Ok(self.ports.audio_ins.values().collect()) + } + fn audio_outs (&self) -> Usually>> { + Ok(self.ports.audio_outs.values().collect()) + } + fn midi_ins (&self) -> Usually>> { + Ok(self.ports.midi_ins.values().collect()) + } + fn midi_outs (&self) -> Usually>> { + Ok(self.ports.midi_outs.values().collect()) + } +} +impl JackDevice { /// Returns a locked mutex of the state's contents. - pub fn state (&self) -> LockResult>> { + pub fn state (&self) -> LockResult>>> { self.state.read() } /// Returns a locked mutex of the state's contents. - pub fn state_mut (&self) -> LockResult>> { + pub fn state_mut (&self) -> LockResult>>> { self.state.write() } pub fn connect_midi_in (&self, index: usize, port: &Port) -> Usually<()> { diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 3280395e..a02d24e5 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -55,11 +55,13 @@ submod! { render render_axis render_border + render_buffer render_collect - render_fill render_layered render_split - render_theme + render_tui + render_tui_fill + render_tui_theme time_base time_note time_tick @@ -80,59 +82,39 @@ submod! { /// Standard result type. pub type Usually = Result>; +/// Standard optional result type. +pub type Perhaps = Result, Box>; + /// A UI component. -pub trait Component: Render + Handle + Sync { +pub trait Component: Render + Handle + Sync { /// Perform type erasure for collecting heterogeneous components. - fn boxed (self) -> Box where Self: Sized + 'static { + fn boxed (self) -> Box> where Self: Sized + 'static { Box::new(self) } } -impl Component for T {} +impl Component for C where C: Render + Handle + Sync {} /// Marker trait for [Component]s that can [Exit] -pub trait ExitableComponent: Exit + Component { +pub trait ExitableComponent: Exit + Component { /// Perform type erasure for collecting heterogeneous components. - fn boxed (self) -> Box where Self: Sized + 'static { + fn boxed (self) -> Box> where Self: Sized + 'static { Box::new(self) } } -impl ExitableComponent for T {} +impl, T, U> ExitableComponent for E {} /// Run the main loop. -pub fn run (state: Arc>) -> Usually>> - where T: Render + Handle + Send + Sync + Sized + 'static +pub fn run <'a, R> (state: Arc>) -> Usually>> + where R: Render, 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 = render_thread(&exited, &state)?; + let main_thread = tui_render_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(()) -} diff --git a/crates/tek_core/src/render.rs b/crates/tek_core/src/render.rs index 4e09170f..0471b1c0 100644 --- a/crates/tek_core/src/render.rs +++ b/crates/tek_core/src/render.rs @@ -2,230 +2,60 @@ use crate::*; pub(crate) use ratatui::prelude::CrosstermBackend; -pub(crate) use ratatui::style::Style; -pub(crate) use ratatui::layout::Rect; -pub(crate) use ratatui::buffer::{Buffer, Cell}; -/// Main thread render loop -pub fn render_thread ( - exited: &Arc, - device: &Arc> -) -> Usually> { - 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); - })) +/// Trait for things that are displayed to the user. +pub trait Render: Send + Sync { + fn render (&self, to: &mut T) -> Perhaps; } -/// Trait for things that render to the display. -pub trait Render: Send + Sync { - // Render something to an area of the buffer. - // Returns area used by component. - // This is insufficient but for the most basic dynamic layout algorithms. - fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually { - Ok(Rect { x: 0, y: 0, width: 0, height: 0 }) - } - fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { - Collected::Box(Box::new(self)) - } -} - -/// Implement the `Render` trait. -#[macro_export] macro_rules! render { - ($T:ty) => { - impl Render for $T {} - }; - ($T:ty |$self:ident, $buf:ident, $area:ident|$block:expr) => { - impl Render for $T { - fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually { - $block - } - } - }; - ($T:ty = $render:path) => { - impl Render for $T { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - $render(self, buf, area) - } - } - } -} - -impl Render for () { - fn render (&self, _: &mut Buffer, a: Rect) -> Usually { - Ok(Rect { x: a.x, y: a.y, width: 0, height: 0 }) - } -} - -impl Render for &T { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - (*self).render(buf, area) - } - fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { - Collected::Ref(self) - } -} - -impl Render for &mut T { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - (**self).render(buf, area) - } - fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a { - Collected::Ref(self) - } -} - -impl Render for Option { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { +/// Options can be rendered optionally. +impl Render for Option where R: Render { + fn render (&self, to: &mut T) -> Perhaps { match self { - Some(widget) => widget.render(b, a), - None => ().render(b, a), + Some(component) => component.render(to), + None => Ok(None) } } } -impl<'a> Render for Box { - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - (**self).render(buf, area) - } - fn into_collected <'b> (self) -> Collected<'b> where Self: Sized + 'b { - Collected::Box(self) +/// Boxed references can be rendered. +impl<'a, T, U> Render for Box + 'a> { + fn render (&self, to: &mut T) -> Perhaps { + (**self).render(to) } } -impl Render for Arc { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - self.as_ref().render(b, a) +/// Immutable references can be rendered. +impl Render for &R where R: Render { + fn render (&self, to: &mut T) -> Perhaps { + (*self).render(to) } } -impl Render for Mutex { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - self.lock().unwrap().render(b, a) +/// Mutable references can be rendered. +impl Render for &mut R where R: Render { + fn render (&self, to: &mut T) -> Perhaps { + (**self).render(to) } } -impl Render for RwLock { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - self.read().unwrap().render(b, a) +/// Counted references can be rendered. +impl Render for Arc where R: Render { + fn render (&self, to: &mut T) -> Perhaps { + self.as_ref().render(to) } } -//impl<'a, T: Fn(&mut Buffer, Rect) -> Usually + Send + Sync + 'a> Render for T { - //fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - //(*self)(b, a) - //} -//} - -impl<'a> Render for Box Usually + Send + Sync + 'a> { - fn render (&self, b: &mut Buffer, a: Rect) -> Usually { - (*self)(b, a) +/// References behind a [Mutex] can be rendered. +impl Render for Mutex where R: Render { + fn render (&self, to: &mut T) -> Perhaps { + self.lock().unwrap().render(to) } } -pub fn center_box (area: Rect, w: u16, h: u16) -> Rect { - let width = w.min(area.width * 3 / 5); - let height = h.min(area.width * 3 / 5); - let x = area.x + (area.width - width) / 2; - let y = area.y + (area.height - height) / 2; - Rect { x, y, width, height } -} - -pub fn half_block (lower: bool, upper: bool) -> Option { - match (lower, upper) { - (true, true) => Some('█'), - (true, false) => Some('▄'), - (false, true) => Some('▀'), - _ => None - } -} - -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