diff --git a/crates/tek_core/src/engine.rs b/crates/tek_core/src/engine.rs index 84469a69..fda5b15e 100644 --- a/crates/tek_core/src/engine.rs +++ b/crates/tek_core/src/engine.rs @@ -118,12 +118,12 @@ impl> Widget for Option { self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) } } -/// A component that consists of other components +/// A [Widget] that contains other [Widget]s pub trait Content: Send + Sync { type Engine: Engine; fn content (&self) -> impl Widget::Engine>; } -/// Every component is renderable +/// Every struct that has [Content] is a renderable [Widget]. impl> Widget for W { type Engine = E; fn layout (&self, to: E::Size) -> Perhaps { @@ -136,6 +136,34 @@ impl> Widget for W { } } } +/// A custom [Widget] defined by passing layout and render closures in place. +pub struct CustomWidget< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +>(L, R, PhantomData); +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> CustomWidget { + pub fn new (layout: L, render: R) -> Self { + Self(layout, render, Default::default()) + } +} +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Widget for CustomWidget { + type Engine = E; + fn layout (&self, to: E::Size) -> Perhaps { + self.0(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.1(to) + } +} /// Handle input pub trait Handle: Send + Sync { fn handle (&mut self, context: &E::Input) -> Perhaps; @@ -174,45 +202,15 @@ impl Handle for Arc> where H: Handle { self.write().unwrap().handle(context) } } -/// A UI component that can render itself and handle input +/// A UI component that can render itself as a [Widget], and [Handle] input. pub trait Component: Widget + Handle {} -/// Everything that implements [Render] and [Handle] is a [Component]. +/// Everything that implements [Widget] and [Handle] is a [Component]. impl + Handle> Component for C {} -/// A UI component that can render itself and handle input +/// A UI component that has [Content] and can [Handle] input. pub trait ContentComponent: Widget + Handle {} -/// Everything that implements [Render] and [Handle] is a [Component]. +/// Everything that implements [Content] and [Handle] is a [Component]. impl + Handle> ContentComponent for C {} - -pub struct CustomWidget< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> ->(L, R, PhantomData); - -impl< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> -> CustomWidget { - pub fn new (layout: L, render: R) -> Self { - Self(layout, render, Default::default()) - } -} -impl< - E: Engine, - L: Send + Sync + Fn(E::Size)->Perhaps, - R: Send + Sync + Fn(&mut E::Output)->Usually<()> -> Widget for CustomWidget { - type Engine = E; - fn layout (&self, to: E::Size) -> Perhaps { - self.0(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.1(to) - } -} - - +/// A component that can exit. pub trait Exit: Send { fn exited (&self) -> bool; fn exit (&mut self); @@ -220,109 +218,12 @@ pub trait Exit: Send { Box::new(self) } } - -/// Marker trait for [Component]s that can [Exit] +/// 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) } } +/// All [Components]s that implement [Exit] implement [ExitableComponent]. impl + Exit> ExitableComponent for C {} - -/// A component that may contain [Focusable] components. -pub trait Focus : Widget + 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 focused (&self) -> &dyn Focusable { - let focus = self.focus(); - self.focusable()[focus] - } - fn focused_mut (&mut self) -> &mut dyn Focusable { - let focus = self.focus(); - self.focusable_mut()[focus] - } - fn focus_prev (&mut self) { - let focus = self.focus(); - self.focus_set(if focus > 0 { focus - 1 } else { N - 1 }); - } - fn focus_next (&mut self) { - let focus = self.focus(); - self.focus_set(if focus < N - 1 { focus + 1 } else { 0 }); - } - fn focus_set (&mut self, focus: usize) { - *self.focus_mut() = focus; - let focusable = self.focusable_mut(); - for index in 0..N { - focusable[index].set_focused(index == focus); - } - } -} - -/// A component that may be focused. -pub trait Focusable: Widget + Handle { - fn is_focused (&self) -> bool; - fn set_focused (&mut self, focused: bool); -} - -impl, E: Engine> Focusable for Option - where Option: Widget -{ - fn is_focused (&self) -> bool { - match self { - Some(focusable) => focusable.is_focused(), - None => false - } - } - fn set_focused (&mut self, focused: bool) { - if let Some(focusable) = self { - focusable.set_focused(focused) - } - } -} - -/// Implement the [Focus] trait for a component. -#[macro_export] macro_rules! focus { - ($struct:ident ($focus:ident) : $count:expr => [ - $($focusable:ident),* - ]) => { - impl Focus<$count, E> for $struct { - fn focus (&self) -> usize { - self.$focus - } - fn focus_mut (&mut self) -> &mut usize { - &mut self.$focus - } - fn focusable (&self) -> [&dyn Focusable;$count] { - [ - $(&self.$focusable as &dyn Focusable,)* - ] - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;$count] { - [ - $(&mut self.$focusable as &mut dyn Focusable,)* - ] - } - } - } -} - -/// Implement the [Focusable] trait for a component. -#[macro_export] macro_rules! focusable { - ($struct:ident) => { - focusable!($struct (focused)); - }; - ($struct:ident ($focused:ident)) => { - impl Focusable for $struct { - fn is_focused (&self) -> bool { - self.$focused - } - fn set_focused (&mut self, focused: bool) { - self.$focused = focused - } - } - } -} diff --git a/crates/tek_core/src/focus.rs b/crates/tek_core/src/focus.rs new file mode 100644 index 00000000..103f1ced --- /dev/null +++ b/crates/tek_core/src/focus.rs @@ -0,0 +1,99 @@ +use crate::*; + +/// A component that may contain [Focusable] components. +pub trait Focus : Widget + 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 focused (&self) -> &dyn Focusable { + let focus = self.focus(); + self.focusable()[focus] + } + fn focused_mut (&mut self) -> &mut dyn Focusable { + let focus = self.focus(); + self.focusable_mut()[focus] + } + fn focus_prev (&mut self) { + let focus = self.focus(); + self.focus_set(if focus > 0 { focus - 1 } else { N - 1 }); + } + fn focus_next (&mut self) { + let focus = self.focus(); + self.focus_set(if focus < N - 1 { focus + 1 } else { 0 }); + } + fn focus_set (&mut self, focus: usize) { + *self.focus_mut() = focus; + let focusable = self.focusable_mut(); + for index in 0..N { + focusable[index].set_focused(index == focus); + } + } +} + +/// A component that may be focused. +pub trait Focusable: Widget + Handle { + fn is_focused (&self) -> bool; + fn set_focused (&mut self, focused: bool); +} + +impl, E: Engine> Focusable for Option + where Option: Widget +{ + fn is_focused (&self) -> bool { + match self { + Some(focusable) => focusable.is_focused(), + None => false + } + } + fn set_focused (&mut self, focused: bool) { + if let Some(focusable) = self { + focusable.set_focused(focused) + } + } +} + +/// Implement the [Focus] trait for a component. +#[macro_export] macro_rules! focus { + ($struct:ident ($focus:ident) : $count:expr => [ + $($focusable:ident),* + ]) => { + impl Focus<$count, E> for $struct { + fn focus (&self) -> usize { + self.$focus + } + fn focus_mut (&mut self) -> &mut usize { + &mut self.$focus + } + fn focusable (&self) -> [&dyn Focusable;$count] { + [$(&self.$focusable as &dyn Focusable,)*] + } + fn focusable_mut (&mut self) -> [&mut dyn Focusable;$count] { + [$(&mut self.$focusable as &mut dyn Focusable,)*] + } + } + } +} + +/// Implement the [Focusable] trait for a component. +#[macro_export] macro_rules! focusable { + ($struct:ident) => { + focusable!($struct (focused)); + }; + ($struct:ident ($focused:ident)) => { + impl Focusable for $struct { + fn is_focused (&self) -> bool { self.$focused } + fn set_focused (&mut self, f: bool) { self.$focused = f } + } + } +} + +#[macro_export] macro_rules! focusables { + ($($item:expr),* $(,)?) => { [$(&$item as &dyn Focusable<_>),+] } +} + +#[macro_export] macro_rules! focusables_mut { + ($($item:expr),* $(,)?) => { [$(&mut $item as &mut dyn Focusable<_>),+] } +} + diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 9e88c347..370c6a0c 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -39,6 +39,7 @@ submod! { audio edn engine + focus space time tui diff --git a/crates/tek_core/src/space.rs b/crates/tek_core/src/space.rs index 31921dd7..734fedf2 100644 --- a/crates/tek_core/src/space.rs +++ b/crates/tek_core/src/space.rs @@ -107,6 +107,8 @@ pub trait Area: Copy { #[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h() - y] } #[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] } #[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] } + #[inline] fn clip_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h().min(h)] } + #[inline] fn clip_w (&self, w: N) -> [N;4] { [self.x(), self.y(), self.w().min(w), self.h()] } } impl Area for (N, N, N, N) { diff --git a/crates/tek_sequencer/src/main_arranger.rs b/crates/tek_sequencer/src/main_arranger.rs index 58af6dc1..39faaa82 100644 --- a/crates/tek_sequencer/src/main_arranger.rs +++ b/crates/tek_sequencer/src/main_arranger.rs @@ -1,11 +1,7 @@ include!("lib.rs"); - use tek_core::clap::{self, Parser}; - -pub fn main () -> Usually<()> { - ArrangerCli::parse().run() -} - +pub fn main () -> Usually<()> { ArrangerCli::parse().run() } +/// Parses CLI arguments to the `tek_arranger` invocation. #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct ArrangerCli { @@ -25,8 +21,8 @@ pub struct ArrangerCli { #[arg(short, long, default_value_t = 8)] scenes: usize, } - impl ArrangerCli { + /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { let mut arranger = Arranger::new(""); let mut transport = self.transport.then_some(TransportToolbar::new(None)); @@ -57,7 +53,7 @@ impl ArrangerCli { Ok(()) } } - +/// Root level object for standalone `tek_arranger` struct ArrangerStandalone { /// Controls the JACK transport. transport: Option>, @@ -68,7 +64,7 @@ struct ArrangerStandalone { /// focus: usize, } - +/// The standalone arranger consists of transport, clip grid, and sequencer. impl Content for ArrangerStandalone { type Engine = Tui; fn content (&self) -> impl Widget { @@ -95,7 +91,7 @@ impl Content for ArrangerStandalone { }) } } - +/// Handle top-level events in standalone arranger. impl Handle for ArrangerStandalone { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { @@ -113,9 +109,9 @@ impl Handle for ArrangerStandalone { self.focus_prev(); }, key!(KeyCode::Down) => { - if self.focus == 0 || ( - self.focus == 1 && self.arranger.is_last_row() - ) { + if self.focus == 0 { + self.focus_next(); + } else if self.focus == 1 && self.arranger.is_last_row() { self.focus_next(); } else { return self.focused_mut().handle(from) @@ -133,7 +129,7 @@ impl Handle for ArrangerStandalone { Ok(Some(true)) } } - +/// Focusable items in standalone arranger. impl Focus<2, Tui> for ArrangerStandalone { fn focus (&self) -> usize { self.focus @@ -142,15 +138,9 @@ impl Focus<2, Tui> for ArrangerStandalone { &mut self.focus } fn focusable (&self) -> [&dyn Focusable;2] { - [ - &self.transport as &dyn Focusable, - &self.arranger as &dyn Focusable, - ] + focusables!(self.transport, self.arranger) } fn focusable_mut (&mut self) -> [&mut dyn Focusable;2] { - [ - &mut self.transport as &mut dyn Focusable, - &mut self.arranger as &mut dyn Focusable, - ] + focusables_mut!(self.transport, self.arranger) } } diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 154c2f4c..05be3a22 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -683,8 +683,10 @@ impl<'a> Widget for VerticalArrangerCursor<'a> { to.render_in(clip_area, &CORNERS)?; to.fill_bg(clip_area, Color::Rgb(40, 50, 30)); } else if let Some(track_area) = track_area { + to.render_in(track_area.clip_h(2), &CORNERS)?; to.fill_bg(track_area, Color::Rgb(40, 50, 30)); } else if let Some(scene_area) = scene_area { + to.render_in(scene_area.clip_w(offset-1), &CORNERS)?; to.fill_bg(scene_area, Color::Rgb(40, 50, 30)); } } @@ -2000,22 +2002,10 @@ impl Focus<5, Tui> for TransportToolbar { &mut self.focus } fn focusable (&self) -> [&dyn Focusable;5] { - [ - &self.playing as &dyn Focusable, - &self.bpm as &dyn Focusable, - &self.quant as &dyn Focusable, - &self.sync as &dyn Focusable, - &self.clock as &dyn Focusable, - ] + focusables!(self.playing, self.bpm, self.quant, self.sync, self.clock) } fn focusable_mut (&mut self) -> [&mut dyn Focusable;5] { - [ - &mut self.playing as &mut dyn Focusable, - &mut self.bpm as &mut dyn Focusable, - &mut self.quant as &mut dyn Focusable, - &mut self.sync as &mut dyn Focusable, - &mut self.clock as &mut dyn Focusable, - ] + focusables_mut!(self.playing, self.bpm, self.quant, self.sync, self.clock) } }