mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
separate render/content macros; add has_jack
This commit is contained in:
parent
08184f9906
commit
e62e36d558
19 changed files with 183 additions and 226 deletions
|
|
@ -1,4 +1,8 @@
|
|||
use crate::*;
|
||||
/// Turns an EDN item sequence into a command enum variant.
|
||||
pub trait EdnCommand<C>: Command<C> {
|
||||
fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self;
|
||||
}
|
||||
/** Implement `EdnCommand` for given `State` and `Command` */
|
||||
#[macro_export] macro_rules! edn_command {
|
||||
($Command:ty : |$state:ident:$State:ty| { $((
|
||||
|
|
@ -47,16 +51,3 @@ use crate::*;
|
|||
let $arg: $type = EdnProvide::<$type>::get_or_fail($state, $arg);
|
||||
};
|
||||
}
|
||||
/// Turns an EDN symbol sequence into a command enum variant.
|
||||
pub trait EdnCommand<C>: Command<C> {
|
||||
fn from_edn <'a> (state: &C, head: &EdnItem<&str>, tail: &'a [EdnItem<String>]) -> Self;
|
||||
fn get_isize (state: &C, item: &EdnItem<&str>) -> Option<isize> {
|
||||
None
|
||||
}
|
||||
fn get_usize (state: &C, item: &EdnItem<&str>) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
fn get_bool (state: &C, item: &EdnItem<&str>) -> Option<usize> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
use EdnItem::*;
|
||||
|
||||
pub trait EdnInput: Input {
|
||||
fn matches (&self, token: &str) -> bool;
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
|
||||
#[macro_export] macro_rules! from_jack {
|
||||
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
|
||||
Ok($cb)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -3,3 +3,22 @@ use crate::*;
|
|||
pub trait HasJack {
|
||||
fn jack (&self) -> &Arc<RwLock<JackConnection>>;
|
||||
}
|
||||
|
||||
/// Implement [HasJack].
|
||||
#[macro_export] macro_rules! has_jack {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasJack for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn jack (&$self) -> &Arc<RwLock<JackConnection>> { $cb }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
|
||||
#[macro_export] macro_rules! from_jack {
|
||||
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
|
||||
type Error = Box<dyn std::error::Error>;
|
||||
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> { Ok($cb) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ pub(crate) use ::jack::{
|
|||
Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
|
||||
};
|
||||
|
||||
mod from_jack; pub use self::from_jack::*;
|
||||
mod has_jack; pub use self::has_jack::*;
|
||||
mod jack_audio; pub use self::jack_audio::*;
|
||||
mod jack_connect; pub use self::jack_connect::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
has_size!(<TuiOut>|self: MidiEditor|&self.size);
|
||||
render!(TuiOut: (self: MidiEditor) => {
|
||||
content!(TuiOut: |self: MidiEditor| {
|
||||
self.autoscroll();
|
||||
//self.autozoom();
|
||||
self.size.of(&self.mode)
|
||||
|
|
@ -17,7 +17,6 @@ impl MidiEditor {
|
|||
FieldV(color, "Loop", looped.to_string())
|
||||
)
|
||||
}
|
||||
|
||||
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
||||
let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||
(clip.color, clip.length)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
pub struct PoolView<'a>(pub bool, pub &'a MidiPool);
|
||||
render!(TuiOut: (self: PoolView<'a>) => {
|
||||
content!(TuiOut: |self: PoolView<'a>| {
|
||||
let Self(compact, model) = self;
|
||||
let MidiPool { clips, mode, .. } = self.1;
|
||||
let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::g(32).into());
|
||||
|
|
@ -24,7 +24,7 @@ render!(TuiOut: (self: PoolView<'a>) => {
|
|||
))))
|
||||
})))))
|
||||
});
|
||||
render!(TuiOut: (self: ClipLength) => {
|
||||
content!(TuiOut: |self: ClipLength| {
|
||||
let bars = ||self.bars_string();
|
||||
let beats = ||self.beats_string();
|
||||
let ticks = ||self.ticks_string();
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct ClipSelected {
|
||||
pub(crate) title: &'static str,
|
||||
pub(crate) name: Arc<str>,
|
||||
pub(crate) color: ItemPalette,
|
||||
pub(crate) time: Arc<str>,
|
||||
}
|
||||
|
||||
render!(TuiOut: (self: ClipSelected) =>
|
||||
FieldV(self.color, self.title, format!("{} {}", self.time, self.name)));
|
||||
|
||||
content!(TuiOut: |self: ClipSelected|FieldV(
|
||||
self.color,
|
||||
self.title,
|
||||
format!("{} {}", self.time, self.name)
|
||||
));
|
||||
impl ClipSelected {
|
||||
|
||||
/// Shows currently playing clip with beats elapsed
|
||||
pub fn play_clip <T: HasPlayClip + HasClock> (state: &T) -> Self {
|
||||
let (name, color) = if let Some((_, Some(clip))) = state.play_clip() {
|
||||
|
|
@ -31,7 +30,6 @@ impl ClipSelected {
|
|||
.unwrap_or_else(||String::from(" ")).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows next clip with beats remaining until switchover
|
||||
pub fn next_clip <T: HasPlayClip> (state: &T) -> Self {
|
||||
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||
|
|
@ -69,5 +67,4 @@ impl ClipSelected {
|
|||
};
|
||||
Self { title: "Next", time, name, color, }
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl PianoHorizontal {
|
|||
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
|
||||
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
|
||||
}
|
||||
render!(TuiOut: (self: PianoHorizontal) => Tui::bg(TuiTheme::g(40), Bsp::s(
|
||||
content!(TuiOut:|self: PianoHorizontal| Tui::bg(TuiTheme::g(40), Bsp::s(
|
||||
Bsp::e(
|
||||
Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())),
|
||||
self.timeline()
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
use crate::*;
|
||||
/// Build a [Render]able out of other [Render]ables,
|
||||
/// then apply optional custom render/layout on top.
|
||||
pub trait Content<E: Output>: Send + Sync + Sized {
|
||||
fn content (&self) -> impl Render<E> { () }
|
||||
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
|
||||
fn render (&self, output: &mut E) { self.content().render(output) }
|
||||
}
|
||||
impl<E: Output, C: Content<E>> Content<E> for &C {
|
||||
fn content (&self) -> impl Render<E> { (*self).content() }
|
||||
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
|
||||
fn render (&self, output: &mut E) { (*self).render(output) }
|
||||
}
|
||||
/// The platonic ideal unit of [Content]: total emptiness at dead center.
|
||||
impl<E: Output> Content<E> for () {
|
||||
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
|
||||
fn render (&self, _: &mut E) {}
|
||||
}
|
||||
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
self.as_ref()
|
||||
.map(|content|content.layout(area))
|
||||
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
||||
}
|
||||
fn render (&self, output: &mut E) {
|
||||
self.as_ref()
|
||||
.map(|content|content.render(output));
|
||||
}
|
||||
}
|
||||
//impl<E: Output, T: Content<E>, E: Content<E>> Content<E> for Option<T> {
|
||||
//fn content (&self) -> impl Render<E> {
|
||||
//self.as_ref()
|
||||
//}
|
||||
//fn layout (&self, area: E::Area) -> E::Area {
|
||||
//self.as_ref()
|
||||
//.map(|content|content.layout(area))
|
||||
//.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
||||
//}
|
||||
//fn render (&self, output: &mut E) {
|
||||
//self.as_ref()
|
||||
//.map(|content|content.render(output));
|
||||
//}
|
||||
//}
|
||||
|
|
@ -94,7 +94,6 @@ impl<E: Output, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<E, A, B>
|
|||
impl<'a, E: Output + 'a, T: EdnViewData<'a, E>> TryFromEdn<'a, T> for Bsp<E, RenderBox<'a, E>, RenderBox<'a, E>> {
|
||||
fn try_from_edn (s: &'a T, head: &EdnItem<&str>, tail: &'a [EdnItem<&str>]) -> Option<Self> {
|
||||
use EdnItem::*;
|
||||
panic!("({head} {} {})", tail[0], tail[1]);
|
||||
Some(match (head, tail) {
|
||||
(Key("bsp/n"), [a, b]) => Self::n(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
||||
(Key("bsp/s"), [a, b]) => Self::s(s.get_content(a).expect("no a"), s.get(b).expect("no b")),
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ mod coordinate; pub use self::coordinate::*;
|
|||
mod size; pub use self::size::*;
|
||||
mod area; pub use self::area::*;
|
||||
|
||||
mod output; pub use self::output::*;
|
||||
mod content; pub use self::content::*;
|
||||
mod render; pub use self::render::*;
|
||||
mod thunk; pub use self::thunk::*;
|
||||
mod output; pub use self::output::*;
|
||||
mod thunk; pub use self::thunk::*;
|
||||
|
||||
mod when; pub use self::when::*;
|
||||
mod either; pub use self::either::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
/// Render target
|
||||
use std::ops::Deref;
|
||||
/// Render target.
|
||||
pub trait Output: Send + Sync + Sized {
|
||||
/// Unit of length
|
||||
type Unit: Coordinate;
|
||||
|
|
@ -20,3 +20,113 @@ pub trait Output: Send + Sync + Sized {
|
|||
#[inline] fn h (&self) -> Self::Unit { self.area().h() }
|
||||
#[inline] fn wh (&self) -> Self::Size { self.area().wh().into() }
|
||||
}
|
||||
/// Renderable with dynamic dispatch.
|
||||
pub trait Render<E: Output>: Send + Sync {
|
||||
/// Compute layout.
|
||||
fn layout (&self, area: E::Area) -> E::Area;
|
||||
/// Write data to display.
|
||||
fn render (&self, output: &mut E);
|
||||
/// Perform type erasure, turning `self` into an opaque [RenderBox].
|
||||
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a {
|
||||
Box::new(self) as RenderBox<'a, E>
|
||||
}
|
||||
}
|
||||
/// Most importantly, every [Content] is also a [Render].
|
||||
///
|
||||
/// However, the converse does not hold true.
|
||||
/// Instead, the [Content::content] method returns an
|
||||
/// opaque [Render] pointer.
|
||||
impl<E: Output, C: Content<E>> Render<E> for C {
|
||||
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
|
||||
fn render (&self, output: &mut E) { Content::render(self, output) }
|
||||
}
|
||||
/// Opaque pointer to a renderable living on the heap.
|
||||
///
|
||||
/// Return this from [Content::content] to use dynamic dispatch.
|
||||
pub type RenderBox<'a, E> = Box<RenderDyn<'a, E>>;
|
||||
/// You can render from a box.
|
||||
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
|
||||
fn content (&self) -> impl Render<E> { self.deref() }
|
||||
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
|
||||
}
|
||||
/// Opaque pointer to a renderable.
|
||||
pub type RenderDyn<'a, E> = dyn Render<E> + 'a;
|
||||
/// You can render from an opaque pointer.
|
||||
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
|
||||
fn content (&self) -> impl Render<E> { self.deref() }
|
||||
fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) }
|
||||
fn render (&self, output: &mut E) { Render::render(self.deref(), output) }
|
||||
}
|
||||
/// Composable renderable with static dispatch.
|
||||
pub trait Content<E: Output>: Send + Sync + Sized {
|
||||
/// Return a [Render]able of a specific type.
|
||||
fn content (&self) -> impl Render<E> { () }
|
||||
/// Perform layout. By default, delegates to [Self::content].
|
||||
fn layout (&self, area: E::Area) -> E::Area { self.content().layout(area) }
|
||||
/// Draw to output. By default, delegates to [Self::content].
|
||||
fn render (&self, output: &mut E) { self.content().render(output) }
|
||||
}
|
||||
/// Every pointer to [Content] is a [Content].
|
||||
impl<E: Output, C: Content<E>> Content<E> for &C {
|
||||
fn content (&self) -> impl Render<E> { (*self).content() }
|
||||
fn layout (&self, area: E::Area) -> E::Area { (*self).layout(area) }
|
||||
fn render (&self, output: &mut E) { (*self).render(output) }
|
||||
}
|
||||
/// The platonic ideal unit of [Content]: total emptiness at dead center (e=1vg^sqrt(-1))
|
||||
impl<E: Output> Content<E> for () {
|
||||
fn layout (&self, area: E::Area) -> E::Area { area.center().to_area_pos().into() }
|
||||
fn render (&self, _: &mut E) {}
|
||||
}
|
||||
impl<E: Output, T: Content<E>> Content<E> for Option<T> {
|
||||
fn content (&self) -> impl Render<E> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn layout (&self, area: E::Area) -> E::Area {
|
||||
self.as_ref()
|
||||
.map(|content|content.layout(area))
|
||||
.unwrap_or([0.into(), 0.into(), 0.into(), 0.into(),].into())
|
||||
}
|
||||
fn render (&self, output: &mut E) {
|
||||
self.as_ref()
|
||||
.map(|content|content.render(output));
|
||||
}
|
||||
}
|
||||
/// Implement [Content] with composable content for a struct.
|
||||
#[macro_export] macro_rules! content {
|
||||
// Implement for all [Output]s.
|
||||
(|$self:ident:$Struct:ty| $content:expr) => {
|
||||
impl<E: Output> Content<E> for $Struct {
|
||||
fn content (&$self) -> impl Render<E> { Some($content) }
|
||||
}
|
||||
};
|
||||
// Implement for specific [Output].
|
||||
($Output:ty:|
|
||||
$self:ident:
|
||||
$Struct:ident$(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?
|
||||
|$content:expr) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn content (&$self) -> impl Render<$Output> { $content }
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Implement [Content] with custom rendering for a struct.
|
||||
#[macro_export] macro_rules! render {
|
||||
(|$self:ident:$Struct:ident $(<
|
||||
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
|
||||
>)?, $to:ident | $render:expr) => {
|
||||
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
|
||||
for $Struct $(<$($L),* $($T),*>>)? {
|
||||
fn render (&$self, $to: &mut E) { $render }
|
||||
}
|
||||
};
|
||||
($Output:ty:|
|
||||
$self:ident:
|
||||
$Struct:ident $(<$($($L:lifetime)? $($T:ident)? $(:$Trait:path)?),+>)?, $to:ident
|
||||
|$render:expr) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn render (&$self, $to: &mut $Output) { $render }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
use crate::*;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Custom layout and rendering.
|
||||
pub trait Render<E: Output>: Send + Sync {
|
||||
fn layout (&self, area: E::Area) -> E::Area;
|
||||
fn render (&self, output: &mut E);
|
||||
fn boxed <'a> (self) -> RenderBox<'a, E> where Self: Sized + 'a {
|
||||
Box::new(self) as RenderBox<'a, E>
|
||||
}
|
||||
}
|
||||
|
||||
pub type RenderDyn<'a, Output> = dyn Render<Output> + 'a;
|
||||
impl<'a, E: Output> Content<E> for &RenderDyn<'a, E> where Self: Sized {
|
||||
fn content (&self) -> impl Render<E> { self.deref() }
|
||||
fn layout (&self, area: E::Area) -> E::Area { Render::layout(self.deref(), area) }
|
||||
fn render (&self, output: &mut E) { Render::render(self.deref(), output) }
|
||||
}
|
||||
|
||||
pub type RenderBox<'a, E: Output> = Box<RenderDyn<'a, E>>;
|
||||
impl<'a, E: Output> Content<E> for RenderBox<'a, E> {
|
||||
fn content (&self) -> impl Render<E> { self.deref() }
|
||||
//fn boxed <'b> (self) -> RenderBox<'b, E> where Self: Sized + 'b { self }
|
||||
}
|
||||
|
||||
impl<E: Output, C: Content<E>> Render<E> for C {
|
||||
fn layout (&self, area: E::Area) -> E::Area { Content::layout(self, area) }
|
||||
fn render (&self, output: &mut E) { Content::render(self, output) }
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! render {
|
||||
(($self:ident:$Struct:ty) => $content:expr) => {
|
||||
impl <E: Output> Content<E> for $Struct {
|
||||
fn content (&$self) -> impl Render<E> { Some($content) }
|
||||
}
|
||||
};
|
||||
(|$self:ident:$Struct:ident $(<
|
||||
$($L:lifetime),* $($T:ident $(:$Trait:path)?),*
|
||||
>)?, $to:ident | $render:expr) => {
|
||||
impl <$($($L),*)? E: Output, $($T$(:$Trait)?),*> Content<E>
|
||||
for $Struct $(<$($L),* $($T),*>>)? {
|
||||
fn render (&$self, $to: &mut E) { $render }
|
||||
}
|
||||
};
|
||||
($Output:ty:
|
||||
($self:ident:$Struct:ident $(<$(
|
||||
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
|
||||
),+>)?) => $content:expr
|
||||
) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn content (&$self) -> impl Render<$Output> { $content }
|
||||
}
|
||||
};
|
||||
|
||||
($Output:ty:
|
||||
|$self:ident : $Struct:ident $(<$(
|
||||
$($L:lifetime)? $($T:ident)? $(:$Trait:path)?
|
||||
),+>)?, $to:ident| $render:expr
|
||||
) => {
|
||||
impl $(<$($($L)? $($T)? $(:$Trait)?),+>)? Content<$Output>
|
||||
for $Struct $(<$($($L)? $($T)?),+>)? {
|
||||
fn render (&$self, $to: &mut $Output) { $render }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ impl SamplerTui {
|
|||
None
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: SamplerTui) => {
|
||||
content!(TuiOut: |self: SamplerTui| {
|
||||
let keys_width = 5;
|
||||
let keys = move||"";//SamplerKeys(self);
|
||||
let fg = self.color.base.rgb;
|
||||
|
|
@ -54,8 +54,8 @@ struct SamplesTui {
|
|||
note_pt: usize,
|
||||
height: usize,
|
||||
}
|
||||
render!(TuiOut: |self: SamplesTui, render|{
|
||||
let x = render.area.x();
|
||||
render!(TuiOut: |self: SamplesTui, to| {
|
||||
let x = to.area.x();
|
||||
let bg_base = self.color.darkest.rgb;
|
||||
let bg_selected = self.color.darker.rgb;
|
||||
let style_empty = Style::default().fg(self.color.base.rgb);
|
||||
|
|
@ -64,7 +64,7 @@ render!(TuiOut: |self: SamplesTui, render|{
|
|||
let note = self.note_hi - y as usize;
|
||||
let bg = if note == self.note_pt { bg_selected } else { bg_base };
|
||||
let style = Some(style_empty.bg(bg));
|
||||
render.blit(&" (no sample) ", x, render.area.y() + y as u16, style);
|
||||
to.blit(&" (no sample) ", x, to.area.y() + y as u16, style);
|
||||
}
|
||||
});
|
||||
impl NoteRange for SamplerTui {
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ has_size!(<TuiOut>|self: App|&self.size);
|
|||
has_clock!(|self: App|&self.clock);
|
||||
has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips);
|
||||
//has_editor!(|self: App|self.editor.as_ref().expect("no editor"));
|
||||
impl HasJack for App {
|
||||
fn jack (&self) -> &Arc<RwLock<JackConnection>> { &self.jack }
|
||||
}
|
||||
has_jack!(|self: App|&self.jack);
|
||||
impl HasSampler for App {
|
||||
fn sampler (&self) -> &Option<Sampler> { &self.sampler }
|
||||
fn sampler_mut (&mut self) -> &mut Option<Sampler> { &mut self.sampler }
|
||||
|
|
@ -106,10 +104,10 @@ edn_provide!('a: Box<dyn Render<TuiOut> + 'a>: |self: App|{
|
|||
":scenes" => self.row(self.w(), self.size.h().saturating_sub(9) as u16,
|
||||
self.scene_header(), self.scene_cells(self.is_editing())).boxed(),
|
||||
});
|
||||
render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||
content!(TuiOut: |self: App| self.size.of(EdnView::from_source(self, self.edn.as_ref())));
|
||||
handle!(TuiIn: |self: App, input| Ok(None));
|
||||
#[derive(Debug, Default)] struct Meter<'a>(pub &'a str, pub f32);
|
||||
render!(TuiOut: (self: Meter<'a>) => col!(
|
||||
content!(TuiOut: |self: Meter<'a>| col!(
|
||||
Field(TuiTheme::g(128).into(), self.0, format!("{:>+9.3}", self.1)),
|
||||
Fixed::xy(if self.1 >= 0.0 { 13 }
|
||||
else if self.1 >= -1.0 { 12 }
|
||||
|
|
@ -128,7 +126,7 @@ render!(TuiOut: (self: Meter<'a>) => col!(
|
|||
else if self.1 >= -3.0 { Color::Yellow }
|
||||
else { Color::Green }, ()))));
|
||||
#[derive(Debug, Default)] struct Meters<'a>(pub &'a[f32]);
|
||||
render!(TuiOut: (self: Meters<'a>) => col!(
|
||||
content!(TuiOut: |self: Meters<'a>| col!(
|
||||
format!("L/{:>+9.3}", self.0[0]),
|
||||
format!("R/{:>+9.3}", self.0[1])
|
||||
));
|
||||
|
|
@ -182,7 +180,7 @@ pub trait HasSelection {
|
|||
fn selected (&self) -> &Selection;
|
||||
fn selected_mut (&mut self) -> &mut Selection;
|
||||
}
|
||||
#[derive(Debug, Default)] struct Track {
|
||||
#[derive(Debug, Default)] pub struct Track {
|
||||
/// Name of track
|
||||
name: Arc<str>,
|
||||
/// Preferred width of track column
|
||||
|
|
@ -332,7 +330,7 @@ pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync
|
|||
trait Device: Send + Sync + std::fmt::Debug {}
|
||||
impl Device for Sampler {}
|
||||
impl Device for Plugin {}
|
||||
#[derive(Debug, Default)] struct Scene {
|
||||
#[derive(Debug, Default)] pub struct Scene {
|
||||
/// Name of scene
|
||||
name: Arc<str>,
|
||||
/// Clips in scene, one per track
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ use crate::*;
|
|||
use KeyCode::*;
|
||||
use ClockCommand::{Play, Pause};
|
||||
/// Transport clock app.
|
||||
pub struct ClockTui {
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub clock: Clock,
|
||||
}
|
||||
pub struct ClockTui { pub jack: Arc<RwLock<JackConnection>>, pub clock: Clock, }
|
||||
handle!(TuiIn: |self: ClockTui, input|ClockCommand::execute_with_state(self, input.event()));
|
||||
keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
||||
key(Char(' ')) =>
|
||||
|
|
@ -15,29 +12,20 @@ keymap!(TRANSPORT_KEYS = |state: ClockTui, input: Event| ClockCommand {
|
|||
});
|
||||
has_clock!(|self: ClockTui|&self.clock);
|
||||
audio!(|self: ClockTui, client, scope|ClockAudio(self).process(client, scope));
|
||||
render!(TuiOut: (self: ClockTui) => ClockView {
|
||||
compact: false,
|
||||
clock: &self.clock
|
||||
});
|
||||
|
||||
content!(TuiOut:|self: ClockTui|ClockView { compact: false, clock: &self.clock });
|
||||
pub struct ClockView<'a> { pub compact: bool, pub clock: &'a Clock }
|
||||
impl<'a> ClockView<'a> {
|
||||
pub fn new (compact: bool, clock: &'a Clock) -> Self {
|
||||
Self { compact, clock }
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: ClockView<'a>) => Outer(
|
||||
Style::default().fg(TuiTheme::g(255))
|
||||
).enclose(row!(
|
||||
content!(TuiOut: |self: ClockView<'a>| Outer(Style::default().fg(TuiTheme::g(255))).enclose(row!(
|
||||
OutputStats::new(self.compact, self.clock),
|
||||
" ",
|
||||
PlayPause { compact: false, playing: self.clock.is_rolling() },
|
||||
" ",
|
||||
BeatStats::new(self.compact, self.clock),
|
||||
)));
|
||||
|
||||
impl<'a> ClockView<'a> {
|
||||
pub fn new (compact: bool, clock: &'a Clock) -> Self { Self { compact, clock } }
|
||||
}
|
||||
pub struct PlayPause { pub compact: bool, pub playing: bool }
|
||||
render!(TuiOut: (self: PlayPause) => Tui::bg(
|
||||
content!(TuiOut: |self: PlayPause| Tui::bg(
|
||||
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
|
||||
Either::new(self.compact,
|
||||
Thunk::new(||Fixed::x(9, Either::new(self.playing,
|
||||
|
|
@ -46,8 +34,14 @@ render!(TuiOut: (self: PlayPause) => Tui::bg(
|
|||
Thunk::new(||Fixed::x(5, Either::new(self.playing,
|
||||
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
||||
|
||||
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
||||
content!(TuiOut: |self: BeatStats| Either::new(self.compact,
|
||||
row!(FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
||||
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
||||
FieldV(TuiTheme::g(128).into(), "Time", &self.time),),
|
||||
col!(Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
||||
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
||||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)))));
|
||||
impl BeatStats {
|
||||
fn new (compact: bool, clock: &Clock) -> Self {
|
||||
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
||||
|
|
@ -63,46 +57,27 @@ impl BeatStats {
|
|||
Self { compact, bpm, beat, time }
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: BeatStats) => Either::new(self.compact,
|
||||
row!(
|
||||
FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
|
||||
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
|
||||
FieldV(TuiTheme::g(128).into(), "Time", &self.time),
|
||||
),
|
||||
col!(
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), &self.bpm), " BPM"),
|
||||
Bsp::e("Beat ", Tui::fg(TuiTheme::g(255), &self.beat)),
|
||||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)),
|
||||
)));
|
||||
|
||||
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
||||
content!(TuiOut: |self: OutputStats| Either::new(self.compact,
|
||||
row!(FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
||||
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
||||
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency)),
|
||||
col!(Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"))));
|
||||
impl OutputStats {
|
||||
fn new (compact: bool, clock: &Clock) -> Self {
|
||||
let rate = clock.timebase.sr.get();
|
||||
let chunk = clock.chunk.load(Relaxed);
|
||||
let sr = if compact {format!("{:.1}kHz", rate / 1000.)} else {format!("{:.0}Hz", rate)};
|
||||
Self {
|
||||
compact,
|
||||
sample_rate: if compact {
|
||||
format!("{:.1}kHz", rate / 1000.)
|
||||
} else {
|
||||
format!("{:.0}Hz", rate)
|
||||
}.into(),
|
||||
sample_rate: sr.into(),
|
||||
buffer_size: format!("{chunk}").into(),
|
||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
render!(TuiOut: (self: OutputStats) => Either::new(self.compact,
|
||||
row!(
|
||||
FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
|
||||
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),
|
||||
FieldV(TuiTheme::g(128).into(), "Lat", &self.latency),
|
||||
),
|
||||
col!(
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.sample_rate)), " sample rate"),
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{}", self.buffer_size)), " sample buffer"),
|
||||
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"),
|
||||
)));
|
||||
|
||||
// TODO:
|
||||
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use crate::*;
|
||||
|
||||
pub struct Bordered<S: BorderStyle, W: Content<TuiOut>>(pub S, pub W);
|
||||
|
||||
render!(TuiOut: (self: Bordered<S: BorderStyle, W: Content<TuiOut>>) => {
|
||||
Fill::xy(lay!(Border(self.0), Padding::xy(1, 1, &self.1)))
|
||||
});
|
||||
content!(TuiOut: |self: Bordered<S: BorderStyle, W: Content<TuiOut>>|Fill::xy(
|
||||
lay!(Border(self.0), Padding::xy(1, 1, &self.1))
|
||||
));
|
||||
|
||||
pub struct Border<S: BorderStyle>(pub S);
|
||||
|
||||
render!(TuiOut: |self: Border<S: BorderStyle>, to| {
|
||||
let area = to.area();
|
||||
if area.w() > 0 && area.y() > 0 {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub enum FileBrowserCommand {
|
|||
Chdir(PathBuf),
|
||||
Filter(Arc<str>),
|
||||
}
|
||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||
content!(TuiOut: |self: FileBrowser| /*Stack::down(|add|{
|
||||
let mut i = 0;
|
||||
for (_, name) in self.dirs.iter() {
|
||||
if i >= self.scroll {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue