diff --git a/Justfile b/Justfile index 569c216f..475359ee 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,4 @@ export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace" -export RUST_BACKTRACE := "1" debug := "reset && cargo run --" release := "reset && cargo run --release --" diff --git a/crates/app/app.rs b/crates/app/app.rs index 4476a88e..3b67f9a2 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -5,33 +5,37 @@ //█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ //███████████████████████████████████████████████████ //█ ▀ ▀ ▀ █ -#![allow(unused, clippy::unit_arg)] -#![feature(adt_const_params, associated_type_defaults, if_let_guard, impl_trait_in_assoc_type, - type_alias_impl_trait, trait_alias, type_changing_struct_update, closure_lifetime_binder)] -pub use ::{ - tek_engine::*, - tek_device, - tek_device::*, - tengri::{ - Usually, Perhaps, Has, MaybeHas, has, maybe_has, - dsl::*, input::*, output::*, tui::*, - tui::ratatui, - tui::ratatui::prelude::buffer::Cell, - tui::ratatui::prelude::Color::{self, *}, - tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier}, - tui::crossterm, - tui::crossterm::event::{Event, KeyCode::{self, *}}, - }, - std::{ - path::{Path, PathBuf}, - sync::{Arc, RwLock}, - sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}, - error::Error, - collections::BTreeMap, - fmt::Write, - }, - xdg::BaseDirectories, -}; +#![allow(unused)] +#![allow(clippy::unit_arg)] +#![feature(adt_const_params)] +#![feature(associated_type_defaults)] +#![feature(if_let_guard)] +#![feature(impl_trait_in_assoc_type)] +#![feature(type_alias_impl_trait)] +#![feature(trait_alias)] +#![feature(type_changing_struct_update)] +#![feature(closure_lifetime_binder)] +pub use ::tek_engine:: *; +pub use ::tek_device::{self, *}; +pub use ::tengri::{Usually, Perhaps, Has, MaybeHas}; +pub use ::tengri::{has, maybe_has}; +pub use ::tengri::dsl::*; +pub use ::tengri::input::*; +pub use ::tengri::output::*; +pub use ::tengri::tui::*; +pub use ::tengri::tui::ratatui; +pub use ::tengri::tui::ratatui::prelude::buffer::Cell; +pub use ::tengri::tui::ratatui::prelude::Color::{self, *}; +pub use ::tengri::tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier}; +pub use ::tengri::tui::crossterm; +pub use ::tengri::tui::crossterm::event::{Event, KeyCode::{self, *}}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}; +use std::error::Error; +use std::collections::BTreeMap; +use std::fmt::Write; +use xdg::BaseDirectories; mod app_jack; pub use self::app_jack::*; #[cfg(test)] mod app_test; /// Total state @@ -111,11 +115,10 @@ impl Config { } pub fn load_defs (&mut self, dsl: impl Dsl) -> Usually<()> { dsl.each(|item|{ - println!("{item:?} {:?}", item.expr().head()); - match item.expr().head() { - Ok(Some("keys")) if let Some(id) = item.expr().tail().head()? => + match item.exp().head() { + Ok(Some("keys")) if let Some(id) = item.exp().tail().head()? => self.load_bind(id.into(), item), - Ok(Some("mode")) if let Some(id) = item.expr().tail().head()? => + Ok(Some("mode")) if let Some(id) = item.exp().tail().head()? => self.load_mode(id.into(), item), _ => return Err(format!("load_defs: unexpected: {item:?}").into()) } @@ -123,46 +126,48 @@ impl Config { } pub fn load_bind (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { let mut map = EventMap::new(); - item.expr().tail().tail()?.each(|item|if item.expr().head() == Ok(Some("see")) { - // TODO - Ok(()) - } else if let Ok(Some(word)) = item.expr().head().word() { - map.add(TuiEvent::from_dsl(item.expr()?.head()?)?, Binding { - commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(), + item.exp().tail().tail()?.each(|item|Self::load_bind_one(&mut map, item))?; + self.binds.write().unwrap().insert(id, map); + Ok(()) + } + fn load_bind_one (map: &mut EventMap, Arc>, item: impl Dsl) -> Usually<()> { + if let Ok(Some(sym)) = item.exp().head().sym() { + map.add(TuiEvent::from_dsl(item.exp()?.head()?)?, Binding { + command: item.exp()?.tail()?.unwrap_or_default().into(), condition: None, description: None, source: None }); - Ok(()) + } else if item.exp().head() == Ok(Some("see")) { + // TODO } else { - return Err(format!("load_bind: unexpected: {item:?}").into()) - })?; - self.binds.write().unwrap().insert(id, map); + return Err(format!("load_defs: unexpected: {item:?}").into()) + } Ok(()) } pub fn load_mode (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { let mut mode = Mode::default(); - item.expr().tail().tail()?.each(|item|Self::load_mode_one(&mut mode, item))?; + item.exp().tail().tail()?.each(|item|Self::load_mode_one(&mut mode, item))?; self.modes.write().unwrap().insert(id.into(), Arc::new(mode)); Ok(()) } pub fn load_mode_one (mode: &mut Mode>, item: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(key)) = item.expr().head() { + Ok(if let Ok(Some(key)) = item.exp().head() { match key { - "name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "keys" => mode.keys.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), - "mode" => if let Some(id) = item.expr()?.tail()?.head()? { + "name" => mode.name.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "info" => mode.info.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "keys" => mode.keys.push(item.exp()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "mode" => if let Some(id) = item.exp()?.tail()?.head()? { let mut submode = Mode::default(); - Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; + Self::load_mode_one(&mut submode, item.exp()?.tail()?.tail()?)?; mode.modes.insert(id.into(), submode); } else { return Err(format!("load_mode_one: incomplete: {item:?}").into()); }, - _ => mode.view.push(item.expr()?.unwrap().into()), + _ => mode.view.push(item.exp()?.unwrap().into()), } - } else if let Ok(Some(word)) = item.word() { - mode.view.push(word.into()); + } else if let Ok(Some(sym)) = item.sym() { + mode.view.push(sym.into()); } else { return Err(format!("load_mode_one: unexpected: {item:?}").into()); }) @@ -205,29 +210,116 @@ impl App { ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) } } -fn render_dsl <'t> ( - state: &'t impl DslNs<'t, Box>>, - src: &str -) -> Box> { - let err: Option> = match state.from(src) { +fn render_dsl <'t, S> (state: &'t S, src: &str) -> Box> + where S: DslSymNs<'t, Box>> + DslExpNs<'t, Box>> +{ + let sym_err: Option> = match state.from_sym(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("▀")))), - }) + let exp_err = match state.from_exp(src) { + Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), + }; + let (err_fg_1, err_bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); + let (err_fg_2, err_bg_2) = (Color::Rgb(250, 200, 120), Color::Rgb(32, 0, 0)); + Box::new(Fill::x(col! { + Fill::x(Margin::x(1, Align::w(Tui::bold(true, Tui::fg_bg(err_fg_1, err_bg_1, "Could not render:"))))), + Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{src}")))), + Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{sym_err:?}")))), + Fill::x(Margin::x(1, Tui::fg_bg(err_fg_2, err_bg_2, format!("{exp_err:?}")))), + })) } +dsl_ns!(|app: App| -> Box> { + ("bold", value: bool, x: Box>) => Box::new(Tui::bold(value, x)), + + ("fg", color: Color, x: Box>) => Box::new(Tui::fg(color, x)), + ("bg", color: Color, x: Box>) => Box::new(Tui::bg(color, x)), + ("fg/bg", fg: Color, bg: Color, x: Box>) => Box::new(Tui::fg_bg(fg, bg, x)), + + ("bsp/n", a: Box>, b: Box>) => Box::new(Bsp::n(a, b)), + ("bsp/s", a: Box>, b: Box>) => Box::new(Bsp::s(a, b)), + ("bsp/e", a: Box>, b: Box>) => Box::new(Bsp::e(a, b)), + ("bsp/w", a: Box>, b: Box>) => Box::new(Bsp::w(a, b)), + ("bsp/a", a: Box>, b: Box>) => Box::new(Bsp::a(a, b)), + ("bsp/b", a: Box>, b: Box>) => Box::new(Bsp::b(a, b)), + + ("align/n", x: Box>) => Box::new(Align::n(x)), + ("align/s", x: Box>) => Box::new(Align::s(x)), + ("align/e", x: Box>) => Box::new(Align::e(x)), + ("align/w", x: Box>) => Box::new(Align::w(x)), + ("align/x", x: Box>) => Box::new(Align::x(x)), + ("align/y", x: Box>) => Box::new(Align::y(x)), + ("align/c", x: Box>) => Box::new(Align::c(x)), + + ("fill/x", x: Box>) => Box::new(Fill::x(x)), + ("fill/y", x: Box>) => Box::new(Fill::y(x)), + ("fill/xy", x: Box>) => Box::new(Fill::xy(x)), + + ("fixed/x", x: u16, c: Box>) => Box::new(Fixed::x(x, c)), + ("fixed/y", y: u16, c: Box>) => Box::new(Fixed::y(y, c)), + ("fixed/xy", x: u16, y: u16, c: Box>) => Box::new(Fixed::xy(x, y, c)), + + ("min/x", x: u16, c: Box>) => Box::new(Min::x(x, c)), + ("min/y", y: u16, c: Box>) => Box::new(Min::y(y, c)), + ("min/xy", x: u16, y: u16, c: Box>) => Box::new(Min::xy(x, y, c)), + + ("max/x", x: u16, c: Box>) => Box::new(Max::x(x, c)), + ("max/y", y: u16, c: Box>) => Box::new(Max::y(y, c)), + ("max/xy", x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), + + ":view/menu" => + app.view(stringify!((bg (rgb 0 0 0) + (bsp/s :view/ports/outs (bsp/s (bg (rgb 33 33 33) (bold :true "tek 0.3.0-rc.0"))) + (bsp/n :view/ports/ins (bsp/n (bg (rgb 33 33 33) (bsp/e (fg (rgb 255 192 48) "[Enter]") " new session")) + (align/n (fill/xy :view/modes)))))))), + ":view/ports/outs" => + app.view(stringify!((fill/x (fixed/y 3 + (bsp/a (fill/x (align/w "L AUDIO OUT") + (bsp/a "MIDI OUT" (fill/x (align/e "AUDIO OUT R"))))))))), + ":view/ports/ins" => + app.view(stringify!(fill/x (fixed/y 3 + (bsp/a (fill/x (align/w "L AUDIO IN ") + (bsp/a "MIDI IN " (fill/x (align/e "AUDIO IN R")))))))), + ":view/browse" => + app.view(stringify!(bsp/s + (padding/xy 3 1 :view/browse-title) (enclose (fg (g 96)) :view/browser))), + ":view/browse/title" => + Box::new(Fill::x(Align::w(FieldV(Default::default(), + match app.dialog.browser_target().unwrap() { + BrowserTarget::SaveProject => "Save project:", + BrowserTarget::LoadProject => "Load project:", + BrowserTarget::ImportSample(_) => "Import sample:", + BrowserTarget::ExportSample(_) => "Export sample:", + BrowserTarget::ImportClip(_) => "Import clip:", + BrowserTarget::ExportClip(_) => "Export clip:", + }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), + ":view/modes" => Box::new({ + let modes = app.config.modes.clone(); + Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ + for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { + let bg = if index == 0 { Rgb(64,64,64) } else { Rgb(32,32,32) }; + 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(""); + //add(&app.view(stringify!((fixed/y 2 (bg #bg (bsp/s + //(fill/x (bsp/a (fill/x (align/w (fg (rgb 224 192 128) #name))) + //(fill/x (align/e (fg (rgb 224 128 32) #id))))) + //(fill/x (align/w #info)))))))); + } + }) }), + ":view/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")))) }))) }, + //(":view/options", view_options), +}); +dsl_ns!(|app: App| -> Color { + ("g", n: u8) => Color::Rgb(n, n, n), + ("rgb", r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), +}); 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( @@ -244,480 +336,6 @@ impl ScenesView for App { (self.width() as u16).saturating_sub(self.w_side()) } } -handle!(TuiIn:|self: App, input|{ - panic!("wat: {:?}", self.mode); - for keys in self.mode.keys.iter() { - panic!("{keys} {:?}", self.config.binds.read().unwrap()); - if let Some(binding) = self.config.binds.read().unwrap().get(keys.as_ref()) { - panic!("{binding:?}"); - } - } - Ok(None) -}); -#[derive(Debug)] -pub enum AppCommand { /* TODO */ } -impl Dialog { - pub fn menu_selected (&self) -> Option { - if let Self::Menu(selected) = self { Some(*selected) } else { None } - } - pub fn device_kind (&self) -> Option { - if let Self::Device(index) = self { Some(*index) } else { None } - } - pub fn device_kind_next (&self) -> Option { - self.device_kind().map(|index|(index + 1) % device_kinds().len()) - } - pub fn device_kind_prev (&self) -> Option { - self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))) - } - pub fn message (&self) -> Option<&str> { - todo!() - } - pub fn browser (&self) -> Option<&Arc> { - todo!() - } - pub fn browser_target (&self) -> Option<&BrowserTarget> { - todo!() - } -} -#[tengri_proc::command(Option)] impl DialogCommand { - fn open (dialog: &mut Option, new: Dialog) -> Perhaps { - *dialog = Some(new); - Ok(None) - } - fn close (dialog: &mut Option) -> Perhaps { - *dialog = None; - Ok(None) - } -} - -impl App { - pub fn editor_focused (&self) -> bool { - false - } - pub fn toggle_dialog (&mut self, mut dialog: Dialog) -> Dialog { - std::mem::swap(&mut self.dialog, &mut dialog); - dialog - } - pub fn toggle_editor (&mut self, value: Option) { - //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); - let value = value.unwrap_or_else(||!self.editor().is_some()); - if value { - // Create new clip in pool when entering empty cell - if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && slot.is_none() - && let Some(track) = self.project.tracks.get_mut(track) - { - let (index, mut clip) = self.pool.add_new_clip(); - // autocolor: new clip colors from scene and track color - let color = track.color.base.mix(scene.color.base, 0.5); - clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); - if let Some(editor) = &mut self.project.editor { - editor.set_clip(Some(&clip)); - } - *slot = Some(clip.clone()); - //Some(clip) - } else { - //None - } - } else if let Selection::TrackClip { track, scene } = *self.selection() - && let Some(scene) = self.project.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && let Some(clip) = slot.as_mut() - { - // Remove clip from arrangement when exiting empty clip editor - let mut swapped = None; - if clip.read().unwrap().count_midi_messages() == 0 { - std::mem::swap(&mut swapped, slot); - } - if let Some(clip) = swapped { - self.pool.delete_clip(&clip.read().unwrap()); - } - } - } - pub fn browser (&self) -> Option<&Browser> { - if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None } - } - pub fn device_pick (&mut self, index: usize) { - self.dialog = Dialog::Device(index); - } - 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!(), - } - } -} - -dsl_ns! { |app: App| - - Box> => { - ("bold", value: bool, x: Box>) => Box::new(Tui::bold(value, x)), - - ("fg", color: Color, x: Box>) => Box::new(Tui::fg(color, x)), - ("bg", color: Color, x: Box>) => Box::new(Tui::bg(color, x)), - ("fg/bg", fg: Color, bg: Color, x: Box>) => Box::new(Tui::fg_bg(fg, bg, x)), - - ("bsp/n", a: Box>, b: Box>) => Box::new(Bsp::n(a, b)), - ("bsp/s", a: Box>, b: Box>) => Box::new(Bsp::s(a, b)), - ("bsp/e", a: Box>, b: Box>) => Box::new(Bsp::e(a, b)), - ("bsp/w", a: Box>, b: Box>) => Box::new(Bsp::w(a, b)), - ("bsp/a", a: Box>, b: Box>) => Box::new(Bsp::a(a, b)), - ("bsp/b", a: Box>, b: Box>) => Box::new(Bsp::b(a, b)), - - ("align/n", x: Box>) => Box::new(Align::n(x)), - ("align/s", x: Box>) => Box::new(Align::s(x)), - ("align/e", x: Box>) => Box::new(Align::e(x)), - ("align/w", x: Box>) => Box::new(Align::w(x)), - ("align/x", x: Box>) => Box::new(Align::x(x)), - ("align/y", x: Box>) => Box::new(Align::y(x)), - ("align/c", x: Box>) => Box::new(Align::c(x)), - - ("fill/x", x: Box>) => Box::new(Fill::x(x)), - ("fill/y", x: Box>) => Box::new(Fill::y(x)), - ("fill/xy", x: Box>) => Box::new(Fill::xy(x)), - - ("fixed/x", x: u16, c: Box>) => Box::new(Fixed::x(x, c)), - ("fixed/y", y: u16, c: Box>) => Box::new(Fixed::y(y, c)), - ("fixed/xy", x: u16, y: u16, c: Box>) => Box::new(Fixed::xy(x, y, c)), - - ("min/x", x: u16, c: Box>) => Box::new(Min::x(x, c)), - ("min/y", y: u16, c: Box>) => Box::new(Min::y(y, c)), - ("min/xy", x: u16, y: u16, c: Box>) => Box::new(Min::xy(x, y, c)), - - ("max/x", x: u16, c: Box>) => Box::new(Max::x(x, c)), - ("max/y", y: u16, c: Box>) => Box::new(Max::y(y, c)), - ("max/xy", x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), - - ":view/menu" => app.view(stringify!( - (bg :color/bg (bsp/s :view/ports/outs (bsp/n :view/ports/ins :view/modes))) - )), - - ":view/modes" => Box::new({ - let modes = app.config.modes.clone(); - let height = (modes.read().unwrap().len() * 2) as u16; - Fixed::y(height, - Fill::x(Stack::south(move|add: &mut dyn FnMut(&dyn Render)|{ - for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() { - let bg = if index == 0 { Rgb(48,64,32) } else { Rgb(16, 32, 24) }; - 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); - add(&Fixed::y(2, Fill::x(Tui::bg(bg, Bsp::s( - Fill::x(Bsp::a(Fill::x(Align::w(Tui::fg(fg1, name))), - Fill::x(Align::e(Tui::fg(fg2, id))))), - Fill::x(Align::w(info))))))); - } - })))}), - - ":view/ports/outs" => - app.view(stringify!((fill/x (fixed/y 3 - (bsp/a (fill/x (align/w "L AUDIO OUT") - (bsp/a "MIDI OUT" (fill/x (align/e "AUDIO OUT R"))))))))), - ":view/ports/ins" => - app.view(stringify!(fill/x (fixed/y 3 - (bsp/a (fill/x (align/w "L AUDIO IN ") - (bsp/a "MIDI IN " (fill/x (align/e "AUDIO IN R")))))))), - ":view/browse" => - app.view(stringify!(bsp/s - (padding/xy 3 1 :view/browse-title) (enclose (fg (g 96)) :view/browser))), - ":view/browse/title" => - Box::new(Fill::x(Align::w(FieldV(Default::default(), - match app.dialog.browser_target().unwrap() { - BrowserTarget::SaveProject => "Save project:", - BrowserTarget::LoadProject => "Load project:", - BrowserTarget::ImportSample(_) => "Import sample:", - BrowserTarget::ExportSample(_) => "Export sample:", - BrowserTarget::ImportClip(_) => "Import clip:", - BrowserTarget::ExportClip(_) => "Export clip:", - }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), - ":view/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")))) }))) }, - //(":view/options", view_options), - }; - - bool => { - ":focused/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(..)), - ":focused/browser" => app.dialog.browser().is_some(), - ":focused/pool/import" => matches!(app.pool.mode, Some(PoolMode::Import(..))), - ":focused/pool/export" => matches!(app.pool.mode, Some(PoolMode::Export(..))), - ":focused/pool/rename" => matches!(app.pool.mode, Some(PoolMode::Rename(..))), - ":focused/pool/length" => matches!(app.pool.mode, Some(PoolMode::Length(..))), - ":focused/clip" => !app.editor_focused() && matches!(app.selection(), - Selection::TrackClip{..}), - ":focused/track" => !app.editor_focused() && matches!(app.selection(), - Selection::Track(..)), - ":focused/scene" => !app.editor_focused() && matches!(app.selection(), - Selection::Scene(..)), - ":focused/mix" => !app.editor_focused() && matches!(app.selection(), - Selection::Mix), - }; - - ItemTheme => { - ":_theme_stub" => Default::default() - }; - - Dialog => { - ":dialog/none" => Dialog::None, - ":dialog/options" => Dialog::Options, - ":dialog/device" => Dialog::Device(0), - ":dialog/device/prev" => Dialog::Device(0), - ":dialog/device/next" => Dialog::Device(0), - ":dialog/help" => Dialog::Help(0), - ":dialog/menu" => Dialog::Menu(0), - ":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject, - Browser::new(None).unwrap().into()), - ":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject, - Browser::new(None).unwrap().into()), - ":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()), - Browser::new(None).unwrap().into()), - ":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()), - Browser::new(None).unwrap().into()), - ":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()), - Browser::new(None).unwrap().into()), - ":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()), - Browser::new(None).unwrap().into()), - }; - - Selection => { - ":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(), - }; - - Option => { - ":editor/pitch" => Some((app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into()) - }; - - Option => { - ":selected/scene" => app.selection().scene(), - ":selected/track" => app.selection().track(), - }; - - Option>> => { - ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { - app.scenes()[*scene].clips[*track].clone() - } else { - None - } - }; - - AppCommand => { - ("stop-all") => todo!(),//app.project.stop_all(), - ("enqueue", clip: Option>>) => todo!(), - ("history", delta: isize) => todo!(), - ("zoom", zoom: usize) => todo!(), - ("select", selection: Selection) => todo!(), - ("dialog" / command: DialogCommand) => todo!(), - ("project" / command: ArrangementCommand) => todo!(), - ("clock" / command: ClockCommand) => todo!(), - ("sampler" / command: SamplerCommand) => todo!(), - ("pool" / command: PoolCommand) => todo!(), - ("pool" / editor: MidiEditCommand) => todo!(), - }; - - DialogCommand; - - ArrangementCommand; - - ClockCommand; - - SamplerCommand; - - PoolCommand; - - MidiEditCommand; - - Color => { - ("g", n: u8) => Color::Rgb(n, n, n), - ("rgb", red: u8, green: u8, blue: u8) => Color::Rgb(red, green, blue), - ":color/bg" => Color::Rgb(28, 32, 36), - - }; - -} - -dsl_ns! { num |app: App| - u8; - u16 => { - ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), - ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), - }; - usize => { - ":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; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//#[derive(Clone, Debug)] -//pub enum DialogCommand { - //Open { dialog: Dialog }, - //Close -//} - -//impl Command> for DialogCommand { - //fn execute (self, state: &mut Option) -> Perhaps { - //match self { - //Self::Open { dialog } => { - //*state = Some(dialog); - //}, - //Self::Close => { - //*state = None; - //} - //}; - //Ok(None) - //} -//} - -//dsl!(DialogCommand: |self: Dialog, iter|todo!()); -//Dsl::take(&mut self.dialog, iter)); - -//#[tengri_proc::command(Option)]//Nope. -//impl DialogCommand { - //fn open (dialog: &mut Option, new: Dialog) -> Perhaps { - //*dialog = Some(new); - //Ok(None) - //} - //fn close (dialog: &mut Option) -> Perhaps { - //*dialog = None; - //Ok(None) - //} -//} -// -//dsl_bind!(AppCommand: App { - //enqueue = |app, clip: Option>>| { todo!() }; - //history = |app, delta: isize| { todo!() }; - //zoom = |app, zoom: usize| { todo!() }; - //stop_all = |app| { app.tracks_stop_all(); Ok(None) }; - ////dialog = |app, command: DialogCommand| - ////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?); - //project = |app, command: ArrangementCommand| - //Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?); - //clock = |app, command: ClockCommand| - //Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c})); - //sampler = |app, command: SamplerCommand| - //Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command})) - //.transpose()?.flatten()); - //pool = |app, command: PoolCommand| { - //let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?; - //// update linked editor after pool action - //match command { - //// autoselect: automatically load selected clip in editor - //PoolCommand::Select { .. } | - //// autocolor: update color in all places simultaneously - //PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => { - //let clip = app.pool.clip().clone(); - //app.editor_mut().map(|editor|editor.set_clip(clip.as_ref())) - //}, - //_ => None - //}; - //Ok(undo) - //}; - //select = |app, selection: Selection| { - //*app.project.selection_mut() = selection; - ////todo! - ////if let Some(ref mut editor) = app.editor_mut() { - ////editor.set_clip(match selection { - ////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app - ////.project - ////.scenes.get(scene) - ////.map(|s|s.clips.get(track)) - ////=> - ////Some(clip), - ////_ => - ////None - ////}); - ////} - //Ok(None) - ////("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { - ////(0, 0) => Self::Select(Selection::Mix), - ////(t, 0) => Self::Select(Selection::Track(t)), - ////(0, s) => Self::Select(Selection::Scene(s)), - ////(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) - //// autoedit: load focused clip in editor. - //}; - ////fn color (app: &mut App, theme: ItemTheme) -> Perhaps { - ////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme})) - ////} - ////fn launch (app: &mut App) -> Perhaps { - ////app.project.launch(); - ////Ok(None) - ////} - //toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) }; - //editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() { - //let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?; - //// update linked sampler after editor action - //app.project.sampler_mut().map(|sampler|match command { - //// autoselect: automatically select sample in sampler - //MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); }, - //_ => {} - //}); - //undo - //} else { - //None - //}); -//}); -//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter)); -//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten())); -//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter)); -//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten())); -//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter)); -//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter)); -//has_editor!(|self: App|{ - //editor = self.editor; - //editor_w = { - //let size = self.size.w(); - //let editor = self.editor.as_ref().expect("missing editor"); - //let time_len = editor.time_len().get(); - //let time_zoom = editor.time_zoom().get().max(1); - //(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) - //}; - //editor_h = 15; - //is_editing = self.editor.is_some(); -//}); //Bsp::s("", //Map::south(1, @@ -937,3 +555,360 @@ dsl_ns! { num |app: App| ////let options = ||["Projects", "Settings", "Help", "Quit"].iter(); ////let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); ////Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) +handle!(TuiIn:|self: App, input|{ + panic!("wat: {:?}", self.mode); + for keys in self.mode.keys.iter() { + panic!("{keys} {:?}", self.config.binds.read().unwrap()); + if let Some(binding) = self.config.binds.read().unwrap().get(keys.as_ref()) { + panic!("{binding:?}"); + } + } + Ok(None) + //Ok(if let Some(binding) = self.mode.as_ref() + //.map(|c|c.keys.dispatch(input.event())).flatten() + //{ + //let binding = binding.clone(); + //let undo = binding.command.clone().execute(self)?; + //// FIXME failed commands are not persisted in undo history + ////self.history.push((binding.command.clone(), undo)); + //Some(true) + //} else { + //None + //}) +}); +dsl_ns!(|app: App| -> u8 + { ":_u8_stub" => 1 }); +dsl_ns!(|app: App| -> isize + { ":_isize_stub" => -1 }); +dsl_ns!(|app: App| -> ItemTheme + { ":_theme_stub" => Default::default() }); +dsl_ns!(|app: App| -> u16{ + ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), + ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), +}); +dsl_ns!(|app: App| -> usize { + ":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), +}); +dsl_ns!(|app: App| -> bool { + ":focused/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(..)), + ":focused/browser" => app.dialog.browser().is_some(), + ":focused/clip" => !app.editor_focused() && matches!(app.selection(), Selection::TrackClip{..}), + ":focused/track" => !app.editor_focused() && matches!(app.selection(), Selection::Track(..)), + ":focused/scene" => !app.editor_focused() && matches!(app.selection(), Selection::Scene(..)), + ":focused/mix" => !app.editor_focused() && matches!(app.selection(), Selection::Mix), + ":focused/pool/import" => matches!(app.pool.mode, Some(PoolMode::Import(..))), + ":focused/pool/export" => matches!(app.pool.mode, Some(PoolMode::Export(..))), + ":focused/pool/rename" => matches!(app.pool.mode, Some(PoolMode::Rename(..))), + ":focused/pool/length" => matches!(app.pool.mode, Some(PoolMode::Length(..))), +}); +dsl_ns!(|app: App| -> Dialog { + ":dialog/none" => Dialog::None, + ":dialog/options" => Dialog::Options, + ":dialog/device" => Dialog::Device(0), + ":dialog/device/prev" => Dialog::Device(0), + ":dialog/device/next" => Dialog::Device(0), + ":dialog/help" => Dialog::Help(0), + ":dialog/menu" => Dialog::Menu(0), + ":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject, Browser::new(None).unwrap().into()), + ":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject, Browser::new(None).unwrap().into()), + ":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()), Browser::new(None).unwrap().into()), + ":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()), Browser::new(None).unwrap().into()), + ":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()), Browser::new(None).unwrap().into()), + ":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()), Browser::new(None).unwrap().into()), +}); +dsl_ns!(|app: App| -> Selection { + ":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(), +}); +dsl_ns!(|app: App| -> Option { + ":editor/pitch" => Some((app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into()) +}); +dsl_ns!(|app: App| -> Option { + ":selected/scene" => app.selection().scene(), + ":selected/track" => app.selection().track(), +}); +dsl_ns!(|app: App| -> Option>> { + ":selected/clip" => if let Selection::TrackClip { track, scene } = app.selection() { + app.scenes()[*scene].clips[*track].clone() + } else { + None + } +}); +#[derive(Debug)] +pub enum AppCommand { /* TODO */ } +dsl_ns!(|app: App| -> AppCommand { + ("stop-all") => todo!(),//app.project.stop_all(), + ("enqueue", clip: Option>>) => todo!(), + ("history", delta: isize) => todo!(), + ("zoom", zoom: usize) => todo!(), + ("select", selection: Selection) => todo!(), + ("dialog" / command: DialogCommand) => todo!(), + ("project" / command: ArrangementCommand) => todo!(), + ("clock" / command: ClockCommand) => todo!(), + ("sampler" / command: SamplerCommand) => todo!(), + ("pool" / command: PoolCommand) => todo!(), + ("pool" / editor: MidiEditCommand) => todo!(), +}); +dsl_ns!(|app: App| -> DialogCommand {}); +dsl_ns!(|app: App| -> ArrangementCommand {}); +dsl_ns!(|app: App| -> ClockCommand {}); +dsl_ns!(|app: App| -> SamplerCommand {}); +dsl_ns!(|app: App| -> PoolCommand {}); +dsl_ns!(|app: App| -> MidiEditCommand {}); +impl App { + pub fn editor_focused (&self) -> bool { + false + } +} +impl Dialog { + pub fn menu_selected (&self) -> Option { + if let Self::Menu(selected) = self { Some(*selected) } else { None } + } + pub fn device_kind (&self) -> Option { + if let Self::Device(index) = self { Some(*index) } else { None } + } + pub fn device_kind_next (&self) -> Option { + self.device_kind().map(|index|(index + 1) % device_kinds().len()) + } + pub fn device_kind_prev (&self) -> Option { + self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1))) + } + pub fn message (&self) -> Option<&str> { + todo!() + } + pub fn browser (&self) -> Option<&Arc> { + todo!() + } + pub fn browser_target (&self) -> Option<&BrowserTarget> { + todo!() + } +} +#[tengri_proc::command(Option)] impl DialogCommand { + fn open (dialog: &mut Option, new: Dialog) -> Perhaps { + *dialog = Some(new); + Ok(None) + } + fn close (dialog: &mut Option) -> Perhaps { + *dialog = None; + Ok(None) + } +} + +impl App { + pub fn toggle_dialog (&mut self, mut dialog: Dialog) -> Dialog { + std::mem::swap(&mut self.dialog, &mut dialog); + dialog + } + pub fn toggle_editor (&mut self, value: Option) { + //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); + let value = value.unwrap_or_else(||!self.editor().is_some()); + if value { + // Create new clip in pool when entering empty cell + if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && slot.is_none() + && let Some(track) = self.project.tracks.get_mut(track) + { + let (index, mut clip) = self.pool.add_new_clip(); + // autocolor: new clip colors from scene and track color + let color = track.color.base.mix(scene.color.base, 0.5); + clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); + if let Some(editor) = &mut self.project.editor { + editor.set_clip(Some(&clip)); + } + *slot = Some(clip.clone()); + //Some(clip) + } else { + //None + } + } else if let Selection::TrackClip { track, scene } = *self.selection() + && let Some(scene) = self.project.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && let Some(clip) = slot.as_mut() + { + // Remove clip from arrangement when exiting empty clip editor + let mut swapped = None; + if clip.read().unwrap().count_midi_messages() == 0 { + std::mem::swap(&mut swapped, slot); + } + if let Some(clip) = swapped { + self.pool.delete_clip(&clip.read().unwrap()); + } + } + } + pub fn browser (&self) -> Option<&Browser> { + if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None } + } + pub fn device_pick (&mut self, index: usize) { + self.dialog = Dialog::Device(index); + } + 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!(), + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//#[derive(Clone, Debug)] +//pub enum DialogCommand { + //Open { dialog: Dialog }, + //Close +//} + +//impl Command> for DialogCommand { + //fn execute (self, state: &mut Option) -> Perhaps { + //match self { + //Self::Open { dialog } => { + //*state = Some(dialog); + //}, + //Self::Close => { + //*state = None; + //} + //}; + //Ok(None) + //} +//} + +//dsl!(DialogCommand: |self: Dialog, iter|todo!()); +//Dsl::take(&mut self.dialog, iter)); + +//#[tengri_proc::command(Option)]//Nope. +//impl DialogCommand { + //fn open (dialog: &mut Option, new: Dialog) -> Perhaps { + //*dialog = Some(new); + //Ok(None) + //} + //fn close (dialog: &mut Option) -> Perhaps { + //*dialog = None; + //Ok(None) + //} +//} +// +//dsl_bind!(AppCommand: App { + //enqueue = |app, clip: Option>>| { todo!() }; + //history = |app, delta: isize| { todo!() }; + //zoom = |app, zoom: usize| { todo!() }; + //stop_all = |app| { app.tracks_stop_all(); Ok(None) }; + ////dialog = |app, command: DialogCommand| + ////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?); + //project = |app, command: ArrangementCommand| + //Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?); + //clock = |app, command: ClockCommand| + //Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c})); + //sampler = |app, command: SamplerCommand| + //Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command})) + //.transpose()?.flatten()); + //pool = |app, command: PoolCommand| { + //let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?; + //// update linked editor after pool action + //match command { + //// autoselect: automatically load selected clip in editor + //PoolCommand::Select { .. } | + //// autocolor: update color in all places simultaneously + //PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => { + //let clip = app.pool.clip().clone(); + //app.editor_mut().map(|editor|editor.set_clip(clip.as_ref())) + //}, + //_ => None + //}; + //Ok(undo) + //}; + //select = |app, selection: Selection| { + //*app.project.selection_mut() = selection; + ////todo! + ////if let Some(ref mut editor) = app.editor_mut() { + ////editor.set_clip(match selection { + ////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app + ////.project + ////.scenes.get(scene) + ////.map(|s|s.clips.get(track)) + ////=> + ////Some(clip), + ////_ => + ////None + ////}); + ////} + //Ok(None) + ////("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { + ////(0, 0) => Self::Select(Selection::Mix), + ////(t, 0) => Self::Select(Selection::Track(t)), + ////(0, s) => Self::Select(Selection::Scene(s)), + ////(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) + //// autoedit: load focused clip in editor. + //}; + ////fn color (app: &mut App, theme: ItemTheme) -> Perhaps { + ////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme})) + ////} + ////fn launch (app: &mut App) -> Perhaps { + ////app.project.launch(); + ////Ok(None) + ////} + //toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) }; + //editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() { + //let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?; + //// update linked sampler after editor action + //app.project.sampler_mut().map(|sampler|match command { + //// autoselect: automatically select sample in sampler + //MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); }, + //_ => {} + //}); + //undo + //} else { + //None + //}); +//}); +//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter)); +//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten())); +//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter)); +//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten())); +//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter)); +//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter)); +//has_editor!(|self: App|{ + //editor = self.editor; + //editor_w = { + //let size = self.size.w(); + //let editor = self.editor.as_ref().expect("missing editor"); + //let time_len = editor.time_len().get(); + //let time_zoom = editor.time_zoom().get().max(1); + //(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) + //}; + //editor_h = 15; + //is_editing = self.editor.is_some(); +//}); diff --git a/deps/tengri b/deps/tengri index 1cc90548..4c312ac8 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 1cc905485fe42f36be4802cec29fee155a3ae295 +Subproject commit 4c312ac8d75f9c6d1579e3882f7aba68a16a0f69