mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
more shuffling; testing dispatch
This commit is contained in:
parent
56df5c5b78
commit
02dc7bc9f8
12 changed files with 1339 additions and 1359 deletions
|
|
@ -1600,3 +1600,17 @@
|
|||
//fn _clip_index_next (&self) -> usize { 0 }
|
||||
//fn _color_random (&self) -> ItemColor { ItemColor::random() }
|
||||
//}
|
||||
|
||||
//pub fn load_axis_bind (name: &str, body: impl Language) -> Usually<Self> {
|
||||
//let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new();
|
||||
//body.each(0, |index, key: &str|if index == 0 {
|
||||
//bind.add_def(&format!("({key} (axis (dec {name})))"))?;
|
||||
//Ok(1)
|
||||
//} else if index == 1 {
|
||||
//bind.add_def(&format!("({key} (axis (inc {name})))"))?;
|
||||
//Ok(2)
|
||||
//} else {
|
||||
//err!("top-level (axis) takes 2 keysyms: dec and inc")
|
||||
//})?;
|
||||
//Ok(bind)
|
||||
//}
|
||||
|
|
|
|||
39
src/conf/axis.rs
Normal file
39
src/conf/axis.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use crate::*;
|
||||
|
||||
/// ```
|
||||
/// use ::{std::sync::Arc, tek::*};
|
||||
/// let axis = Axis::<Arc<str>>::eval("@up @down").unwrap();
|
||||
/// ```
|
||||
pub struct Axis<T>(pub T, pub T);
|
||||
|
||||
impl_debug!(<T: Debug> Axis<T>: |self, w| { write!(w, "Axis({:?} {:?})", self.0, self.1) });
|
||||
|
||||
impl<T: for<'a> From<&'a str>, U: AsRef<str>, V: AsRef<str>> From<(U, V)> for Axis<T> {
|
||||
fn from ((decr, incr): (U, V)) -> Self {
|
||||
Self(decr.as_ref().into(), incr.as_ref().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: for<'a> From<&'a str>> Axis<T> {
|
||||
pub fn eval (body: impl Language) -> Usually<Self> {
|
||||
let mut axis: (Option<T>, Option<T>) = Default::default();
|
||||
body.each(0, |index, item: &str|if index == 0 {
|
||||
axis.0 = Some(item.into());
|
||||
Ok(1)
|
||||
} else if index == 1 {
|
||||
axis.1 = Some(item.into());
|
||||
Ok(2)
|
||||
} else {
|
||||
err!("def (axis X dec inc)")
|
||||
})?;
|
||||
|
||||
if let Some(dec) = axis.0 && let Some(inc) = axis.1 {
|
||||
Ok(Self(dec, inc))
|
||||
} else {
|
||||
err!("def (axis X dec inc)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Named control axis.
|
||||
pub struct ControlAxis(pub Arc<str>);
|
||||
237
src/conf/bind.rs
237
src/conf/bind.rs
|
|
@ -1,50 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of input bindings.
|
||||
///
|
||||
/// ```
|
||||
/// # use tek::*;
|
||||
/// let state = Binds::default();
|
||||
/// state.binds_def_keys(stringify!(Global (@f1 help) (@f2 rename))).unwrap();
|
||||
/// state.binds_def_axis(stringify!(X @left @right)).unwrap();
|
||||
/// ```
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Binds {
|
||||
pub keys: Arc<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>,
|
||||
pub axis: Arc<RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>>>,
|
||||
}
|
||||
|
||||
impl Binds {
|
||||
pub fn binds_clear (&self) {
|
||||
*self.keys.write() = Default::default();
|
||||
*self.axis.write() = Default::default();
|
||||
}
|
||||
pub fn binds_find (&self, id: impl AsRef<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
|
||||
self.keys.read().get(id.as_ref()).cloned()
|
||||
}
|
||||
pub fn binds_for_each <T> (
|
||||
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->T
|
||||
) {
|
||||
for (k, v) in self.keys.read().iter() {
|
||||
cb(k, v);
|
||||
}
|
||||
}
|
||||
pub fn binds_def_keys (&self, expr: impl Language) -> Usually<()> {
|
||||
self.keys.write().insert(
|
||||
expr.head()?.ok_or("bind: missing name")?.into(),
|
||||
Bind::new(expr.tail()?.unwrap_or(""))?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
pub fn binds_def_axis (&self, expr: impl Language) -> Usually<()> {
|
||||
self.axis.write().insert(
|
||||
expr.head()?.ok_or("axis: missing name")?.into(),
|
||||
Axis::new(expr.tail()?.unwrap_or(""))?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A map of input events (e.g. [TuiEvent]) to [Binding]s.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -98,7 +53,7 @@ impl Bind<TuiEvent, Arc<str>> {
|
|||
fn add_def (&mut self, item: &str) -> Usually<()> {
|
||||
let expr = item.expr()?;
|
||||
let head = expr.head()?;
|
||||
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
|
||||
if let Some(event) = TuiKey::eval(&head)?.to_crossterm() {
|
||||
let mut commands: Vec<Arc<str>> = vec![];
|
||||
item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?;
|
||||
self.add(TuiEvent(event), Binding { commands: commands.into(), ..Default::default() });
|
||||
|
|
@ -107,55 +62,11 @@ impl Bind<TuiEvent, Arc<str>> {
|
|||
// TODO
|
||||
Ok(())
|
||||
} else {
|
||||
err!("Binds::add: invalid key: {:?}", item.expr()?.head()?)
|
||||
err!("Bind::add_def: invalid key: {:?}", item.expr()?.head()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// use ::{std::sync::Arc, tek::*};
|
||||
/// let axis = Axis::<Arc<str>>::new("@up @down").unwrap();
|
||||
/// ```
|
||||
pub struct Axis<T>(T, T);
|
||||
|
||||
impl_debug!(<T: Debug> Axis<T>: |self, w| { write!(w, "Axis({:?} {:?})", self.0, self.1) });
|
||||
|
||||
impl<T: for<'a> From<&'a str>, U: AsRef<str>, V: AsRef<str>> From<(U, V)> for Axis<T> {
|
||||
fn from ((decr, incr): (U, V)) -> Self {
|
||||
Self(decr.as_ref().into(), incr.as_ref().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + for<'a> From<&'a str>> Axis<T> {
|
||||
pub fn new (body: impl Language) -> Usually<Self> {
|
||||
let mut axis: (T, T) = Default::default();
|
||||
body.each(0, |index, item: &str|if index == 0 {
|
||||
axis.0 = item.into();
|
||||
Ok(1)
|
||||
} else if index == 1 {
|
||||
axis.0 = item.into();
|
||||
Ok(2)
|
||||
} else {
|
||||
err!("def (axis X dec inc)")
|
||||
})?;
|
||||
Ok(Self(axis.0, axis.1))
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn load_axis_bind (name: &str, body: impl Language) -> Usually<Self> {
|
||||
//let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new();
|
||||
//body.each(0, |index, key: &str|if index == 0 {
|
||||
//bind.add_def(&format!("({key} (axis (dec {name})))"))?;
|
||||
//Ok(1)
|
||||
//} else if index == 1 {
|
||||
//bind.add_def(&format!("({key} (axis (inc {name})))"))?;
|
||||
//Ok(2)
|
||||
//} else {
|
||||
//err!("top-level (axis) takes 2 keysyms: dec and inc")
|
||||
//})?;
|
||||
//Ok(bind)
|
||||
//}
|
||||
|
||||
impl<E: Clone + Ord, C> Bind<E, C> {
|
||||
/// Add a binding to an owned event map.
|
||||
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
||||
|
|
@ -215,152 +126,8 @@ impl<C: Default> Default for Binding<C> {
|
|||
#[derive(Clone)]
|
||||
pub struct Condition(pub Arc<Box<dyn Fn()->bool + Send + Sync>>);
|
||||
|
||||
/// Named control axis.
|
||||
pub struct ControlAxis(pub Arc<str>);
|
||||
|
||||
impl_debug!(Condition: |self, w| { write!(w, "*") });
|
||||
|
||||
pub trait HasBinds {
|
||||
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>;
|
||||
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>>;
|
||||
|
||||
fn binds_clear (&self) {
|
||||
*self.binds_keys().write() = Default::default();
|
||||
*self.binds_axis().write() = Default::default();
|
||||
}
|
||||
fn binds_find (&self, id: impl AsRef<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
|
||||
self.binds_keys().read().get(id.as_ref()).cloned()
|
||||
}
|
||||
fn binds_for_each <T> (
|
||||
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->T
|
||||
) {
|
||||
for (k, v) in self.binds_keys().read().iter() {
|
||||
cb(k, v);
|
||||
}
|
||||
}
|
||||
/// Define a top-level key binding.
|
||||
///
|
||||
/// ```
|
||||
/// # use ::{std::sync::Arc, tek::*};
|
||||
/// let config: Config = Default::default();
|
||||
/// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap();
|
||||
/// ```
|
||||
fn binds_def_keys (&self, expr: impl Language) -> Usually<()> {
|
||||
self.binds_keys().write().insert(
|
||||
expr.head()?.ok_or("bind: missing name")?.into(),
|
||||
Bind::new(expr.tail()?.unwrap_or(""))?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
/// Define an top-level axis binding (keypair).
|
||||
///
|
||||
/// ```
|
||||
/// # use ::{std::sync::Arc, tek::*};
|
||||
/// let config: Config = Default::default();
|
||||
/// config.binds_def_axis(stringify!(Y @up @down)).unwrap();
|
||||
/// ```
|
||||
fn binds_def_axis (&self, expr: impl Language) -> Usually<()> {
|
||||
self.binds_axis().write().insert(
|
||||
expr.head()?.ok_or("axis: missing name")?.into(),
|
||||
Axis::new(expr.tail()?.unwrap_or(""))?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBinds for App {
|
||||
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
|
||||
&self.config.config.binds.keys
|
||||
}
|
||||
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
|
||||
&self.config.config.binds.axis
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBinds for ConfigWatcher {
|
||||
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
|
||||
&self.config.binds.keys
|
||||
}
|
||||
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
|
||||
&self.config.binds.axis
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBinds for Config {
|
||||
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
|
||||
&self.binds.keys
|
||||
}
|
||||
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>> {
|
||||
&self.binds.axis
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DispatchBind<C: Clone>: HasBinds + HasModeStack + HasModes + Namespace<C> {
|
||||
fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> {
|
||||
if let Some(bindings) = self.input_cmds_find(input) {
|
||||
return self.input_cmds_exec(bindings)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// ```
|
||||
/// # use ::{std::sync::Arc, tek::*};
|
||||
/// let app = config_reload(App::default(), stringify!(
|
||||
/// (axis Y @up @down)
|
||||
/// (mode Menu
|
||||
/// (axis Y prev next)
|
||||
/// (bind (@enter confirm)))
|
||||
/// )).unwrap();
|
||||
/// panic!("{:?}\n{:?}", app.config.config.binds, app.config.config.modes);
|
||||
/// assert!(app.input_cmds_find(&"left".into()).is_none());
|
||||
/// assert!(app.input_cmds_find(&"enter".into()).is_some());
|
||||
/// assert!(app.input_cmds_find(&"down".into()).is_some());
|
||||
/// assert!(app.input_cmds_find(&"up".into()).is_some());
|
||||
/// ```
|
||||
fn input_cmds_find (&self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
|
||||
for name in self.mode_stack().read().iter().rev() {
|
||||
let mode = self.modes_get(name).as_ref().map(Arc::clone);
|
||||
if let Some(mode) = mode {
|
||||
for (ctrl, (dec, inc)) in mode.axis.iter() {
|
||||
panic!("{ctrl:?} {dec:?} {inc:?}");
|
||||
}
|
||||
for id in mode.keys.iter() {
|
||||
if let Some(event_map) = self.binds_find(id)
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
return Some(bindings.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn input_cmds_exec (&mut self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
|
||||
for binding in bindings.iter() {
|
||||
for src in binding.commands.iter() {
|
||||
if let Some(command) = Namespace::<C>::namespace(self, &src)? {
|
||||
self.cmd_dispatch(command)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cmd_dispatch (&mut self, command: C) -> Usually<()>;
|
||||
}
|
||||
|
||||
impl DispatchBind<AppCmd> for App {
|
||||
fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> {
|
||||
match command.clone().dispatch(self) {
|
||||
Ok(undo) => {
|
||||
self.history.push((command, undo));
|
||||
Ok(())
|
||||
},
|
||||
Err(err) => {
|
||||
self.history.push((command, None));
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_config_binds (config: &Config) {
|
||||
use ::ansi_term::Color::*;
|
||||
config.binds_for_each(|k, v|{
|
||||
|
|
|
|||
330
src/conf/lang.rs
330
src/conf/lang.rs
|
|
@ -43,6 +43,301 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
impl View<Tui, App> {
|
||||
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
source: source.as_ref().into(),
|
||||
render: Self::compile(source)?
|
||||
})
|
||||
}
|
||||
|
||||
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
|
||||
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
|
||||
{
|
||||
Box::new(f)
|
||||
}
|
||||
|
||||
fn compile (source: impl AsRef<str>) ->
|
||||
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
let source = source.as_ref();
|
||||
let layer = if let Ok(Some(expr)) = source.expr() {
|
||||
Self::compile_expr(expr.into())?
|
||||
} else if let Ok(Some(word)) = source.word() {
|
||||
Self::compile_word(word.into())?
|
||||
} else {
|
||||
return err!("not word/expr:\n{source:?}")
|
||||
};
|
||||
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
|
||||
}
|
||||
|
||||
fn compile_expr (expr: Arc<str>) ->
|
||||
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
|
||||
macro_rules! arg {
|
||||
($src:expr, $head:expr, $index:expr, $name:expr) => {
|
||||
$src.nth($index)?
|
||||
.ok_or_else(||Box::<dyn Error>::from(format!(
|
||||
"{}: no arg #{} ({}) in {}", $head, $index, $name, $src
|
||||
)))?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
|
||||
|
||||
match ns {
|
||||
|
||||
"when" => {
|
||||
let cond = Arc::from(arg!(expr, head, 1, "condition"));
|
||||
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
when(
|
||||
cond(state)?,
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"either" => {
|
||||
let cond = Arc::from(arg!(expr, head, 1, "condition"));
|
||||
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
|
||||
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
either(
|
||||
cond(state)?,
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"bsp" | "split" | "stack" => {
|
||||
let split = head.split('/').skip(1).next();
|
||||
let split = match split {
|
||||
Some("n") => Split::North,
|
||||
Some("s") => Split::South,
|
||||
Some("e") => Split::East,
|
||||
Some("w") => Split::West,
|
||||
Some("a") => Split::Above,
|
||||
Some("b") => Split::Below,
|
||||
_ => return err!("invalid split: {split:?}")
|
||||
};
|
||||
let a = Self::compile(arg!(expr, head, 1, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
|
||||
Self::boxed(move|state, screen|split.stack(
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen))
|
||||
},
|
||||
|
||||
"bar" => {
|
||||
let split = head.split('/').skip(1).next();
|
||||
let split = match split {
|
||||
Some("n") => Split::North,
|
||||
Some("s") => Split::South,
|
||||
Some("e") => Split::East,
|
||||
Some("w") => Split::West,
|
||||
Some("a") => Split::Above,
|
||||
Some("b") => Split::Below,
|
||||
_ => return err!("invalid split: {split:?}")
|
||||
};
|
||||
let size = Arc::from(arg!(expr, head, 1, "size"));
|
||||
let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::<dyn Error>::from("bar: no size"));
|
||||
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||
Self::boxed(move|state, screen|split.bar(
|
||||
size(state)?,
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen))
|
||||
},
|
||||
|
||||
"align" => {
|
||||
let azimuth = head.split('/').skip(1).next();
|
||||
let azimuth = match azimuth {
|
||||
Some("n") => Azimuth::N,
|
||||
Some("s") => Azimuth::S,
|
||||
Some("e") => Azimuth::E,
|
||||
Some("w") => Azimuth::W,
|
||||
Some("ne") => Azimuth::NE,
|
||||
Some("se") => Azimuth::SE,
|
||||
Some("nw") => Azimuth::NW,
|
||||
Some("sw") => Azimuth::SW,
|
||||
Some("c") => Azimuth::C,
|
||||
Some("x") => Azimuth::X,
|
||||
Some("y") => Azimuth::Y,
|
||||
_ => return err!("invalid azimuth: {azimuth:?}")
|
||||
};
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
Align(
|
||||
Some(azimuth),
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"full" => {
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => Self::boxed(move|state, screen|{
|
||||
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("h") | Some("y") => Self::boxed(move|state, screen|{
|
||||
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{
|
||||
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"exact" | "min" | "max" | "push" | "pull" | "pad" | "shrink" | "expand" => {
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
Some("h") | Some("y") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
Some("wh") | Some("xy") | None => {
|
||||
let value1 = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value1 = move|state: &App|state.namespace(&value1);
|
||||
let value2 = Arc::from(arg!(expr, head, 2, "value"));
|
||||
let value2 = move|state: &App|state.namespace(&value2);
|
||||
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"fg" | "bg" => {
|
||||
let color = Arc::from(arg!(expr, head, 1, "color"));
|
||||
let color = move|state: &App|state.namespace(&color);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"fg" => Self::boxed(move|state, screen|fg(
|
||||
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
|
||||
).draw(screen)),
|
||||
"bg" => Self::boxed(move|state, screen|bg(
|
||||
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
|
||||
).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"text" => {
|
||||
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
|
||||
Self::boxed(move|_state, screen|{
|
||||
text.draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
_ => return err!("compile_expr: unexpected: {expr:?}")
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
return err!("compile_expr: invalid expression: {expr:?}")
|
||||
}))
|
||||
}
|
||||
|
||||
fn compile_word (word: Arc<str>)
|
||||
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
macro_rules! view {
|
||||
(|$state:ident|$body:expr)=>{Box::new(move|$state, to|$body.draw(to))};
|
||||
($const:expr)=>{Box::new(move|_, to|$const.draw(to))};
|
||||
()=>{Box::new(move|_, __|Ok(None))};
|
||||
}
|
||||
|
||||
Ok(Arc::new(match word.split("/").next() {
|
||||
Some("Beat") => view!(),
|
||||
Some("Bpm") => view!(),
|
||||
Some("BrowseTitle") => view!(|app|view_browse_title(app)),
|
||||
Some("Config") => view!(|app|view_config(app)),
|
||||
Some("Debug") => Box::new(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
|
||||
Some("Device") => view!(|app|view_device(app)),
|
||||
Some("Menu") => view!(|app|view_menu(app)),
|
||||
Some("Editor") => view!(|app|app.editor()),
|
||||
Some("MetersIn") => view!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
|
||||
Some("MetersOut") => view!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
|
||||
Some("ModeList") => view!(|app|view_modes(app)),
|
||||
Some("HistoryCount") => view!(|app|app.history.len()),
|
||||
Some("HistoryList") => view!(|app|view_history_list(app)),
|
||||
Some("ScenesClips") => view!(|app|view_scenes_clips(app)),
|
||||
Some("ScenesNames") => view!(|app|app.view_scenes_names()),
|
||||
Some("Sessions") => view!(|___|view_sessions()),
|
||||
Some("Selected") => view!(|app|view_selection(app)),
|
||||
Some("Templates") => view!(|app|view_templates(app)),
|
||||
Some("Time") => view!(),
|
||||
Some("Tracks") => view!("TODO tracks"),
|
||||
Some("TracksDevices") => view!(|app|app.project.view_track_devices(app.color.clone())),
|
||||
Some("TracksDevicesAdd") => view!(|app|app.project.view_track_device_add()),
|
||||
Some("TracksDevicesCells") => view!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
|
||||
Some("TracksDevicesCount") => view!(|app|app.project.view_track_device_count()),
|
||||
Some("TracksInputs") => view!(|app|app.project.view_track_inputs(app.color.clone())),
|
||||
Some("TracksInputsAdd") => view!(|app|app.project.view_track_input_add()),
|
||||
Some("TracksInputsCells") => view!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
|
||||
Some("TracksInputsCount") => view!(|app|app.project.view_track_input_count()),
|
||||
Some("TracksNames") => view!(|app|app.project.view_track_names(app.color.clone())),
|
||||
Some("TracksNamesAdd") => view!(|app|app.project.view_track_names_add()),
|
||||
Some("TracksNamesCells") => view!(|app|app.project.view_track_names_cells()),
|
||||
Some("TracksNamesCount") => view!(|app|app.project.view_track_names_count()),
|
||||
Some("TracksOutputs") => view!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
|
||||
Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()),
|
||||
Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()),
|
||||
Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()),
|
||||
Some(_) => Box::new(move|state, to|if let Some(view) = state.views_get(word.as_ref()) {
|
||||
(view.render)(state, to)
|
||||
} else {
|
||||
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn eval_expr <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
|
||||
//Ok(match expr.head()? {
|
||||
|
|
@ -936,3 +1231,38 @@ impl ControlAxis {
|
|||
eval_word(src, |word|Ok(ControlAxis(word.into())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Dialog {
|
||||
pub fn eval <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
||||
eval_word(&src, |word|Ok(match word {
|
||||
":none" => Dialog::None,
|
||||
":options" => Dialog::Options,
|
||||
":device" => Dialog::Device(0),
|
||||
":device/prev" => Dialog::Device(0),
|
||||
":device/next" => Dialog::Device(0),
|
||||
":help" => Dialog::Help(0),
|
||||
":save" => Dialog::Browse(
|
||||
BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":load" => Dialog::Browse(
|
||||
BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":import/clip" => Dialog::Browse(
|
||||
BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/clip" => Dialog::Browse(
|
||||
BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":import/sample" => Dialog::Browse(
|
||||
BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/sample" => Dialog::Browse(
|
||||
BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
_ => return Err(
|
||||
format!("not a dialog: {}", src.src()?.unwrap_or("<???>")).into()
|
||||
)
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
src/conf/mode.rs
131
src/conf/mode.rs
|
|
@ -2,112 +2,6 @@ use crate::*;
|
|||
|
||||
impl_as_mut!(Mode: |self: Mode| self);
|
||||
|
||||
/// Has a stack of currently active interaction modes.
|
||||
pub trait HasModeStack {
|
||||
/// Stack of active modes.
|
||||
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>>;
|
||||
}
|
||||
|
||||
impl HasModeStack for App {
|
||||
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>> {
|
||||
&self.modes
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasModes {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>>;
|
||||
|
||||
/// Register a mode.
|
||||
///
|
||||
/// ```
|
||||
/// # use ::{std::sync::Arc, tek::*};
|
||||
/// let config: Config = Default::default();
|
||||
///
|
||||
/// config.modes_add(stringify!(Menu
|
||||
/// (name Menu)
|
||||
/// (info Mode selector.)
|
||||
/// (axis Y Prev Next)
|
||||
/// (bind Confirm)
|
||||
/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog))))
|
||||
/// )).unwrap();
|
||||
///
|
||||
/// let _: Arc<Mode> = config.modes_get("Menu").unwrap();
|
||||
/// ```
|
||||
fn modes_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||
let name = expr.head()?.ok_or("mode: missing name")?;
|
||||
let body = expr.tail()?.ok_or("mode: missing body")?;
|
||||
let mut mode = Mode::default();
|
||||
body.each(&mut mode, |c, s|c.add(s))?;
|
||||
self.modes().write().insert(name.into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
/// Get a mode by name.
|
||||
fn modes_get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
|
||||
self.modes().read().get(name.as_ref()).cloned()
|
||||
}
|
||||
/// Run something for each mode.
|
||||
fn modes_for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
||||
for (k, v) in self.modes().read().iter() {
|
||||
let _ = ator(k.as_ref(), v.as_ref());
|
||||
}
|
||||
}
|
||||
/// Count modes.
|
||||
fn modes_count (&self) -> usize {
|
||||
self.modes().read().len()
|
||||
}
|
||||
/// Clear modes.
|
||||
fn modes_clear (&self) {
|
||||
*self.modes().write() = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
impl HasModes for App {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||
&self.config.config.modes.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HasModes for ConfigWatcher {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||
&self.config.modes.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HasModes for Config {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||
&self.modes.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HasModes for Modes {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HasModes for Mode {
|
||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||
&self.modes.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Collection of UI modes.
|
||||
///
|
||||
/// ```
|
||||
/// # use tek::*;
|
||||
/// // Empty mode registry:
|
||||
/// let mut state = Modes::default();
|
||||
/// assert_eq!(state.modes_count(), 0);
|
||||
/// assert!(state.modes_get("Foo").is_none());
|
||||
///
|
||||
/// // Add a mode:
|
||||
/// let _ = state.modes_add(&"Foo (name Foo) (info Bar)").unwrap();
|
||||
/// assert_eq!(state.modes_count(), 1);
|
||||
/// assert!(state.modes_get("Foo").is_some());
|
||||
/// ```
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Modes(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
|
||||
|
||||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -120,12 +14,11 @@ pub struct Mode {
|
|||
pub info: Vec<Arc<str>>,
|
||||
pub view: Vec<Arc<View<Tui, App>>>,
|
||||
pub keys: Vec<Arc<str>>,
|
||||
pub axis: BTreeMap<Arc<str>, (Arc<str>, Arc<str>)>,
|
||||
pub modes: Modes,
|
||||
pub axis: BTreeMap<Arc<str>, Arc<str>>,
|
||||
pub modes: Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
/// Supported definitions:
|
||||
|
|
@ -164,8 +57,7 @@ impl Mode {
|
|||
let body = tail.tail()?.ok_or("submode: missing body")?;
|
||||
let mut submode = Mode::default();
|
||||
body.each(&mut submode, |c, s|c.add(s))?;
|
||||
let modes = self.modes.clone();
|
||||
modes.0.write().insert(name.into(), Arc::new(submode));
|
||||
self.modes.write().insert(name.into(), Arc::new(submode));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
|
@ -177,27 +69,22 @@ impl Mode {
|
|||
}
|
||||
|
||||
fn add_axis (&mut self, tail: &str) -> Usually<&mut Self> {
|
||||
let mut axis: Option<Arc<str>> = None;
|
||||
let mut dec: Option<Arc<str>> = None;
|
||||
let mut inc: Option<Arc<str>> = None;
|
||||
let mut axis: Option<Arc<str>> = None;
|
||||
let mut param: Option<Arc<str>> = None;
|
||||
tail.each(0, |index: usize, src: &str|if index == 0 {
|
||||
axis = Some(src.into());
|
||||
Ok(1)
|
||||
} else if index == 1 {
|
||||
dec = Some(src.into());
|
||||
param = Some(src.into());
|
||||
Ok(2)
|
||||
} else if index == 2 {
|
||||
inc = Some(src.into());
|
||||
Ok(3)
|
||||
} else {
|
||||
err!("expected (mode (axis [Name] [command] [command])), got: {tail}")
|
||||
})?;
|
||||
if let Some(axis) = axis && let Some(dec) = dec && let Some(inc) = inc {
|
||||
self.keys.push(format!("((dec {axis}) {dec})").into());
|
||||
self.keys.push(format!("((inc {axis}) {inc})").into());
|
||||
if let Some(axis) = axis && let Some(param) = param {
|
||||
self.axis.insert(axis.into(), param.into());
|
||||
Ok(self)
|
||||
} else {
|
||||
err!("expected (mode (axis X Decrement Increment)), got: {tail}")
|
||||
err!("mode: expected (axis X Param), got: {tail}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
395
src/conf/view.rs
395
src/conf/view.rs
|
|
@ -19,12 +19,6 @@ impl_default!(ClockView: {
|
|||
}
|
||||
});
|
||||
|
||||
/// Collection of custom view definitions.
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Views(
|
||||
pub Arc<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>
|
||||
);
|
||||
|
||||
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
|
||||
pub struct View<S: Screen, T> {
|
||||
pub source: Arc<str>,
|
||||
|
|
@ -75,8 +69,31 @@ pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw<Tui> {
|
|||
)
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let x = "";
|
||||
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
||||
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
||||
/// ```
|
||||
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
|
||||
let theme = ItemTheme::G[96];
|
||||
let sr = field_h(theme, "SR", sr);
|
||||
let buf = field_h(theme, "Buf", buf);
|
||||
let lat = field_h(theme, "Lat", lat);
|
||||
bg(Black, east!(above(
|
||||
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
|
||||
east!(sr, buf, lat).align_e().full_wh(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
|
||||
bar(Split::South, 1u16,
|
||||
dim(true, "Mode"),
|
||||
bold(true, state.mode_stack().read().get(0))
|
||||
).push_x(1).draw(to)
|
||||
}
|
||||
|
||||
pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
|
||||
for name in state.modes.read().iter() {
|
||||
for name in state.mode_stack().read().iter() {
|
||||
let mut error = false;
|
||||
if let Some(mode) = state.modes_get(name).as_ref().map(Arc::clone) {
|
||||
for (index, view) in mode.view.iter().enumerate() {
|
||||
|
|
@ -107,303 +124,6 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
impl View<Tui, App> {
|
||||
|
||||
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
source: source.as_ref().into(),
|
||||
render: Self::compile(source)?
|
||||
})
|
||||
}
|
||||
|
||||
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
|
||||
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
|
||||
{
|
||||
Box::new(f)
|
||||
}
|
||||
|
||||
fn compile (source: impl AsRef<str>) ->
|
||||
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
let source = source.as_ref();
|
||||
let layer = if let Ok(Some(expr)) = source.expr() {
|
||||
Self::compile_expr(expr.into())?
|
||||
} else if let Ok(Some(word)) = source.word() {
|
||||
Self::compile_word(word.into())?
|
||||
} else {
|
||||
return err!("not word/expr:\n{source:?}")
|
||||
};
|
||||
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
|
||||
}
|
||||
|
||||
fn compile_expr (expr: Arc<str>) ->
|
||||
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
|
||||
macro_rules! arg {
|
||||
($src:expr, $head:expr, $index:expr, $name:expr) => {
|
||||
$src.nth($index)?
|
||||
.ok_or_else(||Box::<dyn Error>::from(format!(
|
||||
"{}: no arg #{} ({}) in {}", $head, $index, $name, $src
|
||||
)))?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
|
||||
|
||||
match ns {
|
||||
|
||||
"when" => {
|
||||
let cond = Arc::from(arg!(expr, head, 1, "condition"));
|
||||
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
when(
|
||||
cond(state)?,
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"either" => {
|
||||
let cond = Arc::from(arg!(expr, head, 1, "condition"));
|
||||
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
|
||||
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
either(
|
||||
cond(state)?,
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"bsp" | "split" | "stack" => {
|
||||
let split = head.split('/').skip(1).next();
|
||||
let split = match split {
|
||||
Some("n") => Split::North,
|
||||
Some("s") => Split::South,
|
||||
Some("e") => Split::East,
|
||||
Some("w") => Split::West,
|
||||
Some("a") => Split::Above,
|
||||
Some("b") => Split::Below,
|
||||
_ => return err!("invalid split: {split:?}")
|
||||
};
|
||||
let a = Self::compile(arg!(expr, head, 1, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
|
||||
Self::boxed(move|state, screen|split.stack(
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen))
|
||||
},
|
||||
|
||||
"bar" => {
|
||||
let split = head.split('/').skip(1).next();
|
||||
let split = match split {
|
||||
Some("n") => Split::North,
|
||||
Some("s") => Split::South,
|
||||
Some("e") => Split::East,
|
||||
Some("w") => Split::West,
|
||||
Some("a") => Split::Above,
|
||||
Some("b") => Split::Below,
|
||||
_ => return err!("invalid split: {split:?}")
|
||||
};
|
||||
let size = Arc::from(arg!(expr, head, 1, "size"));
|
||||
let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::<dyn Error>::from("bar: no size"));
|
||||
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
|
||||
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
|
||||
Self::boxed(move|state, screen|split.bar(
|
||||
size(state)?,
|
||||
draw(|screen|a(state, screen)),
|
||||
draw(|screen|b(state, screen)),
|
||||
).draw(screen))
|
||||
},
|
||||
|
||||
"align" => {
|
||||
let azimuth = head.split('/').skip(1).next();
|
||||
let azimuth = match azimuth {
|
||||
Some("n") => Azimuth::N,
|
||||
Some("s") => Azimuth::S,
|
||||
Some("e") => Azimuth::E,
|
||||
Some("w") => Azimuth::W,
|
||||
Some("ne") => Azimuth::NE,
|
||||
Some("se") => Azimuth::SE,
|
||||
Some("nw") => Azimuth::NW,
|
||||
Some("sw") => Azimuth::SW,
|
||||
Some("c") => Azimuth::C,
|
||||
Some("x") => Azimuth::X,
|
||||
Some("y") => Azimuth::Y,
|
||||
_ => return err!("invalid azimuth: {azimuth:?}")
|
||||
};
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
Self::boxed(move|state, screen|{
|
||||
Align(
|
||||
Some(azimuth),
|
||||
draw(|screen|thunk(state, screen))
|
||||
).draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
"full" => {
|
||||
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => Self::boxed(move|state, screen|{
|
||||
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("h") | Some("y") => Self::boxed(move|state, screen|{
|
||||
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{
|
||||
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"exact" | "min" | "max" | "push" | "pull" | "pad" | "shrink" | "expand" => {
|
||||
match head.split('/').skip(1).next() {
|
||||
Some("w") | Some("x") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
Some("h") | Some("y") => {
|
||||
let value = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value = move|state: &App|state.namespace(&value);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
Some("wh") | Some("xy") | None => {
|
||||
let value1 = Arc::from(arg!(expr, head, 1, "value"));
|
||||
let value1 = move|state: &App|state.namespace(&value1);
|
||||
let value2 = Arc::from(arg!(expr, head, 2, "value"));
|
||||
let value2 = move|state: &App|state.namespace(&value2);
|
||||
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
|
||||
match ns {
|
||||
"shrink" => Self::boxed(move|state, screen|Shrink::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"expand" => Self::boxed(move|state, screen|Expand::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"fg" | "bg" => {
|
||||
let color = Arc::from(arg!(expr, head, 1, "color"));
|
||||
let color = move|state: &App|state.namespace(&color);
|
||||
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
|
||||
match ns {
|
||||
"fg" => Self::boxed(move|state, screen|fg(
|
||||
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
|
||||
).draw(screen)),
|
||||
"bg" => Self::boxed(move|state, screen|bg(
|
||||
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
|
||||
).draw(screen)),
|
||||
_ => unreachable!()
|
||||
}
|
||||
},
|
||||
|
||||
"text" => {
|
||||
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
|
||||
Self::boxed(move|_state, screen|{
|
||||
text.draw(screen)
|
||||
})
|
||||
},
|
||||
|
||||
_ => return err!("compile_expr: unexpected: {expr:?}")
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
return err!("compile_expr: invalid expression: {expr:?}")
|
||||
}))
|
||||
}
|
||||
|
||||
fn compile_word (word: Arc<str>)
|
||||
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
|
||||
{
|
||||
macro_rules! view {
|
||||
(|$state:ident|$body:expr)=>{Box::new(move|$state, to|$body.draw(to))};
|
||||
($const:expr)=>{Box::new(move|_, to|$const.draw(to))};
|
||||
()=>{Box::new(move|_, __|Ok(None))};
|
||||
}
|
||||
|
||||
Ok(Arc::new(match word.split("/").next() {
|
||||
Some("Beat") => view!(),
|
||||
Some("Bpm") => view!(),
|
||||
Some("BrowseTitle") => view!(|app|view_browse_title(app)),
|
||||
Some("Config") => view!(|app|view_config(app)),
|
||||
Some("Debug") => Box::new(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
|
||||
Some("Device") => view!(|app|view_device(app)),
|
||||
Some("Menu") => view!(|app|view_menu(app)),
|
||||
Some("Editor") => view!(|app|app.editor()),
|
||||
Some("MetersIn") => view!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
|
||||
Some("MetersOut") => view!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
|
||||
Some("ModeList") => view!(|app|view_modes(app)),
|
||||
Some("HistoryCount") => view!(|app|app.history.len()),
|
||||
Some("HistoryList") => view!(|app|view_history_list(app)),
|
||||
Some("ScenesClips") => view!(|app|view_scenes_clips(app)),
|
||||
Some("ScenesNames") => view!(|app|app.view_scenes_names()),
|
||||
Some("Sessions") => view!(|___|view_sessions()),
|
||||
Some("Selected") => view!(|app|view_selection(app)),
|
||||
Some("Templates") => view!(|app|view_templates(app)),
|
||||
Some("Time") => view!(),
|
||||
Some("Tracks") => view!("TODO tracks"),
|
||||
Some("TracksDevices") => view!(|app|app.project.view_track_devices(app.color.clone())),
|
||||
Some("TracksDevicesAdd") => view!(|app|app.project.view_track_device_add()),
|
||||
Some("TracksDevicesCells") => view!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
|
||||
Some("TracksDevicesCount") => view!(|app|app.project.view_track_device_count()),
|
||||
Some("TracksInputs") => view!(|app|app.project.view_track_inputs(app.color.clone())),
|
||||
Some("TracksInputsAdd") => view!(|app|app.project.view_track_input_add()),
|
||||
Some("TracksInputsCells") => view!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
|
||||
Some("TracksInputsCount") => view!(|app|app.project.view_track_input_count()),
|
||||
Some("TracksNames") => view!(|app|app.project.view_track_names(app.color.clone())),
|
||||
Some("TracksNamesAdd") => view!(|app|app.project.view_track_names_add()),
|
||||
Some("TracksNamesCells") => view!(|app|app.project.view_track_names_cells()),
|
||||
Some("TracksNamesCount") => view!(|app|app.project.view_track_names_count()),
|
||||
Some("TracksOutputs") => view!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
|
||||
Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()),
|
||||
Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()),
|
||||
Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()),
|
||||
Some(_) => Box::new(move|state, to|if let Some(view) = state.views_get(word.as_ref()) {
|
||||
(view.render)(state, to)
|
||||
} else {
|
||||
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
|
||||
}),
|
||||
_ => unreachable!()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_menu (app: &App) -> impl Draw<Tui> {
|
||||
if let Dialog::Menu(selected, items) = &app.dialog {
|
||||
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{
|
||||
|
|
@ -442,7 +162,7 @@ pub fn view_config (app: &App) -> impl Draw<Tui> {
|
|||
let area = to.area();
|
||||
let XYWH(x0, y0, w, h) = area;
|
||||
let mut y = 0;
|
||||
for key in app.modes.read().iter().rev() {
|
||||
for key in app.stack.read().iter().rev() {
|
||||
if let Some(mode) = app.modes_get(key) {
|
||||
to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|Split::East.bar(16,
|
||||
key,
|
||||
|
|
@ -933,7 +653,7 @@ fn iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T
|
|||
})
|
||||
}
|
||||
|
||||
fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
|
||||
pub fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
|
||||
draw(move|to: &mut Tui|{
|
||||
let XYWH(x, y, w, h) = to.area();
|
||||
let mut x_next = x;
|
||||
|
|
@ -1081,29 +801,6 @@ pub fn draw_lv2_header (state: &Lv2, to: &mut Tui, x: u16, y: u16, w: u16) {
|
|||
//Ok(Rect { x, y, width: w, height: 1 })
|
||||
}
|
||||
|
||||
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
|
||||
bar(Split::South, 1u16,
|
||||
dim(true, "Mode"),
|
||||
bold(true, state.modes.read().get(0))
|
||||
).push_x(1).draw(to)
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let x = "";
|
||||
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
||||
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
||||
/// ```
|
||||
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
|
||||
let theme = ItemTheme::G[96];
|
||||
let sr = field_h(theme, "SR", sr);
|
||||
let buf = field_h(theme, "Buf", buf);
|
||||
let lat = field_h(theme, "Lat", lat);
|
||||
bg(Black, east!(above(
|
||||
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
|
||||
east!(sr, buf, lat).align_e().full_wh(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn view_device <'a> (state: &'a App) -> impl Draw<Tui> {
|
||||
let selected = state.dialog.device_kind().unwrap();
|
||||
south(
|
||||
|
|
@ -1157,7 +854,7 @@ pub fn view_scene_name <'a> (
|
|||
}
|
||||
|
||||
/// Draw clips per scene
|
||||
fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> {
|
||||
pub fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> {
|
||||
let select = state.selection();
|
||||
let editor = state.editor();
|
||||
let editing = state.is_editing();
|
||||
|
|
@ -1281,10 +978,11 @@ pub fn view_selection <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
|
|||
}
|
||||
|
||||
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
|
||||
let height = (state.config.modes_count() * 2) as u16;
|
||||
let config: &Config = state.config();
|
||||
let height = (config.modes_count() * 2) as u16;
|
||||
draw(move |to: &mut Tui|{
|
||||
let mut index = 0;
|
||||
state.config.modes_for_each(&mut |id: &str, profile: &Mode| {
|
||||
config.modes_for_each(&mut |id: &str, profile: &Mode| {
|
||||
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
||||
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||
|
|
@ -1691,39 +1389,6 @@ impl ClockView {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait HasViews {
|
||||
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>;
|
||||
/// Load custom view definition.
|
||||
fn views_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||
self.views().write().insert(
|
||||
expr.head()?.ok_or("view: missing name")?.into(),
|
||||
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
fn views_clear (&self) {
|
||||
*self.views().write() = Default::default();
|
||||
}
|
||||
fn views_get (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
|
||||
self.views().read().get(name.as_ref()).cloned()
|
||||
}
|
||||
fn views_for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->T) -> () {
|
||||
for (k, v) in self.views().read().iter() {
|
||||
cb(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl HasViews for App {
|
||||
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
||||
&self.config.config.views.0
|
||||
}
|
||||
}
|
||||
impl HasViews for Config {
|
||||
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
||||
&self.views.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test_view {
|
||||
use super::*;
|
||||
use proptest::prelude::*;
|
||||
|
|
|
|||
122
src/gear/device.rs
Normal file
122
src/gear/device.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
use crate::*;
|
||||
|
||||
impl App {
|
||||
/// FIXME: generalize. Set picked device in device pick dialog.
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().device_pick(0);
|
||||
/// ```
|
||||
pub fn device_pick (&mut self, index: usize) {
|
||||
self.dialog = Dialog::Device(index);
|
||||
}
|
||||
|
||||
/// FIXME: generalize. Add device to current track.
|
||||
pub fn add_device (&mut self, index: usize) -> Usually<()> {
|
||||
match index {
|
||||
0 => {
|
||||
let name = self.jack.with_client(|c|c.name().to_string());
|
||||
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
||||
let track = self.track().expect("no active track");
|
||||
let port = format!("{}/Sampler", &track.name);
|
||||
let connect = Connect::exact(format!("{name}:{midi}"));
|
||||
let sampler = if let Ok(sampler) = Sampler::new(
|
||||
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
||||
) {
|
||||
self.dialog = Dialog::None;
|
||||
Device::Sampler(sampler)
|
||||
} else {
|
||||
self.dialog = Dialog::Message("Failed to add device.".into());
|
||||
return Err("failed to add device".into())
|
||||
};
|
||||
let track = self.track_mut().expect("no active track");
|
||||
track.devices.push(sampler);
|
||||
Ok(())
|
||||
},
|
||||
1 => {
|
||||
todo!();
|
||||
//Ok(())
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tek_proc::command(Device)]
|
||||
#[tek_proc::keyword(Device)]
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceCmd {}
|
||||
|
||||
impl Device {
|
||||
pub fn name (&self) -> &str {
|
||||
match self {
|
||||
Self::Sampler(sampler) => sampler.name.as_ref(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
pub fn midi_ins (&self) -> &[MidiInput] {
|
||||
match self {
|
||||
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn midi_outs (&self) -> &[MidiOutput] {
|
||||
match self {
|
||||
Self::Sampler(_) => &[],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A device that can be plugged into the chain.
|
||||
///
|
||||
/// ```
|
||||
/// let device = tek::Device::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub enum Device {
|
||||
#[default]
|
||||
Bypass,
|
||||
Mute,
|
||||
#[cfg(feature = "sampler")]
|
||||
Sampler(Sampler),
|
||||
#[cfg(feature = "lv2")] // TODO
|
||||
Lv2(Lv2),
|
||||
#[cfg(feature = "vst2")] // TODO
|
||||
Vst2,
|
||||
#[cfg(feature = "vst3")] // TODO
|
||||
Vst3,
|
||||
#[cfg(feature = "clap")] // TODO
|
||||
Clap,
|
||||
#[cfg(feature = "sf2")] // TODO
|
||||
Sf2,
|
||||
}
|
||||
|
||||
/// Some sort of wrapper?
|
||||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||||
|
||||
pub fn device_kinds () -> &'static [&'static str] {
|
||||
&[
|
||||
#[cfg(feature = "sampler")] "Sampler",
|
||||
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
||||
]
|
||||
}
|
||||
|
||||
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
|
@ -107,205 +107,3 @@ pub fn to_rms (samples: &[f32]) -> f32 {
|
|||
.unwrap_or(0.0);
|
||||
(sum / samples.len() as f32).sqrt()
|
||||
}
|
||||
|
||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||
///
|
||||
/// ```
|
||||
/// let track: tek::Track = Default::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Track {
|
||||
/// Name of track
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of track
|
||||
pub color: ItemTheme,
|
||||
/// Preferred width of track column
|
||||
pub width: usize,
|
||||
/// MIDI sequencer state
|
||||
pub sequencer: Sequencer,
|
||||
/// Device chain
|
||||
pub devices: Vec<Device>,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
/// Create a new track with only the default [Sequencer].
|
||||
pub fn new (
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
midi_from: &[Connect],
|
||||
midi_to: &[Connect],
|
||||
) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.as_ref().into(),
|
||||
color: color.unwrap_or_default(),
|
||||
sequencer: Sequencer::new(
|
||||
format!("{}/sequencer", name.as_ref()),
|
||||
jack, clock, clip, midi_from, midi_to
|
||||
)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn new_added (
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
mins: Arc<[Connect]>,
|
||||
mouts: Arc<[Connect]>,
|
||||
) -> Usually<Self> {
|
||||
let name = name.as_ref();
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
width: (name.len() + 2).max(12),
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
sequencer: Sequencer::new(name, jack, clock, clip, mins.as_ref(), mouts.as_ref())?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
||||
}
|
||||
pub fn stop (&mut self) -> Perhaps<TrackCmd> {
|
||||
self.sequencer.enqueue_next(None);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWidth for Track {
|
||||
const MIN_WIDTH: usize = 9;
|
||||
fn width_inc (&mut self) {
|
||||
self.width += 1;
|
||||
}
|
||||
fn width_dec (&mut self) {
|
||||
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// FIXME: generalize. Set picked device in device pick dialog.
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().device_pick(0);
|
||||
/// ```
|
||||
pub fn device_pick (&mut self, index: usize) {
|
||||
self.dialog = Dialog::Device(index);
|
||||
}
|
||||
|
||||
/// FIXME: generalize. Add device to current track.
|
||||
pub fn add_device (&mut self, index: usize) -> Usually<()> {
|
||||
match index {
|
||||
0 => {
|
||||
let name = self.jack.with_client(|c|c.name().to_string());
|
||||
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
||||
let track = self.track().expect("no active track");
|
||||
let port = format!("{}/Sampler", &track.name);
|
||||
let connect = Connect::exact(format!("{name}:{midi}"));
|
||||
let sampler = if let Ok(sampler) = Sampler::new(
|
||||
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
||||
) {
|
||||
self.dialog = Dialog::None;
|
||||
Device::Sampler(sampler)
|
||||
} else {
|
||||
self.dialog = Dialog::Message("Failed to add device.".into());
|
||||
return Err("failed to add device".into())
|
||||
};
|
||||
let track = self.track_mut().expect("no active track");
|
||||
track.devices.push(sampler);
|
||||
Ok(())
|
||||
},
|
||||
1 => {
|
||||
todo!();
|
||||
//Ok(())
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tek_proc::command(Device)]
|
||||
#[tek_proc::keyword(Device)]
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceCmd {}
|
||||
|
||||
impl Device {
|
||||
pub fn name (&self) -> &str {
|
||||
match self {
|
||||
Self::Sampler(sampler) => sampler.name.as_ref(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
pub fn midi_ins (&self) -> &[MidiInput] {
|
||||
match self {
|
||||
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn midi_outs (&self) -> &[MidiOutput] {
|
||||
match self {
|
||||
Self::Sampler(_) => &[],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A device that can be plugged into the chain.
|
||||
///
|
||||
/// ```
|
||||
/// let device = tek::Device::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub enum Device {
|
||||
#[default]
|
||||
Bypass,
|
||||
Mute,
|
||||
#[cfg(feature = "sampler")]
|
||||
Sampler(Sampler),
|
||||
#[cfg(feature = "lv2")] // TODO
|
||||
Lv2(Lv2),
|
||||
#[cfg(feature = "vst2")] // TODO
|
||||
Vst2,
|
||||
#[cfg(feature = "vst3")] // TODO
|
||||
Vst3,
|
||||
#[cfg(feature = "clap")] // TODO
|
||||
Clap,
|
||||
#[cfg(feature = "sf2")] // TODO
|
||||
Sf2,
|
||||
}
|
||||
|
||||
/// Some sort of wrapper?
|
||||
pub struct DeviceAudio<'a>(pub &'a mut Device);
|
||||
|
||||
|
||||
pub fn device_kinds () -> &'static [&'static str] {
|
||||
&[
|
||||
#[cfg(feature = "sampler")] "Sampler",
|
||||
#[cfg(feature = "lv2")] "Plugin (LV2)",
|
||||
]
|
||||
}
|
||||
|
||||
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,14 @@ pub(crate) use symphonia::{
|
|||
}
|
||||
|
||||
impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
|
||||
pub trait HasSampler: AsRef<Sampler> + AsMut<Sampler> {
|
||||
fn sampler (&self) -> &Sampler {
|
||||
self.as_ref()
|
||||
}
|
||||
fn sampler_mut (&mut self) -> &mut Sampler {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl_audio!(Sampler: sampler_jack_process);
|
||||
|
||||
|
|
|
|||
81
src/gear/track.rs
Normal file
81
src/gear/track.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::*;
|
||||
|
||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||
///
|
||||
/// ```
|
||||
/// let track: tek::Track = Default::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Track {
|
||||
/// Name of track
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of track
|
||||
pub color: ItemTheme,
|
||||
/// Preferred width of track column
|
||||
pub width: usize,
|
||||
/// MIDI sequencer state
|
||||
pub sequencer: Sequencer,
|
||||
/// Device chain
|
||||
pub devices: Vec<Device>,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
/// Create a new track with only the default [Sequencer].
|
||||
pub fn new (
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
midi_from: &[Connect],
|
||||
midi_to: &[Connect],
|
||||
) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.as_ref().into(),
|
||||
color: color.unwrap_or_default(),
|
||||
sequencer: Sequencer::new(
|
||||
format!("{}/sequencer", name.as_ref()),
|
||||
jack, clock, clip, midi_from, midi_to
|
||||
)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn new_added (
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
mins: Arc<[Connect]>,
|
||||
mouts: Arc<[Connect]>,
|
||||
) -> Usually<Self> {
|
||||
let name = name.as_ref();
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
width: (name.len() + 2).max(12),
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
sequencer: Sequencer::new(name, jack, clock, clip, mins.as_ref(), mouts.as_ref())?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
||||
}
|
||||
pub fn stop (&mut self) -> Perhaps<TrackCmd> {
|
||||
self.sequencer.enqueue_next(None);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWidth for Track {
|
||||
const MIN_WIDTH: usize = 9;
|
||||
fn width_inc (&mut self) {
|
||||
self.width += 1;
|
||||
}
|
||||
fn width_dec (&mut self) {
|
||||
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
||||
}
|
||||
}
|
||||
1137
src/tek.rs
1137
src/tek.rs
File diff suppressed because it is too large
Load diff
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit ab8c8cb1c4f2bed9ef4e333d5fc091e586ff0202
|
||||
Subproject commit 65f7948e9c0b5ed535d13c354ef5f4150a801f01
|
||||
Loading…
Add table
Add a link
Reference in a new issue