start implementing edn loader; remove PhantomData from some tek_layout constructs

This commit is contained in:
🪞👃🪞 2025-01-03 22:50:58 +01:00
parent f359768ba2
commit 2b07e7963e
20 changed files with 239 additions and 222 deletions

1
Cargo.lock generated
View file

@ -1389,6 +1389,7 @@ name = "tek_engine"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"better-panic", "better-panic",
"clojure-reader",
"crossterm", "crossterm",
"ratatui", "ratatui",
] ]

View file

@ -7,3 +7,4 @@ version = "0.2.0"
crossterm = "0.28.1" crossterm = "0.28.1"
ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] } ratatui = { version = "0.29.0", features = [ "unstable-widget-ref", "underline-color" ] }
better-panic = "0.3.0" better-panic = "0.3.0"
clojure-reader = "0.3.0"

36
engine/src/edn.rs Normal file
View file

@ -0,0 +1,36 @@
use crate::*;
use std::sync::{Arc, RwLock};
use std::collections::BTreeMap;
pub use clojure_reader::edn::Edn;
/// 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),* })
}
};
}
pub trait FromEdn<C>: Sized {
const ID: &'static str;
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
}
/// Implements the [FromEdn] trait.
#[macro_export] macro_rules! from_edn {
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
impl FromEdn<$Context> for $T {
const ID: &'static str = $id;
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
$body
}
}
}
}

View file

@ -4,6 +4,7 @@ mod input; pub use self::input::*;
mod output; pub use self::output::*; mod output; pub use self::output::*;
pub mod tui; pub mod tui;
pub mod edn;
pub use std::error::Error; pub use std::error::Error;

View file

@ -3,23 +3,23 @@ use crate::*;
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default)]
pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W } pub enum Alignment { #[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W }
pub struct Align<E: Engine, T: Content<E>>(Alignment, T, PhantomData<E>); pub struct Align<T>(Alignment, T);
impl<E: Engine, T: Content<E>> Align<E, T> { impl<T> Align<T> {
pub fn c (a: T) -> Self { Self(Alignment::Center, a, Default::default()) } pub fn c (a: T) -> Self { Self(Alignment::Center, a) }
pub fn x (a: T) -> Self { Self(Alignment::X, a, Default::default()) } pub fn x (a: T) -> Self { Self(Alignment::X, a) }
pub fn y (a: T) -> Self { Self(Alignment::Y, a, Default::default()) } pub fn y (a: T) -> Self { Self(Alignment::Y, a) }
pub fn n (a: T) -> Self { Self(Alignment::N, a, Default::default()) } pub fn n (a: T) -> Self { Self(Alignment::N, a) }
pub fn s (a: T) -> Self { Self(Alignment::S, a, Default::default()) } pub fn s (a: T) -> Self { Self(Alignment::S, a) }
pub fn e (a: T) -> Self { Self(Alignment::E, a, Default::default()) } pub fn e (a: T) -> Self { Self(Alignment::E, a) }
pub fn w (a: T) -> Self { Self(Alignment::W, a, Default::default()) } pub fn w (a: T) -> Self { Self(Alignment::W, a) }
pub fn nw (a: T) -> Self { Self(Alignment::NW, a, Default::default()) } pub fn nw (a: T) -> Self { Self(Alignment::NW, a) }
pub fn sw (a: T) -> Self { Self(Alignment::SW, a, Default::default()) } pub fn sw (a: T) -> Self { Self(Alignment::SW, a) }
pub fn ne (a: T) -> Self { Self(Alignment::NE, a, Default::default()) } pub fn ne (a: T) -> Self { Self(Alignment::NE, a) }
pub fn se (a: T) -> Self { Self(Alignment::SE, a, Default::default()) } pub fn se (a: T) -> Self { Self(Alignment::SE, a) }
} }
impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> { impl<E: Engine, T: Content<E>> Content<E> for Align<T> {
fn content (&self) -> impl Content<E> { fn content (&self) -> impl Content<E> {
&self.1 &self.1
} }
@ -45,52 +45,8 @@ impl<E: Engine, T: Content<E>> Content<E> for Align<E, T> {
Y => [it.x(), centered.y()], Y => [it.x(), centered.y()],
}; };
[x, y, centered.w(), centered.h()].into() [x, y, centered.w(), centered.h()].into()
//let [cfx, cfy, ..] = on.center();
//let [cmx, cmy, ..] = it.center();
////let center = |cf, cm, m|if cf >= cm { m + (cf - cm) } else { m.minus(cm - cf) };
//let center_x = center(cfx, cmx, it.x());
//let center_y = center(cfy, cmy, it.y());
//let east_x = on.x() + on.w().minus(it.w());
//let south_y = on.y() + on.h().minus(it.h());
//let [x, y] = match self.0 {
//Alignment::X => [center_x, it.y(), ],
//Alignment::Y => [it.x(), center_y,],
//Alignment::NW => [on.x(), on.y(), ],
//Alignment::N => [center_x, on.y(), ],
//Alignment::NE => [east_x, on.y(), ],
//Alignment::E => [east_x, center_y,],
//Alignment::SE => [east_x, south_y, ],
//Alignment::S => [center_x, south_y, ],
//Alignment::SW => [on.x(), south_y, ],
//Alignment::W => [on.x(), center_y,],
//};
//[x, y, it.w(), it.h()].into()
} }
fn render (&self, render: &mut E::Output) { fn render (&self, render: &mut E::Output) {
render.place(self.layout(render.area()), &self.content()) render.place(self.layout(render.area()), &self.content())
} }
} }
//fn align<E: Engine, T: Content<E>, N: Coordinate, R: Area<N> + From<[N;4]>> (align: &Align<E, T>, outer: R, content: R) -> Option<R> {
//if outer.w() < content.w() || outer.h() < content.h() {
//None
//} else {
//let [ox, oy, ow, oh] = outer.xywh();
//let [ix, iy, iw, ih] = content.xywh();
//Some(match align {
//Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(),
//Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::NW(_) => [ox, oy, iw, ih,].into(),
//Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(),
//Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(),
//Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(),
//Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(),
//Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(),
//Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(),
//_ => unreachable!()
//})
//}
//}

