compiles again

This commit is contained in:
🪞👃🪞 2025-09-02 22:39:04 +03:00
parent 34070de5f7
commit 1434adae09
21 changed files with 654 additions and 591 deletions

View file

@ -1,206 +1,282 @@
use crate::*;
impl Content<TuiOut> for App {
fn content (&self) -> impl Render<TuiOut> + '_ {
Stack::above(move|add|for dsl in self.mode.view.iter() {
add(&Fill::xy(self.view(dsl.as_ref())));
})
impl ScenesView for App {
fn w_side (&self) -> u16 { 20 }
fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(self.w_side()) }
fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) }
}
impl Draw<TuiOut> for App {
fn draw (&self, to: &mut TuiOut) {
for dsl in self.mode.view.iter() {
let _ = self.view(to, dsl).expect("render failed");
}
}
}
impl App {
fn view <'t, D: Dsl> (&'t self, index: D) -> TuiBox<'t> {
match index.src() {
Ok(Some(src)) => render_dsl(self, src),
Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")),
Err(e) => Box::new(format!("{e}")),
}
}
pub fn update_clock (&self) {
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
}
}
type TuiBox<'t> = Box<dyn Render<TuiOut> + 't>;
fn render_dsl <'t> (
state: &'t impl DslNs<'t, TuiBox<'t>>,
src: &str
) -> TuiBox<'t> {
let err: Option<Box<dyn Error>> = match state.from(&src) {
Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e),
};
let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0));
let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0));
let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0));
let bg = Color::Rgb(24, 0, 0);
Box::new(col! {
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("")))),
Tui::bg(bg, col! {
Fill::x(Bsp::e(
Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")),
Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))),
)),
Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))),
}),
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("")))),
})
}
impl<'t> DslNs<'t, TuiBox<'t>> for App {
dsl_exprs!(|app| -> TuiBox<'t> {
"text" (tail: Arc<str>) => Box::new(tail),
"fg" (color: Color, x: TuiBox<'t>) => Box::new(Tui::fg(color, x)),
"bg" (color: Color, x: TuiBox<'t>) => Box::new(Tui::bg(color, x)),
"fg/bg" (fg: Color, bg: Color, x: TuiBox<'t>) => Box::new(Tui::fg_bg(fg, bg, x)),
"either" (cond: bool, a: TuiBox<'t>, b: TuiBox<'t>) =>
Box::new(Either(cond, a, b)),
"bsp/n" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::n(a, b)),
"bsp/s" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::s(a, b)),
"bsp/e" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::e(a, b)),
"bsp/w" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::w(a, b)),
"bsp/a" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::a(a, b)),
"bsp/b" (a: TuiBox<'t>, b: TuiBox<'t>) => Box::new(Bsp::b(a, b)),
"align/nw" (x: TuiBox<'t>) => Box::new(Align::nw(x)),
"align/ne" (x: TuiBox<'t>) => Box::new(Align::ne(x)),
"align/n" (x: TuiBox<'t>) => Box::new(Align::n(x)),
"align/s" (x: TuiBox<'t>) => Box::new(Align::s(x)),
"align/e" (x: TuiBox<'t>) => Box::new(Align::e(x)),
"align/w" (x: TuiBox<'t>) => Box::new(Align::w(x)),
"align/x" (x: TuiBox<'t>) => Box::new(Align::x(x)),
"align/y" (x: TuiBox<'t>) => Box::new(Align::y(x)),
"align/c" (x: TuiBox<'t>) => Box::new(Align::c(x)),
"fill/x" (x: TuiBox<'t>) => Box::new(Fill::x(x)),
"fill/y" (x: TuiBox<'t>) => Box::new(Fill::y(x)),
"fill/xy" (x: TuiBox<'t>) => Box::new(Fill::xy(x)),
"fixed/x" (x: u16, c: TuiBox<'t>) => Box::new(Fixed::x(x, c)),
"fixed/y" (y: u16, c: TuiBox<'t>) => Box::new(Fixed::y(y, c)),
"fixed/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Fixed::xy(x, y, c)),
"min/x" (x: u16, c: TuiBox<'t>) => Box::new(Min::x(x, c)),
"min/y" (y: u16, c: TuiBox<'t>) => Box::new(Min::y(y, c)),
"min/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Min::xy(x, y, c)),
"max/x" (x: u16, c: TuiBox<'t>) => Box::new(Max::x(x, c)),
"max/y" (y: u16, c: TuiBox<'t>) => Box::new(Max::y(y, c)),
"max/xy" (x: u16, y: u16, c: TuiBox<'t>) => Box::new(Max::xy(x, y, c)),
});
dsl_words!(|app| -> TuiBox<'t> {
":logo" => Box::new(Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), Stack::south(|add|{
add(&Fixed::y(1, ""));
add(&Fixed::y(1, ""));
add(&Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"));
add(&Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))));
add(&Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"));
}))))),
":meters/input" => Box::new(Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))),
":meters/output" => Box::new(Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))),
":status" => Box::new("Status Bar"),
":tracks/names" => Box::new(app.project.view_track_names(app.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
":tracks/inputs" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))),
":tracks/devices" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))),
":tracks/outputs" => Box::new(Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))),
":scenes/names" => Box::new("Scene Names"),
":editor" => Box::new("Editor"),
":scenes" => Box::new("Scenes"),
":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog {
let items = items.clone();
let selected = *selected;
Some(Fill::x(Stack::south(move|add|{
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
add(&Tui::fg_bg(
if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
Fixed::y(2, Align::n(Fill::x(item)))
));
}
})))
fn view <'a> (&'a self, to: &mut TuiOut, dsl: impl Dsl + 'a) -> Usually<()> {
if let Ok(Some(expr)) = dsl.expr() {
self.view_expr(to, expr)
} else if let Ok(Some(word)) = dsl.word() {
self.view_word(to, word)
} else {
None
}),
":templates" => Box::new({
let modes = app.config.modes.clone();
let height = (modes.read().unwrap().len() * 2) as u16;
Fixed::y(height, Min::x(30, Stack::south(move|add|{
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
let bg = 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>");
let fg1 = Rgb(224, 192, 128);
let fg2 = Rgb(224, 128, 32);
let field_name = Fill::x(Align::w(Tui::fg(fg1, name)));
let field_id = Fill::x(Align::e(Tui::fg(fg2, id)));
let field_info = Fill::x(Align::w(info));
add(&Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s(
Bsp::a(field_name, field_id), field_info)))));
}
})))
}),
":sessions" => Box::new(Fixed::y(6, Min::x(30, Stack::south(move|add|{
let fg = Rgb(224, 192, 128);
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
add(&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name))))));
}
})))),
":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(),
match app.dialog.browser_target().unwrap() {
BrowseTarget::SaveProject => "Save project:",
BrowseTarget::LoadProject => "Load project:",
BrowseTarget::ImportSample(_) => "Import sample:",
BrowseTarget::ExportSample(_) => "Export sample:",
BrowseTarget::ImportClip(_) => "Import clip:",
BrowseTarget::ExportClip(_) => "Export clip:",
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
":device" => {
let selected = app.dialog.device_kind().unwrap();
Box::new(Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
move||device_kinds().iter(),
move|label: &&'static str, i|{
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
let lb = if i == selected { "[ " } else { " " };
let rb = if i == selected { " ]" } else { " " };
Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) }))) },
});
/// Resolve an expression if known.
fn from_expr <D: Dsl> (&self, dsl: &D) -> Perhaps<TuiBox<'t>> {
if let Some(head) = dsl.expr().head()? {
for (key, value) in Self::EXPRS.0.iter() {
if head == *key {
return value(self, dsl.expr().tail()?.unwrap_or(""))
}
}
panic!("{dsl:?}: invalid")
}
return Ok(None)
}
/// Resolve a symbol if known.
fn from_word <D: Dsl> (&self, dsl: &D) -> Perhaps<TuiBox<'t>> {
if let Some(dsl) = dsl.word()? {
let views = self.config.views.read().unwrap();
if let Some(view) = views.get(dsl) {
let view = view.clone();
std::mem::drop(views);
return Ok(Some(render_dsl(self, view.as_ref())))
}
for (word, get) in Self::WORDS.0 {
if dsl == *word {
return get(self)
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: impl DslExpr + 'a) -> Usually<()> {
let head = expr.head()?; let args = expr.tail();
let mut frags = head.src()?.unwrap_or_default().split("/");
let arg0 = args.head(); let tail0 = args.tail();
let arg1 = tail0.head(); let tail1 = tail0.tail();
let arg2 = tail1.head(); let tail2 = tail1.tail();
let arg3 = tail2.head(); let tail3 = tail2.tail();
match frags.next() {
Some("text") => to.place(&frags.next()),
Some("when") => to.place(&When::new(
self.from(arg0?)?.unwrap(),
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap())
)),
Some("either") => to.place(&Either::new(
self.from(arg0?)?.unwrap(),
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
Thunk::new(move|to: &mut TuiOut|self.view(to, arg2).unwrap())
)),
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
to.place(&Tui::fg(
DslNs::<Color>::from(self, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color")),
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
))
},
Some("bg") => {
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
to.place(&Tui::bg(
DslNs::<Color>::from(self, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color")),
Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap()),
))
},
Some("bsp") => to.place(&{
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
let b = Thunk::new(move|to: &mut TuiOut|self.view(to, arg1).unwrap());
match frags.next() {
Some("n") => Bsp::n(a, b),
Some("s") => Bsp::s(a, b),
Some("e") => Bsp::e(a, b),
Some("w") => Bsp::w(a, b),
Some("a") => Bsp::a(a, b),
Some("b") => Bsp::b(a, b),
frag => unimplemented!("bsp/{frag:?}")
}
}
}
return Ok(None)
}),
Some("align") => to.place(&{
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
match frags.next() {
Some("n") => Align::n(a),
Some("s") => Align::s(a),
Some("e") => Align::e(a),
Some("w") => Align::w(a),
Some("x") => Align::x(a),
Some("y") => Align::y(a),
Some("c") => Align::c(a),
frag => unimplemented!("align/{frag:?}")
}
}),
Some("fill") => to.place(&{
let a = Thunk::new(move|to: &mut TuiOut|self.view(to, arg0).unwrap());
match frags.next() {
Some("x") => Fill::X(a),
Some("y") => Fill::Y(a),
Some("xy") => Fill::XY(a),
frag => unimplemented!("fill/{frag:?}")
}
}),
Some("fixed") => to.place(&{
let axis = frags.next();
let arg = match axis { Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!() };
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, arg).unwrap());
match axis {
Some("x") => Fixed::X(self.from(arg0?)?.unwrap(), cb),
Some("y") => Fixed::Y(self.from(arg0?)?.unwrap(), cb),
Some("xy") => Fixed::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("fixed/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next())
}
}),
Some("min") => to.place(&{
let c = match frags.next() {
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
};
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap());
match frags.next() {
Some("x") => Min::X(self.from(arg0?)?.unwrap(), cb),
Some("y") => Min::Y(self.from(arg0?)?.unwrap(), cb),
Some("xy") => Min::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("min/{frag:?}")
}
}),
Some("max") => to.place(&{
let c = match frags.next() {
Some("x") | Some("y") => arg1, Some("xy") => arg2, _ => panic!()
};
let cb = Thunk::new(move|to: &mut TuiOut|self.view(to, c).unwrap());
match frags.next() {
Some("x") => Max::X(self.from(arg0?)?.unwrap(), cb),
Some("y") => Max::Y(self.from(arg0?)?.unwrap(), cb),
Some("xy") => Max::XY(self.from(arg0?)?.unwrap(), self.from(arg1?)?.unwrap(), cb),
frag => unimplemented!("max/{frag:?}")
}
}),
_ => panic!("unexpected: {expr:?}")
};
Ok(())
}
fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: impl DslExpr + 'a) -> Usually<()> {
let mut frags = dsl.src()?.unwrap().split("/");
match frags.next() {
Some(":logo") => to.place(&Fixed::xy(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{
Fixed::y(1, ""),
Fixed::y(1, ""),
Fixed::y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
Fixed::y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
Fixed::y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
})))),
Some(":status") => to.place(&"TODO: Status Bar"),
Some(":meters") => match frags.next() {
Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Input Meters")))),
Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::y(Align::s("Output Meters")))),
_ => panic!()
},
Some(":tracks") => match frags.next() {
None => to.place(&"TODO tracks"),
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Names")))),
Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Inputs")))),
Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Devices")))),
Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::x(Align::w("Track Outputs")))),
_ => panic!()
},
Some(":scenes") => match frags.next() {
None => to.place(&"TODO scenes"),
Some(":scenes/names") => to.place(&"TODO Scene Names"),
_ => panic!()
},
Some(":editor") => to.place(&"TODO Editor"),
Some(":dialog") => match frags.next() {
Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog {
let items = items.clone();
let selected = selected;
Some(Fill::x(Thunk::new(move|to: &mut TuiOut|{
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
to.place(&Push::y((2 * index) as u16,
Tui::fg_bg(
if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
Fixed::y(2, Align::n(Fill::x(item)))
)));
}
})))
} else {
None
}),
_ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"),
},
Some(":templates") => to.place(&{
let modes = self.config.modes.clone();
let height = (modes.read().unwrap().len() * 2) as u16;
Fixed::y(height, Min::x(30, Thunk::new(move |to: &mut TuiOut|{
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
let bg = 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>");
let fg1 = Rgb(224, 192, 128);
let fg2 = Rgb(224, 128, 32);
let field_name = Fill::x(Align::w(Tui::fg(fg1, name)));
let field_id = Fill::x(Align::e(Tui::fg(fg2, id)));
let field_info = Fill::x(Align::w(info));
to.place(&Push::y((2 * index) as u16,
Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s(
Bsp::a(field_name, field_id), field_info))))));
}
})))
}),
Some(":sessions") => to.place(&Fixed::y(6, Min::x(30, Thunk::new(|to: &mut TuiOut|{
let fg = Rgb(224, 192, 128);
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
to.place(&Push::y((2 * index) as u16,
&Fixed::y(2, Fill::x(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))));
}
})))),
Some(":browse/title") => to.place(&Fill::x(Align::w(FieldV(Default::default(),
match self.dialog.browser_target().unwrap() {
BrowseTarget::SaveProject => "Save project:",
BrowseTarget::LoadProject => "Load project:",
BrowseTarget::ImportSample(_) => "Import sample:",
BrowseTarget::ExportSample(_) => "Export sample:",
BrowseTarget::ImportClip(_) => "Import clip:",
BrowseTarget::ExportClip(_) => "Export clip:",
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
Some(":device") => {
let selected = self.dialog.device_kind().unwrap();
to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
move||device_kinds().iter(),
move|label: &&'static str, i|{
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
let lb = if i == selected { "[ " } else { " " };
let rb = if i == selected { " ]" } else { " " };
Fill::x(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) })))
},
Some(_) => {
let views = self.config.views.read().unwrap();
if let Some(dsl) = views.get(dsl.src()?.unwrap()) {
let dsl = dsl.clone();
std::mem::drop(views);
self.view(to, dsl)?
} else {
unimplemented!("{dsl:?}");
}
},
_ => unreachable!()
}
Ok(())
}
}
pub fn view_nil (_: &App) -> TuiBox {
Box::new(Fill::xy("·"))
}
//pub fn view_nil (_: &App) -> TuiCb {
//|to|to.place(&Fill::xy("·"))
//}
//Bsp::s("",
//Map::south(1,
@ -256,7 +332,7 @@ pub fn view_nil (_: &App) -> TuiBox {
////let selection = self.selection().describe(self.tracks(), self.scenes());
//let hist_len = self.history.len();
//let hist_last = self.history.last();
//Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
//Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Draw<TuiOut>)|{
//add(&Fixed::x(5, Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
//Either::new(false, // TODO
//Thunk::new(move||Fixed::x(9, Either::new(playing,