From 08730df0421369d1ca18988f6fc157e0716cb092 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 16 Aug 2025 13:56:55 +0300 Subject: [PATCH 1/2] wip: unified dsl_ns macro --- crates/app/app.rs | 993 +++++++++--------- crates/cli/tek.rs | 11 +- crates/device/src/arranger/arranger_scenes.rs | 2 +- crates/device/src/arranger/arranger_tracks.rs | 8 +- crates/device/src/clock/clock_view.rs | 6 +- crates/device/src/pool/pool_api.rs | 10 +- crates/device/src/sampler/sampler_api.rs | 2 +- deps/tengri | 2 +- 8 files changed, 524 insertions(+), 510 deletions(-) diff --git a/crates/app/app.rs b/crates/app/app.rs index 2c05610d..5e2c5495 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -5,41 +5,33 @@ //█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ //███████████████████████████████████████████████████ //█ ▀ ▀ ▀ █ -#![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(let_chains)] -#![feature(closure_lifetime_binder)] -#![feature(generic_const_exprs)] -#![feature(generic_arg_infer)] -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 ::tengri::tui::ratatui::prelude::Position; -use xdg::BaseDirectories; +#![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, +}; mod app_jack; pub use self::app_jack::*; #[cfg(test)] mod app_test; /// Total state @@ -214,19 +206,15 @@ impl App { ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) } } - -fn render_dsl <'t, S> (state: &'t S, src: &str) -> Box> - where S: DslSymNs<'t, Box>> + DslExpNs<'t, Box>> -{ +fn render_dsl <'t> ( + state: &'t impl DslNs<'t, Box>>, + src: &str +) -> Box> { let sym_err: Option> = match state.from_sym(src) { - Ok(Some(value)) => return value, - Ok(None) => None, - Err(e) => Some(e), + Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), }; let exp_err = match state.from_exp(src) { - Ok(Some(value)) => return value, - Ok(None) => None, - Err(e) => Some(e), + 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)); @@ -238,92 +226,6 @@ fn render_dsl <'t, S> (state: &'t S, src: &str) -> Box> })) } -dsl_exp_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)), -}); - -dsl_exp_ns!(|app: App| -> Color { - ("g", n: u8) => - Color::Rgb(n, n, n), - ("rgb", r: u8, g: u8, b: u8) => - Color::Rgb(r, g, b), -}); - -dsl_sym_ns!(|app: App| -> Box> { - - ":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/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/browse/title" => { - let title = 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:", - }; - Box::new(Fill::x(Align::w(FieldV(Default::default(), title, 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), -}); 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)))) @@ -339,6 +241,477 @@ 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 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!(), + } + } +} + +dsl_ns!(|app: App| + + u8; + + isize; + + ItemTheme => { + ":_theme_stub" => Default::default() + }; + + 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), + }; + + 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), + }; + + 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", r: u8, g: u8, b: u8) => Color::Rgb(r, g, b), + }; + + 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/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/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), + }; +); + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//#[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, @@ -558,361 +931,3 @@ impl ScenesView for 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_sym_ns!(|app: App| -> isize { - ":_isize_stub" => -1 -}); -dsl_sym_ns!(|app: App| -> ItemTheme { - ":_theme_stub" => Default::default() -}); -dsl_sym_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_sym_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_sym_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.focused_editor() && matches!(app.selection(), Selection::TrackClip{..}), - ":focused/track" => !app.focused_editor() && matches!(app.selection(), Selection::Track(..)), - ":focused/scene" => !app.focused_editor() && matches!(app.selection(), Selection::Scene(..)), - ":focused/mix" => !app.focused_editor() && 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_sym_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_sym_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_sym_ns!(|app: App| -> Option { - ":editor/pitch" => Some((app.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8).into()) -}); -dsl_sym_ns!(|app: App| -> Option { - ":selected/scene" => app.selection().scene(), - ":selected/track" => app.selection().track(), -}); -dsl_sym_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 { -} -dsl_exp_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_exp_ns!(|app: App| -> DialogCommand {}); -dsl_exp_ns!(|app: App| -> ArrangementCommand {}); -dsl_exp_ns!(|app: App| -> ClockCommand {}); -dsl_exp_ns!(|app: App| -> SamplerCommand {}); -dsl_exp_ns!(|app: App| -> PoolCommand {}); -dsl_exp_ns!(|app: App| -> MidiEditCommand {}); -impl App { - pub fn focused_editor (&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!() - } -} -//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(); -//}); -#[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) - //} -//} -// diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 8ffcfcb1..6c0af4a4 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -1,5 +1,4 @@ pub(crate) use tek::*; -pub(crate) use std::sync::{Arc, RwLock}; pub(crate) use clap::{self, Parser, Subcommand}; /// Application entrypoint. @@ -56,16 +55,16 @@ impl Cli { let empty = &[] as &[&str]; let mut midi_ins = vec![]; let mut midi_outs = vec![]; - let mut tracks = vec![]; - let mut scenes = vec![]; + let tracks = vec![]; + let scenes = vec![]; let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re); let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re); let left_froms = Connect::collect(&self.left_from, empty, empty); let left_tos = Connect::collect(&self.left_to, empty, empty); let right_froms = Connect::collect(&self.right_from, empty, empty); let right_tos = Connect::collect(&self.right_to, empty, empty); - let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; - let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; + let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; + let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; Tui::new()?.run(&Jack::new_run(&name, move|jack|{ for (index, connect) in midi_froms.iter().enumerate() { midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?); @@ -74,7 +73,7 @@ impl Cli { midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?); }; let clock = Clock::new(&jack, self.bpm)?; - let mut app = App { + let app = App { config, jack: jack.clone(), color: ItemTheme::random(), diff --git a/crates/device/src/arranger/arranger_scenes.rs b/crates/device/src/arranger/arranger_scenes.rs index 0376e7eb..1c48531d 100644 --- a/crates/device/src/arranger/arranger_scenes.rs +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -25,7 +25,7 @@ impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } } -pub type SceneWith<'a, T: Send + Sync> = (usize, &'a Scene, usize, usize, T); +pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); impl AddScene for T {} diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 794646a7..cf483faa 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -139,10 +139,10 @@ impl TrackCommand { std::mem::swap(&mut color, &mut track.color); Ok(Some(Self::SetColor { color })) } - fn set_mute (track: &mut Track, value: Option) -> Perhaps { + fn set_mute (_track: &mut Track, _value: Option) -> Perhaps { todo!() } - fn set_solo (track: &mut Track, value: Option) -> Perhaps { + fn set_solo (_track: &mut Track, _value: Option) -> Perhaps { todo!() } fn set_rec (track: &mut Track, value: Option) -> Perhaps { @@ -155,10 +155,10 @@ impl TrackCommand { let value = value.unwrap_or(!current); Ok((value != current).then_some(Self::SetRec { value: Some(current) })) } - fn set_size (track: &mut Track, size: usize) -> Perhaps { + fn set_size (_track: &mut Track, _size: usize) -> Perhaps { todo!() } - fn set_zoom (track: &mut Track, zoom: usize) -> Perhaps { + fn set_zoom (_track: &mut Track, _zoom: usize) -> Perhaps { todo!() } fn stop (track: &mut Track) -> Perhaps { diff --git a/crates/device/src/clock/clock_view.rs b/crates/device/src/clock/clock_view.rs index 039b5ea4..b839a3ca 100644 --- a/crates/device/src/clock/clock_view.rs +++ b/crates/device/src/clock/clock_view.rs @@ -63,11 +63,11 @@ pub(crate) fn button_play_pause (playing: bool) -> impl Content { impl Default for ViewCache { fn default () -> Self { let mut beat = String::with_capacity(16); - write!(beat, "{}", Self::BEAT_EMPTY); + let _ = write!(beat, "{}", Self::BEAT_EMPTY); let mut time = String::with_capacity(16); - write!(time, "{}", Self::TIME_EMPTY); + let _ = write!(time, "{}", Self::TIME_EMPTY); let mut bpm = String::with_capacity(16); - write!(bpm, "{}", Self::BPM_EMPTY); + let _ = write!(bpm, "{}", Self::BPM_EMPTY); Self { beat: Memo::new(None, beat), time: Memo::new(None, time), diff --git a/crates/device/src/pool/pool_api.rs b/crates/device/src/pool/pool_api.rs index 90edd252..ffce5802 100644 --- a/crates/device/src/pool/pool_api.rs +++ b/crates/device/src/pool/pool_api.rs @@ -127,7 +127,7 @@ impl PoolClipCommand { Ok(Self::Add { index, clip }.execute(pool)?) } - fn export (pool: &mut Pool, index: usize, path: PathBuf) -> Perhaps { + fn export (_pool: &mut Pool, _index: usize, _path: PathBuf) -> Perhaps { todo!("export clip to midi file"); } @@ -155,7 +155,7 @@ impl PoolClipCommand { #[tengri_proc::command(Pool)] impl RenameCommand { - fn begin (pool: &mut Pool) -> Perhaps { + fn begin (_pool: &mut Pool) -> Perhaps { unreachable!(); } fn cancel (pool: &mut Pool) -> Perhaps { @@ -165,7 +165,7 @@ impl RenameCommand { return Ok(None) } fn confirm (pool: &mut Pool) -> Perhaps { - if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { + if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() { let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name })) @@ -173,7 +173,7 @@ impl RenameCommand { return Ok(None) } fn set (pool: &mut Pool, value: Arc) -> Perhaps { - if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { + if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() { pool.clips()[clip].write().unwrap().name = value; } return Ok(None) @@ -182,7 +182,7 @@ impl RenameCommand { #[tengri_proc::command(Pool)] impl CropCommand { - fn begin (pool: &mut Pool) -> Perhaps { + fn begin (_pool: &mut Pool) -> Perhaps { unreachable!() } fn cancel (pool: &mut Pool) -> Perhaps { diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs index 045398e1..1d30e44e 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -52,7 +52,7 @@ impl Sampler { impl SamplerCommand { fn record_toggle (sampler: &mut Sampler, slot: usize) -> Perhaps { let recording = sampler.recording.as_ref().map(|x|x.0); - Self::record_finish(sampler); + Self::record_finish(sampler)?; // autoslice: continue recording at next slot if recording != Some(slot) { Self::record_begin(sampler, slot) diff --git a/deps/tengri b/deps/tengri index d7884f62..4fc0db57 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit d7884f6289ac6c7a88f806f4453fb7c246c87f0b +Subproject commit 4fc0db577737ad1ce2601aa99d45248dba9a2d5f From c0add81ff4043703fb63a8b6f6b79e5359a2ad33 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 16 Aug 2025 16:28:43 +0300 Subject: [PATCH 2/2] wip: new error --- Justfile | 1 + crates/app/app.rs | 300 +++++++++++++++++++++++----------------------- deps/tengri | 2 +- 3 files changed, 155 insertions(+), 148 deletions(-) diff --git a/Justfile b/Justfile index 475359ee..569c216f 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,5 @@ 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 5e2c5495..4476a88e 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -111,10 +111,11 @@ impl Config { } pub fn load_defs (&mut self, dsl: impl Dsl) -> Usually<()> { dsl.each(|item|{ - match item.exp().head() { - Ok(Some("keys")) if let Some(id) = item.exp().tail().head()? => + println!("{item:?} {:?}", item.expr().head()); + match item.expr().head() { + Ok(Some("keys")) if let Some(id) = item.expr().tail().head()? => self.load_bind(id.into(), item), - Ok(Some("mode")) if let Some(id) = item.exp().tail().head()? => + Ok(Some("mode")) if let Some(id) = item.expr().tail().head()? => self.load_mode(id.into(), item), _ => return Err(format!("load_defs: unexpected: {item:?}").into()) } @@ -122,48 +123,46 @@ impl Config { } pub fn load_bind (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { let mut map = EventMap::new(); - 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(), + 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(), condition: None, description: None, source: None }); - } else if item.exp().head() == Ok(Some("see")) { - // TODO + Ok(()) } else { - return Err(format!("load_defs: unexpected: {item:?}").into()) - } + return Err(format!("load_bind: unexpected: {item:?}").into()) + })?; + self.binds.write().unwrap().insert(id, map); Ok(()) } pub fn load_mode (&mut self, id: Arc, item: impl Dsl) -> Usually<()> { let mut mode = Mode::default(); - item.exp().tail().tail()?.each(|item|Self::load_mode_one(&mut mode, item))?; + item.expr().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.exp().head() { + Ok(if let Ok(Some(key)) = item.expr().head() { match key { - "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()? { + "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()? { let mut submode = Mode::default(); - Self::load_mode_one(&mut submode, item.exp()?.tail()?.tail()?)?; + Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; mode.modes.insert(id.into(), submode); } else { return Err(format!("load_mode_one: incomplete: {item:?}").into()); }, - _ => mode.view.push(item.exp()?.unwrap().into()), + _ => mode.view.push(item.expr()?.unwrap().into()), } - } else if let Ok(Some(sym)) = item.sym() { - mode.view.push(sym.into()); + } else if let Ok(Some(word)) = item.word() { + mode.view.push(word.into()); } else { return Err(format!("load_mode_one: unexpected: {item:?}").into()); }) @@ -210,20 +209,24 @@ fn render_dsl <'t> ( state: &'t impl DslNs<'t, Box>>, src: &str ) -> Box> { - let sym_err: Option> = match state.from_sym(src) { + let err: Option> = match state.from(src) { Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), }; - 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:?}")))), - })) + 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("▀")))), + }) } fn wrap_dialog (dialog: impl Content) -> impl Content { @@ -253,11 +256,6 @@ handle!(TuiIn:|self: App, input|{ }); #[derive(Debug)] pub enum AppCommand { /* TODO */ } -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 } @@ -293,6 +291,9 @@ impl Dialog { } 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 @@ -371,27 +372,99 @@ impl App { } } -dsl_ns!(|app: App| +dsl_ns! { |app: App| - u8; + Box> => { + ("bold", value: bool, x: Box>) => Box::new(Tui::bold(value, x)), - isize; + ("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)), - ItemTheme => { - ":_theme_stub" => Default::default() - }; + ("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)), - u16 => { - ":w/sidebar" => app.project.w_sidebar(app.editor().is_some()), - ":h/sample-detail" => 6.max(app.height() as u16 * 3 / 9), - }; + ("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)), - 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), + ("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 => { @@ -414,6 +487,10 @@ dsl_ns!(|app: App| Selection::Mix), }; + ItemTheme => { + ":_theme_stub" => Default::default() + }; + Dialog => { ":dialog/none" => Dialog::None, ":dialog/options" => Dialog::Options, @@ -490,99 +567,28 @@ 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), + ("rgb", red: u8, green: u8, blue: u8) => Color::Rgb(red, green, blue), + ":color/bg" => Color::Rgb(28, 32, 36), + }; - 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/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/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), +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; +} /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/deps/tengri b/deps/tengri index 4fc0db57..1cc90548 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 4fc0db577737ad1ce2601aa99d45248dba9a2d5f +Subproject commit 1cc905485fe42f36be4802cec29fee155a3ae295