View file

@ -18,9 +18,9 @@ impl Direction {
} }
} }
pub struct Bsp<E: Engine, X: Content<E>, Y: Content<E>>(Direction, X, Y, PhantomData<E>); pub struct Bsp<X, Y>(Direction, X, Y);
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Bsp<E, A, B> { impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Bsp<A, B> {
fn layout (&self, outer: E::Area) -> E::Area { fn layout (&self, outer: E::Area) -> E::Area {
let [_, _, c] = self.areas(outer); let [_, _, c] = self.areas(outer);
c c
@ -35,26 +35,31 @@ impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Bsp<E, A, B> {
} }
} }
impl<E: Engine, A: Content<E>, B: Content<E>> Bsp<E, A, B> { impl<A, B> Bsp<A, B> {
pub fn n (a: A, b: B) -> Self { Self(North, a, b, Default::default()) } pub fn n (a: A, b: B) -> Self { Self(North, a, b) }
pub fn s (a: A, b: B) -> Self { Self(South, a, b, Default::default()) } pub fn s (a: A, b: B) -> Self { Self(South, a, b) }
pub fn e (a: A, b: B) -> Self { Self(East, a, b, Default::default()) } pub fn e (a: A, b: B) -> Self { Self(East, a, b) }
pub fn w (a: A, b: B) -> Self { Self(West, a, b, Default::default()) } pub fn w (a: A, b: B) -> Self { Self(West, a, b) }
pub fn a (a: A, b: B) -> Self { Self(Above, a, b, Default::default()) } pub fn a (a: A, b: B) -> Self { Self(Above, a, b) }
pub fn b (a: A, b: B) -> Self { Self(Below, a, b, Default::default()) } pub fn b (a: A, b: B) -> Self { Self(Below, a, b) }
pub fn contents (&self) -> (&A, &B) { (&self.1, &self.2) } }
pub fn areas (&self, outer: E::Area) -> [E::Area;3] {
pub trait BspAreas<E: Engine, A: Content<E>, B: Content<E>> {
fn direction (&self) -> Direction;
fn contents (&self) -> (&A, &B);
fn areas (&self, outer: E::Area) -> [E::Area;3] {
let direction = self.direction();
let [x, y, w, h] = outer.xywh(); let [x, y, w, h] = outer.xywh();
let (a, b) = self.contents(); let (a, b) = self.contents();
let [ax, ay, aw, ah] = a.layout(outer).xywh(); let [ax, ay, aw, ah] = a.layout(outer).xywh();
let [bx, by, bw, bh] = b.layout(match self.0 { let [bx, by, bw, bh] = b.layout(match direction {
Above | Below => outer, Above | Below => outer,
South => [x, y + ah, w, h.minus(ah)].into(), South => [x, y + ah, w, h.minus(ah)].into(),
North => [x, y, w, h.minus(ah)].into(), North => [x, y, w, h.minus(ah)].into(),
East => [x + aw, y, w.minus(aw), h].into(), East => [x + aw, y, w.minus(aw), h].into(),
West => [x, y, w.minus(aw), h].into(), West => [x, y, w.minus(aw), h].into(),
}).xywh(); }).xywh();
match self.0 { match direction {
Above | Below => { Above | Below => {
let x = ax.min(bx); let x = ax.min(bx);
let w = (ax+aw).max(bx+bw).minus(x); let w = (ax+aw).max(bx+bw).minus(x);
@ -90,6 +95,11 @@ impl<E: Engine, A: Content<E>, B: Content<E>> Bsp<E, A, B> {
} }
} }
impl<E: Engine, A: Content<E>, B: Content<E>> BspAreas<E, A, B> for Bsp<A, B> {
fn direction (&self) -> Direction { self. 0 }
fn contents (&self) -> (&A, &B) { (&self.1, &self.2) }
}
/// Renders multiple things on top of each other, /// Renders multiple things on top of each other,
#[macro_export] macro_rules! lay { #[macro_export] macro_rules! lay {
($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }} ($($expr:expr),* $(,)?) => {{ let bsp = (); $(let bsp = Bsp::b(bsp, $expr);)*; bsp }}

47
layout/src/layout_edn.rs Normal file
View file

@ -0,0 +1,47 @@
use crate::*;
use ::tek_engine::edn::*;
macro_rules! edn_module {
($name:literal = $Host:ident<$E:ident: $Engine:path> { $(
(defn $fn:ident
$(<$($G:ident: $Generic:ty),+>)?
$Struct:ident($($arg:ident : $Arg:ty),*))
)* }) => {
pub trait $Host<$E: Engine> {
pub fn read_one <'e> (edn: &[Edn<'e>]) -> impl Content<$E> {
if let Some(Edn::Symbol(name)) = edn.get(0) {
match name {
$(
stringify!($fn) =>
),*
}
} else {
panic!("invalid edn")
}
}
$(
)*
}
};
}
//edn_module! {
//(host LayoutEdn<E>)
//(name "layout")
//(defn when <A: Content<E>>
//When(cond: bool, item: A))
//(defn either <A: Content<E>, B: Content<E>>
//Either(cond: bool, a: A, b: B))
//(defn map <
//A: Content<E>,
//B: Content<E>,
//I: Iterator<Item = A> + Send + Sync,
//F: Fn() -> I + Send + Sync,
//G: Fn(A, usize)->B + Send + Sync
//>
//Map(get_iterator: I, callback: G))
//}

View file

@ -2,6 +2,7 @@
mod align; pub use self::align::*; mod align; pub use self::align::*;
mod direction; pub use self::direction::*; mod direction; pub use self::direction::*;
mod layout_edn; pub use self::edn::*;
mod measure; pub use self::measure::*; mod measure; pub use self::measure::*;
mod ops; pub use self::ops::*; mod ops; pub use self::ops::*;
mod transform_xy; pub use self::transform_xy::*; mod transform_xy; pub use self::transform_xy::*;

View file

@ -65,7 +65,7 @@ impl<E: Engine> Measure<E> {
y: Arc::new(0.into()), y: Arc::new(0.into()),
} }
} }
pub fn of <T: Content<E>> (&self, item: T) -> Bsp<E, Fill<E, &Self>, T> { pub fn of <T: Content<E>> (&self, item: T) -> Bsp<Fill<E, &Self>, T> {
Bsp::b(Fill::xy(&self), item) Bsp::b(Fill::xy(&self), item)
} }
} }

