diff --git a/Cargo.toml b/Cargo.toml index 3bb50a68..e0ed14ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,9 @@ tek_config = { path = "./crates/config" } tek = { path = "./crates/app" } tek_cli = { path = "./crates/cli" } -atomic_float = { version = "1.0.0" } +atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } +bumpalo = { version = "3.19.0" } clap = { version = "4.5.4", features = [ "derive" ] } gtk = { version = "0.18.1" } konst = { version = "0.3.16", features = [ "rust_1_83" ] } diff --git a/Justfile b/Justfile index 86e1bf36..214e45e5 100644 --- a/Justfile +++ b/Justfile @@ -18,6 +18,14 @@ default: just -l bacon: bacon -s +run: + {{debug}} +run-init: + rm -rf ~/.config/tek && {{debug}} +release: + {{release}} +build-release: + time cargo build -j4 --release tui: cargo run --example tui cloc: @@ -50,15 +58,6 @@ fpush: ftpush: git push --tags -fu codeberg && git push --tags -fu origin -run: - {{debug}} -run-init: - rm -rf ~/.config/tek && {{debug}} -release: - {{release}} -build-release: - time cargo build -j4 --release - clock: {{debug}} {{name}} {{bpm}} clock clock-release: diff --git a/crates/app/tek.rs b/crates/app/tek.rs index 12f52769..1b6c200a 100644 --- a/crates/app/tek.rs +++ b/crates/app/tek.rs @@ -131,19 +131,11 @@ impl App { _ => unreachable!(), } } + pub fn update_clock (&self) { + ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) + } } -fn wrap_dialog (dialog: impl Content) -> impl Content { - Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b( - Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))).enclose(dialog)))) -} -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) } -} - - //#[dizzle::ns] //#[dizzle::ns::include(TuiOut)] //trait AppApi { diff --git a/crates/app/tek_bind.rs b/crates/app/tek_bind.rs index c0c4a7a7..b6b02d01 100644 --- a/crates/app/tek_bind.rs +++ b/crates/app/tek_bind.rs @@ -33,9 +33,10 @@ handle!(TuiIn:|self: App, input|{ #[derive(Debug, Copy, Clone)] pub enum Axis { X, Y, Z, I } -impl<'t> DslNs<'t, AppCommand> for App { - dsl_exprs!(|app| -> AppCommand { /* TODO */ }); - dsl_words!(|app| -> AppCommand { +impl<'a> DslNs<'a, AppCommand> for App {} +impl<'a> DslNsExprs<'a, AppCommand> for App {} +impl<'a> DslNsWords<'a, AppCommand> for App { + dsl_words!('a |app| -> AppCommand { "x/inc" => AppCommand::Inc { axis: Axis::X }, "x/dec" => AppCommand::Dec { axis: Axis::X }, "y/inc" => AppCommand::Inc { axis: Axis::Y }, diff --git a/crates/app/tek_data.rs b/crates/app/tek_data.rs index 2b27fbb8..ff1f6307 100644 --- a/crates/app/tek_data.rs +++ b/crates/app/tek_data.rs @@ -1,17 +1,14 @@ use crate::*; -impl<'t> DslNs<'t, Arc> for App { - dsl_exprs!(|app| -> Arc {}); - dsl_words!(|app| -> Arc {}); - fn from_literal (&self, dsl: &D) -> Perhaps> { - Ok(dsl.src()?.map(|x|x.into())) - } -} +dsl_ns!(App: Arc { + literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); +}); -impl<'t> DslNs<'t, bool> for App { - dsl_words!(|app| -> bool { - ":mode/editor" => app.project.editor.is_some(), +dsl_ns!(App: ItemTheme {}); +dsl_ns!(App: bool { + word = |app| { + ":mode/editor" => app.project.editor.is_some(), ":focused/dialog" => !matches!(app.dialog, Dialog::None), ":focused/message" => matches!(app.dialog, Dialog::Message(..)), ":focused/add_device" => matches!(app.dialog, Dialog::Device(..)), @@ -28,16 +25,11 @@ impl<'t> DslNs<'t, bool> for App { Selection::Scene(..)), ":focused/mix" => !app.editor_focused() && matches!(app.selection(), Selection::Mix), - }); - //fn from_literal (&self, dsl: &D) -> Perhaps> { - //TODO - //} -} + }; +}); -impl<'t> DslNs<'t, ItemTheme> for App {} - -impl<'t> DslNs<'t, Dialog> for App { - dsl_words!(|app| -> Dialog { +dsl_ns!(App: Dialog { + word = |app| { ":dialog/none" => Dialog::None, ":dialog/options" => Dialog::Options, ":dialog/device" => Dialog::Device(0), @@ -56,67 +48,94 @@ impl<'t> DslNs<'t, Dialog> for App { Browse::new(None).unwrap().into()), ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), Browse::new(None).unwrap().into()), - }); -} + }; +}); -impl<'t> DslNs<'t, Selection> for App { - dsl_words!(|app| -> Selection { +dsl_ns!(App: Selection { + word = |app| { ":select/scene" => app.selection().select_scene(app.tracks().len()), ":select/scene/next" => app.selection().select_scene_next(app.scenes().len()), ":select/scene/prev" => app.selection().select_scene_prev(), ":select/track" => app.selection().select_track(app.tracks().len()), ":select/track/next" => app.selection().select_track_next(app.tracks().len()), ":select/track/prev" => app.selection().select_track_prev(), - }); -} + }; +}); -impl<'t> DslNs<'t, Color> for App { - dsl_words!(|app| -> Color { +dsl_ns!(App: Color { + word = |app| { ":color/bg" => Color::Rgb(28, 32, 36), - }); - dsl_exprs!(|app| -> Color { - "g" (n: u8) => Color::Rgb(n, n, n), + }; + expr = |app| { + "g" (n: u8) => Color::Rgb(n, n, n), "rgb" (r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), - }); -} + }; +}); -impl<'t> DslNs<'t, Option> for App { - dsl_words!(|app| -> Option { +dsl_ns!(App: Option { + word = |app| { ":editor/pitch" => Some( (app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into() ) - }); -} + }; +}); -impl<'t> DslNs<'t, Option> for App { - dsl_words!(|app| -> Option { +dsl_ns!(App: Option { + word = |app| { ":selected/scene" => app.selection().scene(), ":selected/track" => app.selection().track(), - }); -} + }; +}); -impl<'t> DslNs<'t, Option>>> for App { - dsl_words!(|app| -> Option>> { +dsl_ns!(App: Option>> { + word = |app| { ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { app.scenes()[*scene].clips[*track].clone() } else { None } - }); -} + }; +}); -dsl_ns! { num |app: App| - u8; - u16 => { +dsl_ns!(App: u8 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u8) + } else { + None + }); +}); + +dsl_ns!(App: u16 { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as u16) + } else { + None + }); + word = |app| { ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), }; - usize => { +}); + +dsl_ns!(App: usize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as usize) + } else { + None + }); + word = |app| { ":scene-count" => app.scenes().len(), ":track-count" => app.tracks().len(), ":device-kind" => app.dialog.device_kind().unwrap_or(0), ":device-kind/next" => app.dialog.device_kind_next().unwrap_or(0), ":device-kind/prev" => app.dialog.device_kind_prev().unwrap_or(0), }; - isize; -} +}); + +dsl_ns!(App: isize { + literal = |dsl|Ok(if let Some(src) = dsl.src()? { + Some(to_number(src)? as isize) + } else { + None + }); +}); diff --git a/crates/app/tek_view.rs b/crates/app/tek_view.rs index 880bdf6f..9804ba0e 100644 --- a/crates/app/tek_view.rs +++ b/crates/app/tek_view.rs @@ -1,206 +1,282 @@ use crate::*; -impl Content for App { - fn content (&self) -> impl Render + '_ { - 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 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 + 't>; - -fn render_dsl <'t> ( - state: &'t impl DslNs<'t, TuiBox<'t>>, - src: &str -) -> TuiBox<'t> { - let err: Option> = 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) => 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(""); - let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); - 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 (&self, dsl: &D) -> Perhaps> { - 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 (&self, dsl: &D) -> Perhaps> { - 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::::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::::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(""); + let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); + 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)|{ + //Fixed::y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Draw)|{ //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, diff --git a/crates/config/config.rs b/crates/config/config.rs index 3b23f99d..c9bb65b5 100644 --- a/crates/config/config.rs +++ b/crates/config/config.rs @@ -74,7 +74,7 @@ impl Config { } } pub fn init (&mut self) -> Usually<()> { - self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; + self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?; Ok(()) } pub fn init_file ( @@ -82,7 +82,7 @@ impl Config { ) -> Usually<()> { if self.dirs.find_config_file(path).is_none() { println!("Creating {path:?}"); - std::fs::write(self.dirs.place_config_file(path)?, defaults); + std::fs::write(self.dirs.place_config_file(path)?, defaults)?; } Ok(if let Some(path) = self.dirs.find_config_file(path) { println!("Loading {path:?}"); diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 56ac336b..0963df93 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -13,12 +13,6 @@ def_sizes_iter!(InputsSizes => MidiInput); def_sizes_iter!(OutputsSizes => MidiOutput); def_sizes_iter!(PortsSizes => Arc, [Connect]); -pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl Content { - let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); - let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); - Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) -} - #[derive(Default, Debug)] pub struct Arrangement { /// Project name. pub name: Arc, diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 364ffab8..3f002a11 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -241,34 +241,38 @@ impl Track { pub trait HasTrack { fn track (&self) -> Option<&Track>; fn track_mut (&mut self) -> Option<&mut Track>; - fn view_midi_ins_status (&self, theme: ItemTheme) -> impl Content { + fn view_midi_ins_status (&self, theme: ItemTheme) -> impl Draw { self.track().map(|track|{ let ins = track.sequencer.midi_ins.len() as u16; - Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + ins, border.enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "), Map::south(1, ||track.sequencer.midi_ins.iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content { + fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw { self.track().map(|track|{ let outs = track.sequencer.midi_outs.len() as u16; - Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + outs, border.enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "), Map::south(1, ||track.sequencer.midi_outs.iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content { + fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { self.track().and_then(|track|track.devices.get(0)).map(|device|{ let ins = device.audio_ins().len() as u16; - Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + ins, border.enclose( Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "), Map::south(1, ||device.audio_ins().iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) } - fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content { + fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { self.track().and_then(|track|track.devices.last()).map(|device|{ let outs = device.audio_outs().len() as u16; - Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose( + let border = Outer(true, Style::default().fg(Tui::g(96))); + Fixed::xy(20, 1 + outs, border.enclose( Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "), Map::south(1, ||device.audio_outs().iter(), |port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))}) @@ -276,10 +280,10 @@ pub trait HasTrack { } impl Track { - pub fn per <'a, T: Content + 'a, U: TracksSizes<'a>> ( + pub fn per <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - ) -> impl Content + 'a { + ) -> impl Draw + Layout + 'a { Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; @@ -290,10 +294,10 @@ impl Track { } } -pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( +pub(crate) fn per_track_top <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + Layout + 'a { Align::x(Tui::bg(Reset, Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{ let width = (x2 - x1) as u16; @@ -303,37 +307,39 @@ pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( callback(index, track))))}))) } -pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( +pub(crate) fn per_track <'a, T: Draw + Layout + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + 'a { per_track_top(tracks, move|index, track|Fill::y(Align::y(callback(index, track)))) } pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a -) -> impl Content + 'a { +) -> impl Draw + Layout + 'a { Map::new(iter, move|( _index, name, connections, y, y2 ): (usize, &'a Arc, &'a [Connect], usize, usize), _| map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" 󰣲 ", name))))), Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1, Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, &connect.info))))))))) } -pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( - fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a -) -> impl Content + 'a { - Map::new(iter, move|( - _index, _name, connections, y, y2 - ): (usize, &'a Arc, &'a [Connect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), - Map::new(||connections.iter(), move|_conn, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) -} +//pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( + //fg: Color, bg: Color, iter: &mut impl Iterator, &'a [Connect], usize, usize)> +//) -> impl Draw + Layout + 'a { + //Fill::xy(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter { + //to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s( + //Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w(&"▞▞▞▞ ▞▞▞▞"))))), + //Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() { + //to.place(&map_south(index as u16, 1, Fill::x(Align::w(Tui::bold(false, + //wrap(bg, fg, Fill::x(&""))))))) + //}) + //))) + //})) +//} //track_scroll: Fill::x(Fixed::y(1, ScrollbarH { //offset: arrangement.track_scroll, //length: h_tracks_area as usize, diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index 3daf8ed0..dcc87a92 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,38 +1,55 @@ use crate::*; +pub(crate) fn wrap ( + bg: Color, fg: Color, content: impl Draw + Layout +) -> impl Draw + Layout { + let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐"))); + let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); + Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) +} + +type Add<'a> = &'a dyn FnMut(&dyn Draw); + impl Arrangement { - pub fn view_inputs <'a> (&'a self, _theme: ItemTheme) -> impl Content + 'a { - Stack::south(move|add|{ - add(&Fixed::y(1, - Bsp::e(Fixed::x(20, Align::w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))), - Bsp::w(Fixed::x(4, button_2("I", "+", false)), - Stack::east(move|add|{ - for (_index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!( - Either(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "), - Either(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "), - Either(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "), - ))))) - } - }))))); - for (_index, port) in self.midi_ins().iter().enumerate() { - add(&Fixed::y(1, Bsp::e( - Fixed::x(20, Align::w(Bsp::e(" ● ", - Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))), - Bsp::w(Fixed::x(4, ()), - Stack::east(move|add|{ - for (_index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Tui::bg(track.color.darker.rgb, Align::w(Fixed::x(track.width as u16, row!( - Either(track.sequencer.monitoring, Tui::fg(Green, " ● "), " · "), - Either(track.sequencer.recording, Tui::fg(Red, " ● "), " · "), - Either(track.sequencer.overdub, Tui::fg(Yellow, " ● "), " · "), - ))))) - } - }))))); + + pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + Layout + '_ { + Bsp::s(self.view_inputs_header(), Thunk::new(|to: &mut TuiOut|{ + for port in self.midi_ins().iter() { + to.place(&self.view_inputs_row(port)) } - }) + })) } - pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + + fn view_inputs_header (&self) -> impl Draw + Layout + '_ { + Fixed::y(1, Bsp::e( + Fixed::x(20, Align::w( + button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))), + Bsp::w(Fixed::x(4, button_2("I", "+", false)), + Thunk::new(|to: &mut TuiOut|for (_index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Tui::bg(track.color.dark.rgb, Align::w(Fixed::x(track.width as u16, row!( + Either::new(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "), + Either::new(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "), + Either::new(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "), + ))))) + })))) + } + + fn view_inputs_row (&self, port: &MidiInput) -> impl Draw + Layout { + Fixed::y(1, Bsp::e( + Fixed::x(20, Align::w( + Bsp::e(" ● ", Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))), + Bsp::w(Fixed::x(4, ()), Thunk::new(|to: &mut TuiOut|{ + for (_index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Tui::bg(track.color.darker.rgb, Align::w(Fixed::x(track.width as u16, row!( + Either::new(track.sequencer.monitoring, Tui::fg(Green, " ● "), " · "), + Either::new(track.sequencer.recording, Tui::fg(Red, " ● "), " · "), + Either::new(track.sequencer.overdub, Tui::fg(Yellow, " ● "), " · "), + ))))) + } + })))) + } + + pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 1; for output in self.midi_outs().iter() { h += 1 + output.connections.len(); @@ -40,59 +57,68 @@ impl Arrangement { let h = h as u16; let list = Bsp::s( Fixed::y(1, Fill::x(Align::w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))), - Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add|{ + Fixed::y(h - 1, Fill::xy(Align::nw(Thunk::new(|to: &mut TuiOut|{ for (_index, port) in self.midi_outs().iter().enumerate() { - add(&Fixed::y(1,Fill::x(Bsp::e( + to.place(&Fixed::y(1,Fill::x(Bsp::e( Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.port_name())))), Fill::x(Align::e(format!("{}/{} ", port.port().get_connections().len(), port.connections.len()))))))); for (index, conn) in port.connections.iter().enumerate() { - add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info()))))); + to.place(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info()))))); } } }))))); - Fixed::y(h, Self::view_track_row_section(theme, list, button_2("O", "+", false), + Fixed::y(h, view_track_row_section(theme, list, button_2("O", "+", false), Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ + Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), - Stack::south(move|add|{ - add(&Fixed::y(1, Align::w(Bsp::e( - Either(true, Tui::fg(Green, "play "), "play "), - Either(false, Tui::fg(Yellow, "solo "), "solo "), + to.place(&Fixed::x(self.track_width(index, track), + Thunk::new(|to: &mut TuiOut|{ + to.place(&Fixed::y(1, Align::w(Bsp::e( + Either::new(true, Tui::fg(Green, "play "), "play "), + Either::new(false, Tui::fg(Yellow, "solo "), "solo "), )))); for (_index, port) in self.midi_outs().iter().enumerate() { - add(&Fixed::y(1, Align::w(Bsp::e( - Either(true, Tui::fg(Green, " ● "), " · "), - Either(false, Tui::fg(Yellow, " ● "), " · "), + to.place(&Fixed::y(1, Align::w(Bsp::e( + Either::new(true, Tui::fg(Green, " ● "), " · "), + Either::new(false, Tui::fg(Yellow, " ● "), " · "), )))); for (_index, _conn) in port.connections.iter().enumerate() { - add(&Fixed::y(1, Fill::x(""))); + to.place(&Fixed::y(1, Fill::x(""))); } }})))}})))))) } - pub fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content + 'a { + + pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 2u16; for track in self.tracks().iter() { h = h.max(track.devices.len() as u16 * 2); } - Self::view_track_row_section( - theme, + view_track_row_section(theme, button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h + 1, - Tui::bg(track.color.dark.rgb, Align::nw(Map::south(2, move||0..h, - |_, _index|Fixed::xy(track.width as u16, 2, - Tui::fg_bg( - ItemTheme::G[32].lightest.rgb, - ItemTheme::G[32].dark.rgb, - Align::nw(format!(" · {}", "--"))))))))); - } + Thunk::new(move|to: &mut TuiOut|for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::xy(self.track_width(index, track), h + 1, + Tui::bg(track.color.dark.rgb, Align::nw(Map::south(2, move||0..h, + |_, _index|Fixed::xy(track.width as u16, 2, + Tui::fg_bg( + ItemTheme::G[32].lightest.rgb, + ItemTheme::G[32].dark.rgb, + Align::nw(format!(" · {}", "--"))))))))); })) } + +} + +fn view_track_row_section ( + _theme: ItemTheme, + button: impl Draw + Layout, + button_add: impl Draw + Layout, + content: impl Draw + Layout, +) -> impl Draw + Layout { + Bsp::w(Fill::y(Fixed::x(4, Align::nw(button_add))), + Bsp::e(Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)))) } pub trait HasClipsSize { @@ -135,21 +161,12 @@ pub trait TracksView: } }) } - fn view_track_row_section ( - _theme: ItemTheme, - button: impl Content, - button_add: impl Content, - content: impl Content - ) -> impl Content { - Bsp::w(Fill::y(Fixed::x(4, Align::nw(button_add))), - Bsp::e(Fixed::x(20, Fill::y(Align::nw(button))), Fill::xy(Align::c(content)))) - } - fn view_track_header <'a, T: Content> ( - &'a self, theme: ItemTheme, content: T - ) -> impl Content { + fn view_track_header <'a> ( + &'a self, theme: ItemTheme, content: impl Draw + Layout + ) -> impl Draw + Layout { Fixed::x(12, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content)))) } - fn view_track_names (&self, theme: ItemTheme) -> impl Content { + fn view_track_names (&self, theme: ItemTheme) -> impl Draw + Layout { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); @@ -161,10 +178,10 @@ pub trait TracksView: let button_2 = Bsp::s( button_2("T", "+", false), button_2("S", "+", false)); - Self::view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, - Fixed::y(2, Fill::x(Stack::east(move|add|{ + view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb, + Fixed::y(2, Thunk::new(|to: &mut TuiOut|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), + to.place(&Fixed::x(self.track_width(index, track), Tui::bg(if selected.track() == Some(index) { track.color.light.rgb } else { @@ -172,55 +189,49 @@ pub trait TracksView: }, Bsp::s(Fill::x(Align::nw(Bsp::e( format!("·t{index:02} "), Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name)) - ))), ""))) ); - } - }))))) + ))), ""))) );}})))) } - fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Content { - Self::view_track_row_section(theme, - Bsp::s( - Fill::x(Align::w(button_2("o", "utput", false))), - Fill::xy(Stack::south(|add|{ - for port in self.midi_outs().iter() { - add(&Fill::x(Align::w(port.port_name()))); - } - }))), + fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw + Layout { + view_track_row_section(theme, + Bsp::s(Fill::x(Align::w(button_2("o", "utput", false))), + Thunk::new(|to: &mut TuiOut|for port in self.midi_outs().iter() { + to.place(&Fill::x(Align::w(port.port_name()))); + })), button_2("O", "+", false), - Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::x(self.track_width(index, track), - Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), - |port, index|Tui::fg(Rgb(255, 255, 255), - Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( - format!("·o{index:02} {}", port.port_name()))))))))))); - } - }))))) + Tui::bg(theme.darker.rgb, Align::w(Thunk::new(|to: &mut TuiOut|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::x(self.track_width(index, track), + Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(), + |port, index|Tui::fg(Rgb(255, 255, 255), + Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w( + format!("·o{index:02} {}", port.port_name())))))))))));}})))) } - fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content { + fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw + Layout { let mut h = 0u16; for track in self.tracks().iter() { h = h.max(track.sequencer.midi_ins.len() as u16); } - Self::view_track_row_section( - theme, + view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), - Tui::bg(theme.darker.rgb, Align::w(Fill::x( - Stack::east(move|add|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - add(&Fixed::xy(self.track_width(index, track), h + 1, - Align::nw(Bsp::s( - Tui::bg(track.color.base.rgb, - Fill::x(Align::w(row!( - Either(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "), - Either(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "), - Either(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "), - )))), - Map::south(1, ||track.sequencer.midi_ins.iter(), - |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, - Fill::x(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); - }}))))) + Tui::bg(theme.darker.rgb, Align::w(Thunk::new(move|to: &mut TuiOut|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + to.place(&Fixed::xy(self.track_width(index, track), h + 1, + Align::nw(Bsp::s( + Tui::bg(track.color.base.rgb, + Fill::x(Align::w(row!( + Either::new(track.sequencer.monitoring, + Tui::fg(Green, "●mon "), "·mon "), + Either::new(track.sequencer.recording, + Tui::fg(Red, "●rec "), "·rec "), + Either::new(track.sequencer.overdub, + Tui::fg(Yellow, "●dub "), "·dub "), + )))), + Map::south(1, ||track.sequencer.midi_ins.iter(), + |port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb, + Fill::x(Align::w(format!("·i{index:02} {}", port.port_name()))))))))); + } + })))) } fn track_width (&self, index: usize, track: &Track) -> u16 { track.width as u16 @@ -259,14 +270,12 @@ pub trait ScenesView: } }) } - fn view_scenes_names (&self) -> impl Content { - Fixed::x(20, Stack::south(move|add|{ - for (index, scene, ..) in self.scenes_with_sizes() { - add(&self.view_scene_name(index, scene)); - } + fn view_scenes_names (&self) -> impl Draw + Layout { + Fixed::x(20, Thunk::new(|to: &mut TuiOut|for (index, scene, ..) in self.scenes_with_sizes() { + to.place(&self.view_scene_name(index, scene)); })) } - fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content { + fn view_scene_name <'a> (&'a self, index: usize, scene: &'a Scene) -> impl Draw + Layout + 'a { let h = if self.selection().scene() == Some(index) && let Some(_editor) = self.editor() { 7 } else { @@ -277,15 +286,13 @@ pub trait ScenesView: } else { scene.color.base.rgb }; - Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s( - Fill::x(Align::w(Bsp::e( - format!("·s{index:02} "), - Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &scene.name)) - ))), - When(self.selection().scene() == Some(index) && self.is_editing(), - Fill::xy(Align::nw(Bsp::s( - self.editor().as_ref().map(|e|e.clip_status()), - self.editor().as_ref().map(|e|e.edit_status()))))))))) + let a = Fill::x(Align::w(Bsp::e(format!("·s{index:02} "), + Tui::fg(Tui::g(255), Tui::bold(true, &scene.name))))); + let b = When(self.selection().scene() == Some(index) && self.is_editing(), + Fill::xy(Align::nw(Bsp::s( + self.editor().as_ref().map(|e|e.clip_status()), + self.editor().as_ref().map(|e|e.edit_status()))))); + Fixed::xy(20, h, Tui::bg(bg, Align::nw(Bsp::s(a, b)))) } } @@ -297,70 +304,68 @@ pub trait ClipsView: Sync { fn view_scenes_clips <'a> (&'a self) - -> impl Content + 'a + -> impl Draw + 'a { self.clips_size().of(Fill::xy(Bsp::a( Fill::xy(Align::se(Tui::fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))), - Stack::::east(move|column|{ - for (track_index, track, _, _) in self.tracks_with_sizes() { - //column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp")))); - column(&Fixed::x( - track.width as u16, - Fill::y(self.view_track_clips(track_index, track)) - )) - } + Thunk::new(|to: &mut TuiOut|for ( + track_index, track, _, _ + ) in self.tracks_with_sizes() { + to.place(&Fixed::x(track.width as u16, + Fill::y(self.view_track_clips(track_index, track)))) })))) } - fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Content + 'a { - Stack::south(move|cell|{ - for (scene_index, scene, ..) in self.scenes_with_sizes() { - let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { - let clip = clip.read().unwrap(); - (format!(" ⏹ {}", &clip.name).into(), clip.color) - } else { - (" ⏹ -- ".into(), ItemTheme::G[32]) - }; - let fg = theme.lightest.rgb; - let mut outline = theme.base.rgb; - let bg = if self.selection().track() == Some(track_index) - && self.selection().scene() == Some(scene_index) - { - outline = theme.lighter.rgb; - theme.light.rgb - } else if self.selection().track() == Some(track_index) - || self.selection().scene() == Some(scene_index) - { - outline = theme.darkest.rgb; - theme.base.rgb - } else { - theme.dark.rgb - }; - let w = if self.selection().track() == Some(track_index) - && let Some(editor) = self.editor () - { - editor.width().max(24).max(track.width) - } else { - track.width - } as u16; - let y = if self.selection().scene() == Some(scene_index) - && let Some(editor) = self.editor () - { - editor.height().max(12) - } else { - Self::H_SCENE - } as u16; - cell(&Fixed::xy(w, y, Bsp::b( - Fill::xy(Outer(true, Style::default().fg(outline))), - Fill::xy(Bsp::b( - Bsp::b( - Tui::fg_bg(outline, bg, Fill::xy("")), - Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), - ), - Fill::xy(When(self.selection().track() == Some(track_index) - && self.selection().scene() == Some(scene_index) - && self.is_editing(), self.editor()))))))); - } + fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Draw + 'a { + Thunk::new(move|to: &mut TuiOut|for ( + scene_index, scene, .. + ) in self.scenes_with_sizes() { + let (name, theme): (Arc, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) { + let clip = clip.read().unwrap(); + (format!(" ⏹ {}", &clip.name).into(), clip.color) + } else { + (" ⏹ -- ".into(), ItemTheme::G[32]) + }; + let fg = theme.lightest.rgb; + let mut outline = theme.base.rgb; + let bg = if self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + { + outline = theme.lighter.rgb; + theme.light.rgb + } else if self.selection().track() == Some(track_index) + || self.selection().scene() == Some(scene_index) + { + outline = theme.darkest.rgb; + theme.base.rgb + } else { + theme.dark.rgb + }; + let w = if self.selection().track() == Some(track_index) + && let Some(editor) = self.editor () + { + editor.width().max(24).max(track.width) + } else { + track.width + } as u16; + let y = if self.selection().scene() == Some(scene_index) + && let Some(editor) = self.editor () + { + editor.height().max(12) + } else { + Self::H_SCENE + } as u16; + + to.place(&Fixed::xy(w, y, Bsp::b( + Fill::xy(Outer(true, Style::default().fg(outline))), + Fill::xy(Bsp::b( + Bsp::b( + Tui::fg_bg(outline, bg, Fill::xy("")), + Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Tui::bold(true, name)))), + ), + Fill::xy(When(self.selection().track() == Some(track_index) + && self.selection().scene() == Some(scene_index) + && self.is_editing(), self.editor()))))))); }) } } diff --git a/crates/device/src/clock/clock_view.rs b/crates/device/src/clock/clock_view.rs index b839a3ca..287eec9f 100644 --- a/crates/device/src/clock/clock_view.rs +++ b/crates/device/src/clock/clock_view.rs @@ -6,7 +6,7 @@ pub fn view_transport ( bpm: Arc>, beat: Arc>, time: Arc>, -) -> impl Content { +) -> impl Draw + Layout { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(button_play_pause(play))), @@ -23,7 +23,7 @@ pub fn view_status ( sr: Arc>, buf: Arc>, lat: Arc>, -) -> impl Content { +) -> impl Draw + Layout { let theme = ItemTheme::G[96]; Tui::bg(Black, row!(Bsp::a( Fill::xy(Align::w(sel.map(|sel|FieldH(theme, "Selected", sel)))), @@ -35,18 +35,18 @@ pub fn view_status ( ))) } -pub(crate) fn button_play_pause (playing: bool) -> impl Content { +pub(crate) fn button_play_pause (playing: bool) -> impl Draw + Layout { let compact = true;//self.is_editing(); Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) }, Either::new(compact, - Thunk::new(move||Fixed::x(9, Either::new(playing, + Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::x(9, Either::new(playing, Tui::fg(Rgb(0, 255, 0), " PLAYING "), Tui::fg(Rgb(255, 128, 0), " STOPPED "))) - ), - Thunk::new(move||Fixed::x(5, Either::new(playing, + )), + Thunk::new(move|to: &mut TuiOut|to.place(&Fixed::x(5, Either::new(playing, Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)), Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",)))) - ) + )) ) ) } @@ -93,7 +93,7 @@ impl ViewCache { //} //pub fn scene_add (cache: &Arc>, scene: usize, scenes: usize, is_editing: bool) - //-> impl Content + //-> impl Draw //{ //let data = (scene, scenes); //cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1)); @@ -133,7 +133,7 @@ impl ViewCache { } } - //pub fn view_h2 (&self) -> impl Content { + //pub fn view_h2 (&self) -> impl Draw { //let cache = self.project.clock.view_cache.clone(); //let cache = cache.read().unwrap(); //add(&Fixed::x(15, Align::w(Bsp::s( diff --git a/crates/device/src/editor/editor_view.rs b/crates/device/src/editor/editor_view.rs index 0a11c72c..afcf904e 100644 --- a/crates/device/src/editor/editor_view.rs +++ b/crates/device/src/editor/editor_view.rs @@ -1,5 +1,7 @@ use crate::*; +tui_layout!(|self: MidiEditor, to|self.content().layout(to)); +tui_draw!(|self: MidiEditor, to|self.content().draw(to)); content!(TuiOut: |self: MidiEditor| { self.autoscroll(); //self.autozoom(); @@ -8,7 +10,7 @@ content!(TuiOut: |self: MidiEditor| { impl MidiEditor { - pub fn clip_status (&self) -> impl Content + '_ { + pub fn clip_status (&self) -> impl Draw + Layout + '_ { let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.name.clone(), clip.length, clip.looped) } else { (ItemTheme::G[64], String::new().into(), 0, false) }; @@ -25,7 +27,7 @@ impl MidiEditor { )) } - pub fn edit_status (&self) -> impl Content + '_ { + pub fn edit_status (&self) -> impl Draw + Layout + '_ { let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.length) } else { (ItemTheme::G[64], 0) }; diff --git a/crates/device/src/editor/editor_view_h.rs b/crates/device/src/editor/editor_view_h.rs index bf04f16f..e461e738 100644 --- a/crates/device/src/editor/editor_view_h.rs +++ b/crates/device/src/editor/editor_view_h.rs @@ -23,7 +23,7 @@ has!(Measure:|self:PianoHorizontal|self.size); impl PianoHorizontal { pub fn new (clip: Option<&Arc>>) -> Self { let size = Measure::new(); - let mut range = MidiRangeModel::from((12, true)); + let mut range = MidiRangeModel::from((12, true)); range.time_axis = size.x.clone(); range.note_axis = size.y.clone(); let piano = Self { @@ -46,6 +46,8 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } +tui_layout!(|self: PianoHorizontal, to|self.content().layout(to)); +tui_draw!(|self: PianoHorizontal, to|self.content().draw(to)); content!(TuiOut:|self: PianoHorizontal| Bsp::s( Bsp::e( Fixed::x(5, format!("{}x{}", self.size.w(), self.size.h())), @@ -139,12 +141,12 @@ impl PianoHorizontal { } } - fn notes (&self) -> impl Content { + fn notes (&self) -> impl Draw { let time_start = self.get_time_start(); let note_lo = self.get_note_lo(); let note_hi = self.get_note_hi(); let buffer = self.buffer.clone(); - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let source = buffer.read().unwrap(); let [x0, y0, w, _h] = to.area().xywh(); //if h as usize != note_axis { @@ -170,7 +172,7 @@ impl PianoHorizontal { } }) } - fn cursor (&self) -> impl Content { + fn cursor (&self) -> impl Draw + Layout { let note_hi = self.get_note_hi(); let note_lo = self.get_note_lo(); let note_pos = self.get_note_pos(); @@ -179,7 +181,7 @@ impl PianoHorizontal { let time_start = self.get_time_start(); let time_zoom = self.get_time_zoom(); let style = Some(Style::default().fg(self.color.lightest.rgb)); - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let [x0, y0, w, _] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { if note == note_pos { @@ -201,7 +203,7 @@ impl PianoHorizontal { } }) } - fn keys (&self) -> impl Content { + fn keys (&self) -> impl Draw + Layout { let state = self; let color = state.color; let note_lo = state.get_note_lo(); @@ -210,7 +212,7 @@ impl PianoHorizontal { let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(Tui::g(255))); let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.rgb).bold()); - Fill::y(Fixed::x(self.keys_width, ThunkRender::new(move|to: &mut TuiOut|{ + Fill::y(Fixed::x(self.keys_width, Thunk::new(move|to: &mut TuiOut|{ let [x, y0, _w, _h] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { to.blit(&to_key(note), x, screen_y, key_style); @@ -225,8 +227,8 @@ impl PianoHorizontal { } }))) } - fn timeline (&self) -> impl Content + '_ { - Fill::x(Fixed::y(1, ThunkRender::new(move|to: &mut TuiOut|{ + fn timeline (&self) -> impl Draw + Layout + '_ { + Fill::x(Fixed::y(1, Thunk::new(move|to: &mut TuiOut|{ let [x, y, w, _h] = to.area(); let style = Some(Style::default().dim()); let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); diff --git a/crates/device/src/editor/editor_view_v.rs b/crates/device/src/editor/editor_view_v.rs index 493c6de9..2913f04a 100644 --- a/crates/device/src/editor/editor_view_v.rs +++ b/crates/device/src/editor/editor_view_v.rs @@ -1,3 +1,5 @@ +//! TODO + use crate::*; pub struct OctaveVertical { @@ -24,7 +26,7 @@ impl OctaveVertical { } impl Content for OctaveVertical { - fn content (&self) -> impl Render { + fn content (&self) -> impl Draw + Layout + '_ { row!( Tui::fg_bg(self.color(0), self.color(1), "▙"), Tui::fg_bg(self.color(2), self.color(3), "▙"), diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 22edce2e..56df9abf 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -14,7 +14,7 @@ pub(crate) use std::error::Error; pub(crate) use std::ffi::OsString; pub(crate) use ::tengri::{from, has, maybe_has, Usually, Perhaps, Has, MaybeHas}; -pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}}; +pub(crate) use ::tengri::{dsl::*, input::*, output::{*, Layout}, tui::{*, ratatui::prelude::*}}; pub(crate) use ::tek_engine::*; pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage}; pub(crate) use ::tek_engine::jack::{Control, ProcessScope, RawMidi}; @@ -47,45 +47,6 @@ macro_rules! def_sizes_iter { #[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*; #[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*; -pub fn button_2 <'a> ( - key: impl Content + 'a, label: impl Content + 'a, editing: bool, -) -> impl Content + 'a { - let key = Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e( - Tui::fg(Tui::g(0), "▐"), - Bsp::e(key, Tui::fg(Tui::g(96), "▐")) - )); - let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label)); - Tui::bold(true, Bsp::e(key, label)) -} - -pub fn button_3 <'a, K, L, V> ( - key: K, - label: L, - value: V, - editing: bool, -) -> impl Content + 'a where - K: Content + 'a, - L: Content + 'a, - V: Content + 'a, -{ - let key = Tui::fg_bg(Tui::orange(), Tui::g(0), - Bsp::e(Tui::fg(Tui::g(0), "▐"), Bsp::e(key, Tui::fg(if editing { - Tui::g(128) - } else { - Tui::g(96) - }, "▐")))); - let label = Bsp::e( - When::new(!editing, Bsp::e( - Tui::fg_bg(Tui::g(255), Tui::g(96), label), - Tui::fg_bg(Tui::g(128), Tui::g(96), "▐"), - )), - Bsp::e( - Tui::fg_bg(Tui::g(224), Tui::g(128), value), - Tui::fg_bg(Tui::g(128), Reset, "▌"), - )); - Tui::bold(true, Bsp::e(key, label)) -} - pub fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { diff --git a/crates/device/src/lv2/lv2_tui.rs b/crates/device/src/lv2/lv2_tui.rs index 0c76a1c9..8bfad8a2 100644 --- a/crates/device/src/lv2/lv2_tui.rs +++ b/crates/device/src/lv2/lv2_tui.rs @@ -1,8 +1,8 @@ use crate::*; use super::*; -impl Content for Lv2 { - fn render (&self, to: &mut TuiOut) { +impl Draw for Lv2 { + fn draw (&self, to: &mut TuiOut) { let area = to.area(); let [x, y, _, height] = area; let mut width = 20u16; diff --git a/crates/device/src/meter.rs b/crates/device/src/meter.rs index 106975c7..48f2c774 100644 --- a/crates/device/src/meter.rs +++ b/crates/device/src/meter.rs @@ -9,8 +9,8 @@ pub enum MeteringMode { #[derive(Debug, Default, Clone)] pub struct Log10Meter(pub f32); - -render!(TuiOut: |self: Log10Meter, to| { +tui_layout!(|self: Log10Meter, to|to); +tui_draw!(|self: Log10Meter, to| { let [x, y, w, h] = to.area(); let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs())); let v = (signal * h as f32 / 100.0).ceil() as u16; @@ -32,7 +32,7 @@ pub fn to_log10 (samples: &[f32]) -> f32 { #[derive(Debug, Default, Clone)] pub struct RmsMeter(pub f32); -render!(TuiOut: |self: RmsMeter, to| { +draw!(TuiOut: |self: RmsMeter, to| { let [x, y, w, h] = to.area(); let signal = f32::max(0.0, f32::min(100.0, self.0.abs())); let v = (signal * h as f32).ceil() as u16; @@ -53,7 +53,7 @@ pub fn to_rms (samples: &[f32]) -> f32 { (sum / samples.len() as f32).sqrt() } -pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a { +pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw + 'a { col!( FieldH(ItemTheme::G[128], label, format!("{:>+9.3}", value)), Fixed::xy(if value >= 0.0 { 13 } @@ -74,7 +74,7 @@ pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Content + 'a else { Green }, ()))) } -pub fn view_meters (values: &[f32;2]) -> impl Content + use<'_> { +pub fn view_meters (values: &[f32;2]) -> impl Draw + use<'_> { let left = format!("L/{:>+9.3}", values[0]); let right = format!("R/{:>+9.3}", values[1]); Bsp::s(left, right) diff --git a/crates/device/src/sampler/sampler_browse.rs b/crates/device/src/sampler/sampler_browse.rs index f239767c..9d9bf491 100644 --- a/crates/device/src/sampler/sampler_browse.rs +++ b/crates/device/src/sampler/sampler_browse.rs @@ -143,8 +143,8 @@ fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { Ok((subdirs, files)) } -impl Content for AddSampleModal { - fn render (&self, _to: &mut TuiOut) { +impl Draw for AddSampleModal { + fn draw (&self, _to: &mut TuiOut) { todo!() //let area = to.area(); //to.make_dim(); diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index 90999e2c..20c4ab13 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -2,28 +2,31 @@ use crate::*; impl Sampler { - pub fn view_grid (&self) -> impl Content + use<'_> { + pub fn view_grid (&self) -> impl Draw + Layout + use<'_> { let cells_x = 8u16; let cells_y = 8u16; let cell_width = 10u16; let cell_height = 2u16; //let width = cells_x * cell_width; //let height = cells_y * cell_height; - let cols = Map::east( - cell_width, - move||0..cells_x, - move|x, _|Map::south( - cell_height, - move||0..cells_y, - move|y, _|self.view_grid_cell("........", x, y, cell_width, cell_height) - ) - ); - cols + //let cols = Map::east( + //cell_width, + //move||0..cells_x, + //move|x, _|Map::south( + //cell_height, + //move||0..cells_y, + //move|y, _|self.view_grid_cell("........", x, y, cell_width, cell_height) + //) + //); + //cols + //Thunk::new(|to: &mut TuiOut|{ + //}) + "TODO" } pub fn view_grid_cell <'a> ( &'a self, name: &'a str, x: u16, y: u16, w: u16, h: u16 - ) -> impl Content + use<'a> { + ) -> impl Draw + use<'a> { let cursor = self.cursor(); let hi_fg = Color::Rgb(64, 64, 64); let hi_bg = if y == 0 { Color::Reset } else { Color::Rgb(64, 64, 64) /*prev*/ }; @@ -55,7 +58,7 @@ impl Sampler { pub fn view_list <'a, T: NotePoint + NoteRange> ( &'a self, compact: bool, editor: &T - ) -> impl Content + 'a { + ) -> impl Draw + 'a { let note_lo = editor.get_note_lo(); let note_pt = editor.get_note_pos(); let note_hi = editor.get_note_hi(); @@ -97,7 +100,7 @@ impl Sampler { } } - pub fn view_sample (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample (&self, note_pt: usize) -> impl Draw + use<'_> { Outer(true, Style::default().fg(Tui::g(96))) .enclose(Fill::xy(draw_viewer(if let Some((_, Some(sample))) = &self.recording { Some(sample) @@ -108,7 +111,7 @@ impl Sampler { }))) } - pub fn view_sample_info (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample_info (&self, note_pt: usize) -> impl Draw + use<'_> { Fill::x(Fixed::y(1, draw_info(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { @@ -118,7 +121,7 @@ impl Sampler { }))) } - pub fn view_sample_status (&self, note_pt: usize) -> impl Content + use<'_> { + pub fn view_sample_status (&self, note_pt: usize) -> impl Draw + use<'_> { Fixed::x(20, draw_info_v(if let Some((_, Some(sample))) = &self.recording { Some(sample) } else if let Some(sample) = &self.mapped[note_pt] { @@ -128,20 +131,20 @@ impl Sampler { })) } - pub fn view_status (&self, index: usize) -> impl Content { + pub fn view_status (&self, index: usize) -> impl Draw { draw_status(self.mapped[index].as_ref()) } - pub fn view_meters_input (&self) -> impl Content + use<'_> { + pub fn view_meters_input (&self) -> impl Draw + use<'_> { draw_meters(&self.input_meters) } - pub fn view_meters_output (&self) -> impl Content + use<'_> { + pub fn view_meters_output (&self) -> impl Draw + use<'_> { draw_meters(&self.output_meters) } } -fn draw_meters (meters: &[f32]) -> impl Content + use<'_> { +fn draw_meters (meters: &[f32]) -> impl Draw + use<'_> { Tui::bg(Black, Fixed::x(2, Map::east(1, ||meters.iter(), |value, _index|{ Fill::y(RmsMeter(*value)) }))) @@ -163,9 +166,9 @@ fn draw_list_item (sample: &Option>>) -> String { } } -fn draw_viewer (sample: Option<&Arc>>) -> impl Content + use<'_> { +fn draw_viewer (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { let min_db = -64.0; - ThunkRender::new(move|to: &mut TuiOut|{ + Thunk::new(move|to: &mut TuiOut|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; if let Some(sample) = &sample { @@ -218,41 +221,41 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Content + }) } -fn draw_info (sample: Option<&Arc>>) -> impl Content + use<'_> { - When(sample.is_some(), Thunk::new(move||{ +fn draw_info (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { + When(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; - row!( + to.place(&row!( FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), FieldH(theme, "Start", format!("{:<8}", sample.start)), FieldH(theme, "End", format!("{:<8}", sample.end)), FieldH(theme, "Trans", "0"), FieldH(theme, "Gain", format!("{}", sample.gain)), - ) + )) })) } -fn draw_info_v (sample: Option<&Arc>>) -> impl Content + use<'_> { - Either(sample.is_some(), Thunk::new(move||{ +fn draw_info_v (sample: Option<&Arc>>) -> impl Draw + Layout + use<'_> { + Either::new(sample.is_some(), Thunk::new(move|to: &mut TuiOut|{ let sample = sample.unwrap().read().unwrap(); let theme = sample.color; - Fixed::x(20, col!( + to.place(&Fixed::x(20, col!( Fill::x(Align::w(FieldH(theme, "Name ", format!("{:<10}", sample.name.clone())))), Fill::x(Align::w(FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())))), Fill::x(Align::w(FieldH(theme, "Start ", format!("{:<8}", sample.start)))), Fill::x(Align::w(FieldH(theme, "End ", format!("{:<8}", sample.end)))), Fill::x(Align::w(FieldH(theme, "Trans ", "0"))), Fill::x(Align::w(FieldH(theme, "Gain ", format!("{}", sample.gain)))), - )) - }), Thunk::new(move||Tui::fg(Red, col!( + ))) + }), Thunk::new(|to: &mut TuiOut|to.place(&Tui::fg(Red, col!( Tui::bold(true, "× No sample."), "[r] record", "[Shift-F9] import", - )))) + ))))) } -fn draw_status (sample: Option<&Arc>>) -> impl Content { +fn draw_status (sample: Option<&Arc>>) -> impl Draw + Layout { Tui::bold(true, Tui::fg(Tui::g(224), sample .map(|sample|{ let sample = sample.read().unwrap(); diff --git a/crates/device/src/sequencer/seq_launch.rs b/crates/device/src/sequencer/seq_launch.rs index 48d30f1c..fb8a0524 100644 --- a/crates/device/src/sequencer/seq_launch.rs +++ b/crates/device/src/sequencer/seq_launch.rs @@ -38,7 +38,7 @@ pub trait HasPlayClip: HasClock { *self.reset_mut() = true; } - fn play_status (&self) -> impl Content { + fn play_status (&self) -> impl Draw { let (name, color): (Arc, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let MidiClip { ref name, color, .. } = *clip.read().unwrap(); (name.clone(), color) @@ -51,7 +51,7 @@ pub trait HasPlayClip: HasClock { FieldV(color, "Now:", format!("{} {}", time, name)) } - fn next_status (&self) -> impl Content { + fn next_status (&self) -> impl Draw { let mut time: Arc = String::from("--.-.--").into(); let mut name: Arc = String::from("").into(); let mut color = ItemTheme::G[64]; diff --git a/deps/tengri b/deps/tengri index b98fccd9..5e6338fa 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit b98fccd98ba13be8eabe29d66621a15d025b2042 +Subproject commit 5e6338fad8febc217bde4879d8250373a75faf7a