View file

@ -1,55 +1,11 @@
use crate::*; use crate::*;
impl<E: Engine> Layout<E> for E {} /// Show an item only when a condition is true.
pub struct When<A>(pub bool, pub A);
pub trait Layout<E: Engine> { impl<E: Engine, A: Content<E>> Content<E> for When<A> {
/// Content `item` when `cond` is true.
fn when <A> (cond: bool, item: A) -> When<E, A> where
A: Content<E>
{
When(cond, item, Default::default())
}
/// Content `item` if `cond` is true, otherwise render `other`.
fn either <A, B> (cond: bool, a: A, b: B) -> Either<E, A, B> where
A: Content<E>,
B: Content<E>,
{
Either(cond, a, b, Default::default())
}
/// Maps an [Option<T>] through a callback `F`
fn opt <A, F, R> (option: Option<A>, cb: F) -> Opt<E, A, F, R> where
F: Fn(A) -> R,
R: Content<E>
{
Opt(option, cb, Default::default())
}
fn map <T, I, J, R, F>(iterator: J, callback: F) -> Map<E, T, I, J, R, F> where
E: Engine,
I: Iterator<Item = T> + Send + Sync,
J: Fn() -> I + Send + Sync,
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync
{
Map(Default::default(), iterator, callback)
}
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine,
//I: Iterator<Item = T> + Send + Sync,
//R: Content<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
}
pub struct Opt<E: Engine, A, F: Fn(A)->R, R: Content<E>>(Option<A>, F, PhantomData<E>);
/// Contents `self.1` when `self.0` is true.
pub struct When<E: Engine, A>(bool, A, PhantomData<E>);
impl<E: Engine, A: Content<E>> Content<E> for When<E, A> {
fn layout (&self, to: E::Area) -> E::Area { fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, item, ..) = self; let Self(cond, item) = self;
let mut area = E::Area::zero(); let mut area = E::Area::zero();
if *cond { if *cond {
let item_area = item.layout(to); let item_area = item.layout(to);
@ -61,45 +17,44 @@ impl<E: Engine, A: Content<E>> Content<E> for When<E, A> {
area.into() area.into()
} }
fn render (&self, to: &mut E::Output) { fn render (&self, to: &mut E::Output) {
let Self(cond, item, ..) = self; let Self(cond, item) = self;
if *cond { item.render(to) } if *cond { item.render(to) }
} }
} }
/// Contents `self.1` when `self.0` is true, otherwise renders `self.2` /// Show one item if a condition is true and another if the condition is false
pub struct Either<E: Engine, A, B>(bool, A, B, PhantomData<E>); pub struct Either<A, B>(pub bool, pub A, pub B);
impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<E, A, B> { impl<E: Engine, A: Content<E>, B: Content<E>> Content<E> for Either<A, B> {
fn layout (&self, to: E::Area) -> E::Area { fn layout (&self, to: E::Area) -> E::Area {
let Self(cond, a, b, ..) = self; let Self(cond, a, b) = self;
if *cond { a.layout(to) } else { b.layout(to) } if *cond { a.layout(to) } else { b.layout(to) }
} }
fn render (&self, to: &mut E::Output) { fn render (&self, to: &mut E::Output) {
let Self(cond, a, b, ..) = self; let Self(cond, a, b) = self;
if *cond { a.render(to) } else { b.render(to) } if *cond { a.render(to) } else { b.render(to) }
} }
} }
pub struct Map<E, T, I, J, R, F>(PhantomData<E>, J, F) where pub struct Map<A, B, I, F, G>(pub F, pub G) where
E: Engine, I: Iterator<Item = A> + Send + Sync,
I: Iterator<Item = T> + Send + Sync, F: Fn() -> I + Send + Sync,
J: Fn()->I + Send + Sync, G: Fn(A, usize)->B + Send + Sync;
R: Content<E>,
F: Fn(T, usize)->R + Send + Sync;
impl<E, T, I, J, R, F> Content<E> for Map<E, T, I, J, R, F> where impl<E, A, B, I, F, G> Content<E> for Map<A, B, I, F, G> where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, B: Content<E>,
J: Fn()->I + Send + Sync, I: Iterator<Item = A> + Send + Sync,
R: Content<E>, F: Fn() -> I + Send + Sync,
F: Fn(T, usize)->R + Send + Sync G: Fn(A, usize)->B + Send + Sync
{ {
fn layout (&self, area: E::Area) -> E::Area { fn layout (&self, area: E::Area) -> E::Area {
let Self(get_iterator, callback) = self;
let mut index = 0; let mut index = 0;
let [mut min_x, mut min_y] = area.center(); let [mut min_x, mut min_y] = area.center();
let [mut max_x, mut max_y] = area.center(); let [mut max_x, mut max_y] = area.center();
for item in (self.1)() { for item in get_iterator() {
let area = (self.2)(item, index).layout(area).xywh(); let area = callback(item, index).layout(area).xywh();
let [x,y,w,h] = area.xywh(); let [x,y,w,h] = area.xywh();
min_x = min_x.min(x.into()); min_x = min_x.min(x.into());
min_y = min_y.min(y.into()); min_y = min_y.min(y.into());
@ -113,10 +68,11 @@ impl<E, T, I, J, R, F> Content<E> for Map<E, T, I, J, R, F> where
area.center_xy([w.into(), h.into()].into()).into() area.center_xy([w.into(), h.into()].into()).into()
} }
fn render (&self, to: &mut E::Output) { fn render (&self, to: &mut E::Output) {
let Self(get_iterator, callback) = self;
let mut index = 0; let mut index = 0;
//let area = self.layout(to.area()); //let area = self.layout(to.area());
for item in (self.1)() { for item in get_iterator() {
let item = (self.2)(item, index); let item = callback(item, index);
//to.place(area.into(), &item); //to.place(area.into(), &item);
to.place(to.area().into(), &item); to.place(to.area().into(), &item);
index += 1; index += 1;
@ -125,6 +81,15 @@ impl<E, T, I, J, R, F> Content<E> for Map<E, T, I, J, R, F> where
} }
/* /*
//pub fn reduce <E, T, I, R, F>(iterator: I, callback: F) -> Reduce<E, T, I, R, F> where
//E: Engine,
//I: Iterator<Item = T> + Send + Sync,
//R: Content<E>,
//F: Fn(R, T, usize) -> R + Send + Sync
//{
//Reduce(Default::default(), iterator, callback)
//}
pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where pub struct Reduce<E, T, I, R, F>(PhantomData<(E, R)>, I, F) where
E: Engine, E: Engine,
I: Iterator<Item = T> + Send + Sync, I: Iterator<Item = T> + Send + Sync,
@ -141,3 +106,42 @@ impl<E, T, I, R, F> Content<E> for Reduce<E, T, I, R, F> where
} }
} }
*/ */
//macro_rules! define_ops {
//($Trait:ident<$E:ident:$Engine:path> { $(
//$(#[$attr:meta $($attr_args:tt)*])*
//(
//$fn:ident
//$(<$($G:ident$(:$Gen:path)?, )+>)?
//$Op:ident
//($($arg:ident:$Arg:ty),*)
//)
//)* }) => {
//impl<$E: $Engine> $Trait<E> for E {}
//pub trait $Trait<$E: $Engine> {
//$(
//$(#[$attr $($attr_args)*])*
//fn $fn $(<$($G),+>)?
//($($arg:$Arg),*)-> $Op<$($(, $G)+)?>
//$(where $($G: $($Gen + Send + Sync)?),+)?
//{ $Op($($arg),*) }
//)*
//}
//}
//}
//define_ops! {
//Layout<E: Engine> {
//(when <A: Content<E>,>
//When(cond: bool, item: A))
///// When `cond` is `true`, render `a`, otherwise render `b`.
//(either <A: Content<E>, B: Content<E>,>
//Either(cond: bool, a: A, b: B))
///// If `opt` is `Some(T)` renders `cb(t)`, otherwise nothing.
//(opt <A, F: Fn(A) -> B, B: Content<E>,>
//Opt(option: Option<A>, cb: F))
///// Maps items of iterator through callback.
//(map <A, B: Content<E>, I: Iterator<Item = A>, F: Fn() -> I, G: Fn(A, usize)->B,>
//Map(get_iterator: F, callback: G))
//}
//}

View file

@ -20,7 +20,7 @@ impl<'a> ArrangerVClips<'a> {
impl<'a> Content<Tui> for ArrangerVClips<'a> { impl<'a> Content<Tui> for ArrangerVClips<'a> {
fn content (&self) -> impl Content<Tui> { fn content (&self) -> impl Content<Tui> {
let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0)); let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0));
let col = Tui::map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses)); let col = Map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses));
Fill::xy(col) Fill::xy(col)
} }
} }
@ -37,7 +37,7 @@ impl<'a> ArrangerVClips<'a> {
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone())) Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone()))
); );
let clips = Tui::map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _| let clips = Map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height)) Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height))
); );
Fixed::y(height, row!(icon, name, clips)) Fixed::y(height, row!(icon, name, clips))

View file

@ -20,7 +20,7 @@ render!(Tui: (self: ArrangerVHead<'a>) => {
row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field)) row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field))
} }
Some(Push::x(self.scenes_w, Some(Push::x(self.scenes_w,
Tui::map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| { Map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color(); let color = track.color();
let input = Self::format_input(track); let input = Self::format_input(track);

View file

@ -74,11 +74,11 @@ render!(Tui: (self: TransportView<'a>) => Outer(
pub struct PlayPause { pub compact: bool, pub playing: bool } pub struct PlayPause { pub compact: bool, pub playing: bool }
render!(Tui: (self: PlayPause) => Tui::bg( render!(Tui: (self: PlayPause) => Tui::bg(
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Tui::either(self.compact, Either(self.compact,
Thunk::new(||Fixed::x(9, Tui::either(self.playing, Thunk::new(||Fixed::x(9, Either(self.playing,
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "), Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))), Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
Thunk::new(||Fixed::x(5, Tui::either(self.playing, Thunk::new(||Fixed::x(5, Either(self.playing,
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))))))); Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
@ -95,7 +95,7 @@ impl BeatStats {
Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time } Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time }
} }
} }
render!(Tui: (self: BeatStats) => Tui::either(self.compact, render!(Tui: (self: BeatStats) => Either(self.compact,
row!( row!(
FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm), FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat), FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
@ -124,7 +124,7 @@ impl OutputStats {
} }
} }
} }
render!(Tui: (self: OutputStats) => Tui::either(self.compact, render!(Tui: (self: OutputStats) => Either(self.compact,
row!( row!(
FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate), FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size), FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),

View file

@ -1,37 +1,4 @@
use crate::*; use crate::*;
use std::sync::{Arc, RwLock};
use std::collections::BTreeMap;
pub use clojure_reader::edn::Edn;
//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),* })
}
};
}
pub trait FromEdn<C>: Sized {
const ID: &'static str;
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
}
/// Implements the [FromEdn] trait.
#[macro_export] macro_rules! from_edn {
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
impl FromEdn<$Context> for $T {
const ID: &'static str = $id;
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
$body
}
}
}
}
from_edn!("sampler" => |jack: &Arc<RwLock<JackConnection>>, args| -> crate::Sampler { from_edn!("sampler" => |jack: &Arc<RwLock<JackConnection>>, args| -> crate::Sampler {
let mut name = String::new(); let mut name = String::new();

View file

@ -12,6 +12,7 @@ pub(crate) use ::tek_layout::{
Output, Content, Thunk, render, Output, Content, Thunk, render,
Input, Handle, handle, Input, Handle, handle,
kexp, kpat, kexp, kpat,
edn::*,
tui::{ tui::{
Tui, Tui,
TuiIn, key, ctrl, shift, alt, TuiIn, key, ctrl, shift, alt,

View file

@ -15,12 +15,13 @@ pub trait HasPlayPhrase: HasClock {
None None
} }
} }
fn pulses_since_start_looped (&self) -> Option<f64> { fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() { if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase
let times = (elapsed as usize / length) as f64;
let elapsed = (elapsed as usize % length) as f64; let elapsed = (elapsed as usize % length) as f64;
Some(elapsed) Some((times, elapsed))
} else { } else {
None None
} }

View file

@ -25,7 +25,9 @@ impl ClipSelected {
name, name,
color, color,
time: state.pulses_since_start_looped() time: state.pulses_since_start_looped()
.map(|elapsed|format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))) .map(|(times, time)|format!("{:>3}x {:>}",
times+1.0,
state.clock().timebase.format_beats_1(time)))
.unwrap_or_else(||String::from(" ")) .unwrap_or_else(||String::from(" "))
} }
} }
@ -44,7 +46,7 @@ impl ClipSelected {
let current = state.clock().playhead.pulse.get(); let current = state.clock().playhead.pulse.get();
if target > current { if target > current {
let remaining = target - current; let remaining = target - current;
format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) format!("-{:>}", state.clock().timebase.format_beats_1(remaining))
} else { } else {
String::new() String::new()
} }

View file

@ -7,7 +7,7 @@ render!(Tui: (self: PoolView<'a>) => {
let color = self.1.phrase().read().unwrap().color; let color = self.1.phrase().read().unwrap().color;
Outer( Outer(
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb) Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
).enclose(Tui::map(||model.phrases().iter(), |clip, i|{ ).enclose(Map(||model.phrases().iter(), |clip, i|{
let item_height = 1; let item_height = 1;
let item_offset = i as u16 * item_height; let item_offset = i as u16 * item_height;
let selected = i == model.phrase_index(); let selected = i == model.phrase_index();
@ -20,8 +20,8 @@ render!(Tui: (self: PoolView<'a>) => {
offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!( offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!(
Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))), Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))),
Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))), Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))),
Align::w(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))), Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
Align::e(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))), Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
))) )))
})) }))
}); });

View file

@ -17,7 +17,7 @@ render!(Tui: (self: SampleList<'a>) => {
let note_lo = editor.note_lo().load(Relaxed); let note_lo = editor.note_lo().load(Relaxed);
let note_pt = editor.note_point(); let note_pt = editor.note_point();
let note_hi = editor.note_hi(); let note_hi = editor.note_hi();
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Tui::map(move||(note_lo..=note_hi).rev(), move|note, i| { Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map(move||(note_lo..=note_hi).rev(), move|note, i| {
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));

View file

@ -47,38 +47,27 @@ from_jack!(|jack|SequencerTui {
clock, clock,
} }
}); });
render!(Tui: (self: SequencerTui) => { render!(Tui: (self: SequencerTui) => self.size.of(
let w = Bsp::s(self.toolbar_view(),
self.size.w(); Bsp::n(self.status_view(),
let phrase_w = Bsp::w(self.pool_view(), Fill::xy(&self.editor))))));
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; impl SequencerTui {
let color = self.player.play_phrase().as_ref().map(|(_,p)| fn toolbar_view (&self) -> impl Content<Tui> + use<'_> {
p.as_ref().map(|p|p.read().unwrap().color) self.transport.then(||TransportView::new(true, &self.clock))
).flatten().clone(); }
let toolbar = Tui::when(self.transport, fn status_view (&self) -> impl Content<Tui> + use<'_> {
TransportView::new(true, &self.clock)); let edit_clip = MidiEditClip(&self.editor);
let selectors = Tui::when(self.selectors, let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player))); row!(selectors, edit_clip, MidiEditStatus(&self.editor))
let pool_w = }
if self.pool.visible { phrase_w } else { 0 }; fn pool_view (&self) -> impl Content<Tui> + use<'_> {
let pool = let w = self.size.w();
Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool)))); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let edit_clip = let pool_w = if self.pool.visible { phrase_w } else { 0 };
MidiEditClip(&self.editor); let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
self.size.of(Bsp::s( Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
toolbar, }
Bsp::s( }
lay!(Align::w(edit_clip), Align::e(selectors)),
Bsp::n(
Align::x(Fixed::y(1, MidiEditStatus(&self.editor))),
Bsp::w(
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))),
Fill::xy(&self.editor),
),
)
)
))
});
audio!(|self:SequencerTui, client, scope|{ audio!(|self:SequencerTui, client, scope|{
// Start profiling cycle // Start profiling cycle
let t0 = self.perf.get_t0(); let t0 = self.perf.get_t0();