mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 15:56:57 +02:00
restruct: 92e
This commit is contained in:
parent
4ec2165e3d
commit
ae347eeef7
31 changed files with 2924 additions and 2663 deletions
130
src/app/bind.rs
Normal file
130
src/app/bind.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
use crate::*;
|
||||
/// A control axis.
|
||||
///
|
||||
/// ```
|
||||
/// let axis = tek::ControlAxis::X;
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||
X, Y, Z, I
|
||||
}
|
||||
|
||||
/// Collection of input bindings.
|
||||
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
|
||||
|
||||
pub(crate) fn load_bind (binds: &Binds, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
binds.write().unwrap().insert(name.as_ref().into(), Bind::load(body)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
|
||||
///
|
||||
/// ```
|
||||
/// let lang = "(@x (nop)) (@y (nop) (nop))";
|
||||
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
|
||||
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
|
||||
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
|
||||
/// ```
|
||||
#[derive(Debug)] pub struct Bind<E, C>(
|
||||
/// Map of each event (e.g. key combination) to
|
||||
/// all command expressions bound to it by
|
||||
/// all loaded input layers.
|
||||
pub BTreeMap<E, Vec<Binding<C>>>
|
||||
);
|
||||
|
||||
/// A sequence of zero or more commands (e.g. [AppCommand]),
|
||||
/// optionally filtered by [Condition] to form layers.
|
||||
///
|
||||
/// ```
|
||||
/// //FIXME: Why does it overflow?
|
||||
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
|
||||
/// ```
|
||||
#[derive(Debug, Clone)] pub struct Binding<C> {
|
||||
pub commands: Arc<[C]>,
|
||||
pub condition: Option<Condition>,
|
||||
pub description: Option<Arc<str>>,
|
||||
pub source: Option<Arc<PathBuf>>,
|
||||
}
|
||||
|
||||
/// Condition that must evaluate to true in order to enable an input layer.
|
||||
///
|
||||
/// ```
|
||||
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
|
||||
/// ```
|
||||
#[derive(Clone)] pub struct Condition(
|
||||
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
|
||||
);
|
||||
|
||||
impl Bind<TuiEvent, Arc<str>> {
|
||||
pub fn load (lang: &impl Language) -> Usually<Self> {
|
||||
let mut map = Bind::new();
|
||||
lang.each(|item|if item.expr().head() == Ok(Some("see")) {
|
||||
// TODO
|
||||
Ok(())
|
||||
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
||||
if let Some(key) = TuiEvent::named(item.expr()?.head()?)? {
|
||||
map.add(key, Binding {
|
||||
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
||||
condition: None,
|
||||
description: None,
|
||||
source: None
|
||||
});
|
||||
Ok(())
|
||||
} else if Some(":char") == item.expr()?.head()? {
|
||||
// TODO
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
||||
})?;
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
/// Default is always empty map regardless if `E` and `C` implement [Default].
|
||||
impl<E, C> Default for Bind<E, C> {
|
||||
fn default () -> Self { Self(Default::default()) }
|
||||
}
|
||||
impl<C: Default> Default for Binding<C> {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
commands: Default::default(),
|
||||
condition: Default::default(),
|
||||
description: Default::default(),
|
||||
source: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Clone + Ord, C> Bind<E, C> {
|
||||
/// Create a new event map
|
||||
pub fn new () -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Add a binding to an owned event map.
|
||||
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
||||
self.add(event, binding);
|
||||
self
|
||||
}
|
||||
/// Add a binding to an event map.
|
||||
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
|
||||
if !self.0.contains_key(&event) {
|
||||
self.0.insert(event.clone(), Default::default());
|
||||
}
|
||||
self.0.get_mut(&event).unwrap().push(binding);
|
||||
self
|
||||
}
|
||||
/// Return the binding(s) that correspond to an event.
|
||||
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
|
||||
self.0.get(event).map(|x|x.as_slice())
|
||||
}
|
||||
/// Return the first binding that corresponds to an event, considering conditions.
|
||||
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
|
||||
self.query(event)
|
||||
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl_debug!(Condition |self, w| { write!(w, "*") });
|
||||
260
src/app/cli.rs
Normal file
260
src/app/cli.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use crate::*;
|
||||
|
||||
/// The command-line interface descriptor.
|
||||
///
|
||||
/// ```
|
||||
/// let cli: tek::Cli = Default::default();
|
||||
///
|
||||
/// use clap::CommandFactory;
|
||||
/// tek::Cli::command().debug_assert();
|
||||
/// ```
|
||||
#[derive(Parser)]
|
||||
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
||||
#[derive(Debug, Default)] pub struct Cli {
|
||||
/// Pre-defined configuration modes.
|
||||
///
|
||||
/// TODO: Replace these with scripted configurations.
|
||||
#[command(subcommand)] pub action: Action,
|
||||
}
|
||||
|
||||
/// Application modes that can be passed to the mommand line interface.
|
||||
///
|
||||
/// ```
|
||||
/// let action: tek::Action = Default::default();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Subcommand, Default)] pub enum Action {
|
||||
/// Continue where you left off
|
||||
#[default] Resume,
|
||||
/// Run headlessly in current session.
|
||||
Headless,
|
||||
/// Show status of current session.
|
||||
Status,
|
||||
/// List known sessions.
|
||||
List,
|
||||
/// Continue work in a copy of the current session.
|
||||
Fork,
|
||||
/// Create a new empty session.
|
||||
New {
|
||||
/// Name of JACK client
|
||||
#[arg(short='n', long)] name: Option<String>,
|
||||
/// Whether to attempt to become transport master
|
||||
#[arg(short='Y', long, default_value_t = false)] sync_lead: bool,
|
||||
/// Whether to sync to external transport master
|
||||
#[arg(short='y', long, default_value_t = true)] sync_follow: bool,
|
||||
/// Initial tempo in beats per minute
|
||||
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
||||
/// Whether to include a transport toolbar (default: true)
|
||||
#[arg(short='c', long, default_value_t = true)] show_clock: bool,
|
||||
/// MIDI outs to connect to (multiple instances accepted)
|
||||
#[arg(short='I', long)] midi_from: Vec<String>,
|
||||
/// MIDI outs to connect to (multiple instances accepted)
|
||||
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
||||
/// MIDI ins to connect to (multiple instances accepted)
|
||||
#[arg(short='O', long)] midi_to: Vec<String>,
|
||||
/// MIDI ins to connect to (multiple instances accepted)
|
||||
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
||||
/// Audio outs to connect to left input
|
||||
#[arg(short='l', long)] left_from: Vec<String>,
|
||||
/// Audio outs to connect to right input
|
||||
#[arg(short='r', long)] right_from: Vec<String>,
|
||||
/// Audio ins to connect from left output
|
||||
#[arg(short='L', long)] left_to: Vec<String>,
|
||||
/// Audio ins to connect from right output
|
||||
#[arg(short='R', long)] right_to: Vec<String>,
|
||||
/// Tracks to creat
|
||||
#[arg(short='t', long)] tracks: Option<usize>,
|
||||
/// Scenes to create
|
||||
#[arg(short='s', long)] scenes: Option<usize>,
|
||||
},
|
||||
/// Import media as new session.
|
||||
Import,
|
||||
/// Show configuration.
|
||||
Config,
|
||||
/// Show version.
|
||||
Version,
|
||||
}
|
||||
|
||||
/// Command-line configuration.
|
||||
#[cfg(feature = "cli")]
|
||||
impl Cli {
|
||||
pub fn run (&self) -> Usually<()> {
|
||||
self.action.run(&Config::init_new(None)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
impl Action {
|
||||
fn run (&self, config: &Config) -> Usually<()> {
|
||||
use Action::*;
|
||||
match self {
|
||||
Version => tek_show_version(),
|
||||
Config => tek_print_config(&config),
|
||||
List => todo!("list sessions"),
|
||||
Resume => todo!("resume session"),
|
||||
New {
|
||||
name, bpm, tracks, scenes, sync_lead, sync_follow,
|
||||
midi_from: mf, midi_from_re: mfr, midi_to: mt, midi_to_re: mtr,
|
||||
left_from: lf, right_from: rf, left_to: lt, right_to: rt, ..
|
||||
} => {
|
||||
let name = name.as_ref().map_or("tek", |x|x.as_str());
|
||||
let jack = Jack::new(&name)?;
|
||||
let proj = tek_project_new(
|
||||
&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr
|
||||
)?;
|
||||
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
||||
proj.scenes_add(scenes.unwrap_or(0))?;
|
||||
//if matches!(self, Action::Status) {
|
||||
//// Show status and exit
|
||||
//tek_print_status(&proj);
|
||||
//return Ok(())
|
||||
//}
|
||||
// Initialize the app state
|
||||
let app = tek(&jack, proj, config, ":menu");
|
||||
//if matches!(self, Action::Headless) {
|
||||
//// TODO: Headless mode (daemon + client over IPC, then over network...)
|
||||
//println!("todo headless");
|
||||
//return Ok(())
|
||||
//}
|
||||
// Run the [Tui] and [Jack] threads with the [App] state.
|
||||
Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{
|
||||
// Between jack init and app's first cycle:
|
||||
jack.sync_lead(sync_lead, |mut state|{
|
||||
let clock = app.clock();
|
||||
clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||
state.position.bbt = Some(clock.bbt());
|
||||
state.position
|
||||
})?;
|
||||
jack.sync_follow(sync_follow)?;
|
||||
// FIXME: They don't work properly.
|
||||
Ok(app)
|
||||
})?)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tek_project_new (
|
||||
jack: &Jack,
|
||||
clock: Clock,
|
||||
left_from: &[impl AsRef<str>],
|
||||
left_to: &[impl AsRef<str>],
|
||||
right_from: &[impl AsRef<str>],
|
||||
right_to: &[impl AsRef<str>],
|
||||
midi_from: &[impl AsRef<str>],
|
||||
midi_to: &[impl AsRef<str>],
|
||||
midi_from_re: &[impl AsRef<str>],
|
||||
midi_to_re: &[impl AsRef<str>],
|
||||
) -> Usually<Arrangement> {
|
||||
// TODO: Collect audio IO:
|
||||
let empty = &[] as &[&str];
|
||||
let left_froms = Connect::collect(left_from, empty, empty);
|
||||
let left_tos = Connect::collect(left_to, empty, empty);
|
||||
let right_froms = Connect::collect(right_from, empty, empty);
|
||||
let right_tos = Connect::collect(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()];
|
||||
|
||||
// Create initial project:
|
||||
let mut project = Arrangement::new(
|
||||
jack,
|
||||
None,
|
||||
clock,
|
||||
vec![],
|
||||
vec![],
|
||||
Connect::collect(&midi_from, &[] as &[&str], &midi_from_re).iter().enumerate()
|
||||
.map(|(index, connect)|jack.midi_in(&format!("M/{index}"), &[connect.clone()]))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
Connect::collect(&midi_to, &[] as &[&str], &midi_to_re).iter().enumerate()
|
||||
.map(|(index, connect)|jack.midi_out(&format!("{index}/M"), &[connect.clone()]))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
);
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
pub fn tek_show_version () {
|
||||
println!("todo version");
|
||||
}
|
||||
|
||||
pub fn tek_print_config (config: &Config) {
|
||||
use ::ansi_term::Color::*;
|
||||
println!("{:?}", config.dirs);
|
||||
for (k, v) in config.views.read().unwrap().iter() {
|
||||
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
|
||||
}
|
||||
for (k, v) in config.binds.read().unwrap().iter() {
|
||||
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
||||
for (k, v) in v.0.iter() {
|
||||
print!("{} ", &Yellow.paint(match &k.0 {
|
||||
Event::Key(KeyEvent { modifiers, .. }) =>
|
||||
format!("{:>16}", format!("{modifiers}")),
|
||||
_ => unimplemented!()
|
||||
}));
|
||||
print!("{}", &Yellow.bold().paint(match &k.0 {
|
||||
Event::Key(KeyEvent { code, .. }) =>
|
||||
format!("{:<10}", format!("{code}")),
|
||||
_ => unimplemented!()
|
||||
}));
|
||||
for v in v.iter() {
|
||||
print!(" => {:?}", v.commands);
|
||||
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
||||
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
||||
//println!(" {:?}", v.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (k, v) in config.modes.read().unwrap().iter() {
|
||||
println!();
|
||||
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
||||
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
||||
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
||||
print!("\n{}", Blue.paint("KEYS"));
|
||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
for (k, v) in v.modes.read().unwrap().iter() {
|
||||
print!("{} {} {:?}",
|
||||
Blue.paint("MODE"),
|
||||
Green.bold().paint(format!("{k:<16}")),
|
||||
v.name);
|
||||
print!(" INFO={:?}",
|
||||
v.info);
|
||||
print!(" VIEW={:?}",
|
||||
v.view);
|
||||
println!(" KEYS={:?}",
|
||||
v.keys);
|
||||
}
|
||||
print!("{}", Blue.paint("VIEW"));
|
||||
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tek_print_status (project: &Arrangement) {
|
||||
println!("Name: {:?}", &project.name);
|
||||
println!("JACK: {:?}", &project.jack);
|
||||
println!("Buffer: {:?}", &project.clock.chunk);
|
||||
println!("Sample rate: {:?}", &project.clock.timebase.sr);
|
||||
println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq);
|
||||
println!("Tempo: {:?}", &project.clock.timebase.bpm);
|
||||
println!("Quantize: {:?}", &project.clock.quant);
|
||||
println!("Launch: {:?}", &project.clock.sync);
|
||||
println!("Playhead: {:?}us", &project.clock.playhead.usec);
|
||||
println!("Playhead: {:?}s", &project.clock.playhead.sample);
|
||||
println!("Playhead: {:?}p", &project.clock.playhead.pulse);
|
||||
println!("Started: {:?}", &project.clock.started);
|
||||
println!("Tracks:");
|
||||
for (i, t) in project.tracks.iter().enumerate() {
|
||||
println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width,
|
||||
&t.sequencer.play_clip, &t.sequencer.next_clip);
|
||||
}
|
||||
println!("Scenes:");
|
||||
for (i, t) in project.scenes.iter().enumerate() {
|
||||
println!(" Scene {i}: {} {:?}", &t.name, &t.clips);
|
||||
}
|
||||
println!("MIDI Ins: {:?}", &project.midi_ins);
|
||||
println!("MIDI Outs: {:?}", &project.midi_outs);
|
||||
println!("Audio Ins: {:?}", &project.audio_ins);
|
||||
println!("Audio Outs: {:?}", &project.audio_outs);
|
||||
// TODO git integration
|
||||
// TODO dawvert integration
|
||||
}
|
||||
192
src/app/config.rs
Normal file
192
src/app/config.rs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
use crate::{*, bind::*, view::*};
|
||||
|
||||
/// Configuration: mode, view, and bind definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let config = tek::Config::default();
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Some dizzle.
|
||||
/// // What indentation to use here lol?
|
||||
/// let source = stringify!((mode :menu (name Menu)
|
||||
/// (info Mode selector.) (keys :axis/y :confirm)
|
||||
/// (view (bg (g 0) (bsp/s :ports/out
|
||||
/// (bsp/n :ports/in
|
||||
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
|
||||
/// (fill :dialog/menu)))))))));
|
||||
/// // Add this definition to the config and try to load it.
|
||||
/// // A "mode" is basically a state machine
|
||||
/// // with associated input and output definitions.
|
||||
/// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap();
|
||||
/// ```
|
||||
#[derive(Default, Debug)] pub struct Config {
|
||||
/// XDG base directories of running user.
|
||||
pub dirs: BaseDirectories,
|
||||
/// Active collection of interaction modes.
|
||||
pub modes: Modes,
|
||||
/// Active collection of event bindings.
|
||||
pub binds: Binds,
|
||||
/// Active collection of view definitions.
|
||||
pub views: Views,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
const CONFIG_DIR: &'static str = "tek";
|
||||
const CONFIG_SUB: &'static str = "v0";
|
||||
const CONFIG: &'static str = "tek.edn";
|
||||
const DEFAULTS: &'static str = include_str!("../tek.edn");
|
||||
pub fn init_new (dirs: Option<BaseDirectories>) -> Usually<Self> {
|
||||
let mut config = Self::new(None);
|
||||
config.init()?;
|
||||
Ok(config)
|
||||
}
|
||||
/// Create a new app configuration from a set of XDG base directories,
|
||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||
let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB);
|
||||
let dirs = dirs.unwrap_or_else(default);
|
||||
Self { dirs, ..Default::default() }
|
||||
}
|
||||
/// Write initial contents of configuration.
|
||||
pub fn init (&mut self) -> Usually<()> {
|
||||
self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{
|
||||
cfgs.add(&dsl)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
/// Write initial contents of a configuration file.
|
||||
pub fn init_one (
|
||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||
) -> Usually<()> {
|
||||
if self.dirs.find_config_file(path).is_none() {
|
||||
//println!("Creating {path:?}");
|
||||
std::fs::write(self.dirs.place_config_file(path)?, defaults)?;
|
||||
}
|
||||
Ok(if let Some(path) = self.dirs.find_config_file(path) {
|
||||
//println!("Loading {path:?}");
|
||||
let src = std::fs::read_to_string(&path)?;
|
||||
src.as_str().each(move|item|each(self, item))?;
|
||||
} else {
|
||||
return Err(format!("{path}: not found").into())
|
||||
})
|
||||
}
|
||||
/// Add statements to configuration from [Dsl] source.
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> {
|
||||
dsl.each(|item|self.add_one(item))?;
|
||||
Ok(self)
|
||||
}
|
||||
fn add_one (&self, item: impl Language) -> Usually<()> {
|
||||
if let Some(expr) = item.expr()? {
|
||||
let head = expr.head()?;
|
||||
let tail = expr.tail()?;
|
||||
let name = tail.head()?;
|
||||
let body = tail.tail()?;
|
||||
//println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||
match head {
|
||||
Some("mode") if let Some(name) = name => load_mode(&self.modes, &name, &body)?,
|
||||
Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?,
|
||||
Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?,
|
||||
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
||||
}
|
||||
}
|
||||
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||
self.modes.clone().read().unwrap().get(mode.as_ref()).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_mode (modes: &Modes, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
let mut mode = Mode::default();
|
||||
body.each(|item|mode.add(item))?;
|
||||
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Collection of interaction modes.
|
||||
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||
|
||||
impl Mode<Arc<str>> {
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
/// Supported definitions:
|
||||
///
|
||||
/// - (name ...) -> name
|
||||
/// - (info ...) -> description
|
||||
/// - (keys ...) -> key bindings
|
||||
/// - (mode ...) -> submode
|
||||
/// - ... -> view
|
||||
///
|
||||
/// ```
|
||||
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
||||
/// mode.add("(name hello)").unwrap();
|
||||
/// ```
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<()> {
|
||||
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
|
||||
//println!("Mode::add: {head} {:?}", expr.tail());
|
||||
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
match head {
|
||||
"name" => self.add_name(tail)?,
|
||||
"info" => self.add_info(tail)?,
|
||||
"keys" => self.add_keys(tail)?,
|
||||
"mode" => self.add_mode(tail)?,
|
||||
_ => self.add_view(tail)?,
|
||||
};
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
self.add_view(word);
|
||||
} else {
|
||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
||||
})
|
||||
|
||||
//DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into()))
|
||||
//.word(|word|self.add_view(word))
|
||||
//.expr(|expr|expr.head(|head|{
|
||||
////println!("Mode::add: {head} {:?}", expr.tail());
|
||||
//let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
//match head {
|
||||
//"name" => self.add_name(tail),
|
||||
//"info" => self.add_info(tail),
|
||||
//"keys" => self.add_keys(tail)?,
|
||||
//"mode" => self.add_mode(tail)?,
|
||||
//_ => self.add_view(tail),
|
||||
//};
|
||||
//}))
|
||||
}
|
||||
|
||||
fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.name.push(src.into())))
|
||||
}
|
||||
fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.info.push(src.into())))
|
||||
}
|
||||
fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.view.push(src.into())))
|
||||
}
|
||||
fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?))
|
||||
}
|
||||
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(if let Some(id) = dsl.head()? {
|
||||
load_mode(&self.modes, &id, &dsl.tail())?;
|
||||
} else {
|
||||
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||
/// ```
|
||||
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
||||
pub path: PathBuf,
|
||||
pub name: Vec<D>,
|
||||
pub info: Vec<D>,
|
||||
pub view: Vec<D>,
|
||||
pub keys: Vec<D>,
|
||||
pub modes: Modes,
|
||||
}
|
||||
590
src/app/view.rs
Normal file
590
src/app/view.rs
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
use crate::*;
|
||||
|
||||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||
|
||||
/// Load custom view definition.
|
||||
pub(crate) fn load_view (views: &Views, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
views.write().unwrap().insert(name.as_ref().into(), body.src()?.unwrap_or_default().into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::view_logo();
|
||||
/// ```
|
||||
pub fn view_logo () -> impl Draw<Tui> {
|
||||
wh_exact(Some(32), Some(7), bold(true, fg(Rgb(240, 200, 180), south!{
|
||||
h_exact(1, ""),
|
||||
h_exact(1, ""),
|
||||
h_exact(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
|
||||
h_exact(1, east("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", east(fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
|
||||
h_exact(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
|
||||
})))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let x = std::sync::Arc::<std::sync::RwLock<String>>::default();
|
||||
/// let _ = tek::view_transport(true, x.clone(), x.clone(), x.clone());
|
||||
/// let _ = tek::view_transport(false, x.clone(), x.clone(), x.clone());
|
||||
/// ```
|
||||
pub fn view_transport (play: bool, bpm: &str, beat: &str, time: &str) -> impl Draw<Tui> {
|
||||
let theme = ItemTheme::G[96];
|
||||
bg(Black, east!(above(
|
||||
wh_full(origin_w(button_play_pause(play))),
|
||||
wh_full(origin_e(east!(
|
||||
field_h(theme, "BPM", bpm),
|
||||
field_h(theme, "Beat", beat),
|
||||
field_h(theme, "Time", time),
|
||||
)))
|
||||
)))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let x = std::sync::Arc::<std::sync::RwLock<String>>::default();
|
||||
/// let _ = tek::view_status(None, x.clone(), x.clone(), x.clone());
|
||||
/// let _ = tek::view_status(Some("".into()), x.clone(), x.clone(), x.clone());
|
||||
/// ```
|
||||
pub fn view_status (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
|
||||
let theme = ItemTheme::G[96];
|
||||
let sr = field_h(theme, "SR", sr);
|
||||
let buf = field_h(theme, "Buf", buf);
|
||||
let lat = field_h(theme, "Lat", lat);
|
||||
bg(Black, east!(above(
|
||||
wh_full(origin_w(sel.map(|sel|field_h(theme, "Selected", sel)))),
|
||||
wh_full(origin_e(east!(sr, buf, lat))),
|
||||
)))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::button_play_pause(true);
|
||||
/// ```
|
||||
pub fn button_play_pause (playing: bool) -> impl Draw<Tui> {
|
||||
let compact = true;//self.is_editing();
|
||||
bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
||||
either(compact,
|
||||
thunk(move|to: &mut Tui|w_exact(9, either(playing,
|
||||
fg(Rgb(0, 255, 0), " PLAYING "),
|
||||
fg(Rgb(255, 128, 0), " STOPPED "))
|
||||
).draw(to)),
|
||||
thunk(move|to: &mut Tui|w_exact(5, either(playing,
|
||||
fg(Rgb(0, 255, 0), south(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||
fg(Rgb(255, 128, 0), south(" ▗▄▖ ", " ▝▀▘ ",)))
|
||||
).draw(to)),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "track")] pub fn view_track_row_section (
|
||||
_theme: ItemTheme,
|
||||
button: impl Draw<Tui>,
|
||||
button_add: impl Draw<Tui>,
|
||||
content: impl Draw<Tui>,
|
||||
) -> impl Draw<Tui> {
|
||||
west(h_full(w_exact(4, origin_nw(button_add))),
|
||||
east(w_exact(20, h_full(origin_nw(button))), wh_full(origin_c(content))))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let bg = tengri::ratatui::style::Color::Red;
|
||||
/// let fg = tengri::ratatui::style::Color::Green;
|
||||
/// let _ = tek::view_wrap(bg, fg, "and then blue, too!");
|
||||
/// ```
|
||||
pub fn view_wrap (bg: Color, fg: Color, content: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
let left = fg_bg(bg, Reset, w_exact(1, y_repeat("▐")));
|
||||
let right = fg_bg(bg, Reset, w_exact(1, y_repeat("▌")));
|
||||
east(left, west(right, fg_bg(fg, bg, content)))
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::view_meter("", 0.0);
|
||||
/// let _ = tek::view_meters(&[0.0, 0.0]);
|
||||
/// ```
|
||||
pub fn view_meter <'a> (label: &'a str, value: f32) -> impl Draw<Tui> + 'a {
|
||||
let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value));
|
||||
let w = if value >= 0.0 { 13 }
|
||||
else if value >= -1.0 { 12 }
|
||||
else if value >= -2.0 { 11 }
|
||||
else if value >= -3.0 { 10 }
|
||||
else if value >= -4.0 { 9 }
|
||||
else if value >= -6.0 { 8 }
|
||||
else if value >= -9.0 { 7 }
|
||||
else if value >= -12.0 { 6 }
|
||||
else if value >= -15.0 { 5 }
|
||||
else if value >= -20.0 { 4 }
|
||||
else if value >= -25.0 { 3 }
|
||||
else if value >= -30.0 { 2 }
|
||||
else if value >= -40.0 { 1 }
|
||||
else { 0 };
|
||||
let c = if value >= 0.0 { Red }
|
||||
else if value >= -3.0 { Yellow }
|
||||
else { Green };
|
||||
south!(f, wh_exact(Some(w), Some(1), bg(c, ())))
|
||||
}
|
||||
|
||||
pub fn view_meters (values: &[f32;2]) -> impl Draw<Tui> + use<'_> {
|
||||
let left = format!("L/{:>+9.3}", values[0]);
|
||||
let right = format!("R/{:>+9.3}", values[1]);
|
||||
south(left, right)
|
||||
}
|
||||
|
||||
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
when(sample.is_some(), thunk(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
east!(
|
||||
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||
field_h(theme, "Start", format!("{:<8}", sample.start)),
|
||||
field_h(theme, "End", format!("{:<8}", sample.end)),
|
||||
field_h(theme, "Trans", "0"),
|
||||
field_h(theme, "Gain", format!("{}", sample.gain)),
|
||||
).draw(to)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
let a = thunk(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
w_exact(20, south!(
|
||||
w_full(origin_w(field_h(theme, "Name ", format!("{:<10}", sample.name.clone())))),
|
||||
w_full(origin_w(field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())))),
|
||||
w_full(origin_w(field_h(theme, "Start ", format!("{:<8}", sample.start)))),
|
||||
w_full(origin_w(field_h(theme, "End ", format!("{:<8}", sample.end)))),
|
||||
w_full(origin_w(field_h(theme, "Trans ", "0"))),
|
||||
w_full(origin_w(field_h(theme, "Gain ", format!("{}", sample.gain)))),
|
||||
)).draw(to)
|
||||
});
|
||||
|
||||
let b = thunk(|to: &mut Tui|fg(Red, south!(
|
||||
bold(true, "× No sample."),
|
||||
"[r] record",
|
||||
"[Shift-F9] import",
|
||||
)).draw(to));
|
||||
|
||||
either(sample.is_some(), a, b)
|
||||
}
|
||||
|
||||
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
||||
bold(true, fg(g(224), sample
|
||||
.map(|sample|{
|
||||
let sample = sample.read().unwrap();
|
||||
format!("Sample {}-{}", sample.start, sample.end)
|
||||
})
|
||||
.unwrap_or_else(||"No sample".to_string())))
|
||||
}
|
||||
|
||||
pub fn view_track_header (theme: ItemTheme, content: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
w_exact(12, bg(theme.darker.term, w_full(origin_e(content))))
|
||||
}
|
||||
|
||||
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
||||
-> impl Draw<Tui> + use<'a, T>
|
||||
{
|
||||
let ins = ports.len() as u16;
|
||||
let frame = Outer(true, Style::default().fg(g(96)));
|
||||
let iter = move||ports.iter();
|
||||
let names = iter_south(iter, move|port, index|h_full(origin_w(format!(" {index} {}", port.port_name()))));
|
||||
let field = field_v(theme, title, names);
|
||||
wh_exact(Some(20), Some(1 + ins), border(true, frame, wh_exact(Some(20), Some(1 + ins), field)))
|
||||
}
|
||||
|
||||
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
||||
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
|
||||
iter(items, move|(_index, name, connections, y, y2): Item<'a>, _| {
|
||||
y_push(y as u16, h_exact((y2-y) as u16,
|
||||
south(h_full(bold(true, fg_bg(fg, bg, origin_w(east(" ", name))))),
|
||||
iter(||connections.iter(), move|connect: &'a Connect, index|y_push(
|
||||
index as u16, h_exact(1, origin_w(bold(false, fg_bg(fg, bg, &connect.info))))
|
||||
)))))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_scenes_clips (
|
||||
scenes: impl ScenesSizes<'_>,
|
||||
tracks: impl TracksSizes<'_>,
|
||||
select: &Selection,
|
||||
editor: Option<&MidiEditor>,
|
||||
size: &Size,
|
||||
is_editing: bool,
|
||||
) -> impl Draw<Tui> {
|
||||
size.of(wh_full(above(wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h())))),
|
||||
thunk(|to: &mut Tui|{
|
||||
for (index, track, _, _) in tracks {
|
||||
let clips = view_track_clips(scenes, select, editor, index, track, is_editing);
|
||||
w_exact(track.width as u16, h_full(clips)).draw(to);
|
||||
}
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn view_track_clips (
|
||||
scenes: impl ScenesSizes<'_>,
|
||||
select: &Selection,
|
||||
editor: &Option<MidiEditor>,
|
||||
index: usize,
|
||||
track: &Track,
|
||||
is_editing: bool,
|
||||
) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
|
||||
for (scene_index, scene, ..) in scenes {
|
||||
|
||||
let (
|
||||
name, theme
|
||||
): (Arc<str>, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(index) {
|
||||
let clip = clip.read().unwrap();
|
||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||
} else {
|
||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||
};
|
||||
|
||||
let fg = theme.lightest.term;
|
||||
|
||||
let mut outline = theme.base.term;
|
||||
|
||||
let bg = if select.track() == Some(index) && select.scene() == Some(scene_index) {
|
||||
outline = theme.lighter.term;
|
||||
theme.light.term
|
||||
} else if select.track() == Some(index) || select.scene() == Some(scene_index) {
|
||||
outline = theme.darkest.term;
|
||||
theme.base.term
|
||||
} else {
|
||||
theme.dark.term
|
||||
};
|
||||
|
||||
let w = if select.track() == Some(index) && let Some(editor) = editor {
|
||||
(editor.size.w() as usize).max(24).max(track.width)
|
||||
} else {
|
||||
track.width
|
||||
} as u16;
|
||||
|
||||
let y = if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
||||
(editor.size.h() as usize).max(12)
|
||||
} else {
|
||||
Self::H_SCENE as usize
|
||||
} as u16;
|
||||
|
||||
let is_selected = is_editing && select.track() == Some(index) && select.scene() == Some(scene_index);
|
||||
|
||||
wh_exact(Some(w), Some(y), below(
|
||||
wh_full(Outer(true, Style::default().fg(outline))),
|
||||
wh_full(below(
|
||||
below(
|
||||
fg_bg(outline, bg, wh_full("")),
|
||||
wh_full(origin_nw(fg_bg(fg, bg, bold(true, &name)))),
|
||||
),
|
||||
wh_full(when(is_selected, editor.map(|e|e.view()))))))
|
||||
).draw(to);
|
||||
|
||||
}
|
||||
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_track_names (
|
||||
theme: ItemTheme,
|
||||
tracks: impl TracksSizes<'_>,
|
||||
track_count: usize,
|
||||
scene_count: usize,
|
||||
selected: &Selection,
|
||||
) -> impl Draw<Tui> {
|
||||
let button = south(
|
||||
button_3("t", "rack ", format!("{}{track_count}", selected.track()
|
||||
.map(|track|format!("{track}/")).unwrap_or_default()), false),
|
||||
button_3("s", "cene ", format!("{}{scene_count}", selected.scene()
|
||||
.map(|scene|format!("{scene}/")).unwrap_or_default()), false));
|
||||
let button_2 = south(
|
||||
button_2("T", "+", false),
|
||||
button_2("S", "+", false));
|
||||
view_track_row_section(theme, button, button_2, bg(theme.darker.term,
|
||||
h_exact(2, thunk(|to: &mut Tui|{
|
||||
for (index, track, x1, _x2) in tracks {
|
||||
x_push(x1 as u16, w_exact(track_width(index, track),
|
||||
bg(if selected.track() == Some(index) {
|
||||
track.color.light.term
|
||||
} else {
|
||||
track.color.base.term
|
||||
}, south(w_full(origin_nw(east(
|
||||
format!("·t{index:02} "),
|
||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||
))), ""))) ).draw(to);
|
||||
}
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn view_track_outputs (
|
||||
theme: ItemTheme,
|
||||
tracks: impl TracksSizes<'_>,
|
||||
midi_outs: impl Iterator<Item = ()>,
|
||||
) -> impl Draw<Tui> {
|
||||
view_track_row_section(theme,
|
||||
south(w_full(origin_w(button_2("o", "utput", false))),
|
||||
thunk(|to: &mut Tui|{
|
||||
for port in midi_outs {
|
||||
w_full(origin_w(port.port_name())).draw(to);
|
||||
}
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
})),
|
||||
button_2("O", "+", false),
|
||||
bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in tracks {
|
||||
let iter = ||track.sequencer.midi_outs.iter();
|
||||
let draw = |port: &MidiOutput, _|fg(Rgb(255, 255, 255),
|
||||
h_exact(1, bg(track.color.dark.term, w_full(origin_w(
|
||||
format!("·o{index:02} {}", port.port_name()))))));
|
||||
w_exact(track_width(index, track),
|
||||
origin_nw(h_full(iter_south(iter, draw)))).draw(to);
|
||||
}
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn view_track_inputs (
|
||||
theme: ItemTheme,
|
||||
tracks: impl TracksSizes<'_>,
|
||||
h: u16,
|
||||
) -> impl Draw<Tui> {
|
||||
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
||||
bg(theme.darker.term, origin_w(thunk(move|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in tracks {
|
||||
wh_exact(Some(track_width(index, track)), Some(h + 1),
|
||||
origin_nw(south(
|
||||
bg(track.color.base.term,
|
||||
w_full(origin_w(east!(
|
||||
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
|
||||
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
||||
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
||||
)))),
|
||||
iter_south(||track.sequencer.midi_ins.iter(),
|
||||
|port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
||||
w_full(origin_w(format!("·i{index:02} {}", port.port_name()))))))))
|
||||
.draw(to);
|
||||
}
|
||||
Ok(XYWH(0, 0, 0, 0))
|
||||
}))))
|
||||
}
|
||||
|
||||
pub fn view_scenes_names (
|
||||
scenes: &impl ScenesSizes<'_>
|
||||
) -> impl Draw<Tui> {
|
||||
w_exact(20, thunk(|to: &mut Tui|{
|
||||
for (index, scene, ..) in scenes {
|
||||
view_scene_name(index, scene).draw(to);
|
||||
}
|
||||
Ok(XYWH(1, 1, 1, 1))
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_scene_name (
|
||||
select: Option<&Selection>,
|
||||
editor: Option<&MidiEditor>,
|
||||
index: usize,
|
||||
scene: &Scene,
|
||||
editing: bool
|
||||
) -> impl Draw<Tui> {
|
||||
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
||||
7
|
||||
} else {
|
||||
Self::H_SCENE as u16
|
||||
};
|
||||
let a = w_full(origin_w(east(format!("·s{index:02} "),
|
||||
fg(g(255), bold(true, &scene.name)))));
|
||||
let b = when(select.scene() == Some(index) && editing,
|
||||
wh_full(origin_nw(south(
|
||||
editor.as_ref().map(|e|e.clip_status()),
|
||||
editor.as_ref().map(|e|e.edit_status())))));
|
||||
let c = if select.scene() == Some(index) {
|
||||
scene.color.light.term
|
||||
} else {
|
||||
scene.color.base.term
|
||||
};
|
||||
wh_exact(Some(20), Some(h), bg(c, origin_nw(south(a, b))))
|
||||
}
|
||||
|
||||
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
||||
}
|
||||
|
||||
pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
||||
}
|
||||
|
||||
pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
||||
}
|
||||
|
||||
pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
||||
}
|
||||
|
||||
pub fn view_track_per (
|
||||
tracks: impl TracksSizes<'_>
|
||||
) -> impl Draw<Tui> {
|
||||
iter_east(tracks, move|(index, track, x1, x2): (usize, &Track, usize, usize), _|{
|
||||
w_exact((x2 - x1) as u16, fg_bg(
|
||||
track.color.lightest.term,
|
||||
track.color.base.term,
|
||||
callback(index, track)))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_per_track () -> impl Draw<Tui> {}
|
||||
|
||||
pub fn view_per_track_top () -> impl Draw<Tui> {}
|
||||
|
||||
pub fn view_inputs (
|
||||
tracks: impl TracksSizes<'_>,
|
||||
midi_ins: &[MidiIn],
|
||||
) -> impl Draw<Tui> {
|
||||
let header = h_exact(1, view_inputs_header(tracks));
|
||||
south(header, thunk(|to: &mut Tui|{
|
||||
for (index, port) in midi_ins.iter().enumerate() {
|
||||
x_push(index as u16 * 10, h_exact(1, view_inputs_row(tracks, port))).draw(to);
|
||||
}
|
||||
todo!()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_inputs_header (
|
||||
tracks: impl TracksSizes<'_>,
|
||||
midi_ins: &[MidiIn],
|
||||
) -> impl Draw<Tui> {
|
||||
east(w_exact(20, origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false))),
|
||||
west(w_exact(4, button_2("I", "+", false)), thunk(move|to: &mut Tui|{
|
||||
for (_index, track, x1, _x2) in tracks {
|
||||
#[cfg(feature = "track")]
|
||||
x_push(x1 as u16, bg(track.color.dark.term, origin_w(w_exact(track.width as u16, east!(
|
||||
either(track.sequencer.monitoring, fg(Green, "mon "), "mon "),
|
||||
either(track.sequencer.recording, fg(Red, "rec "), "rec "),
|
||||
either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "),
|
||||
))))).draw(to);
|
||||
}
|
||||
todo!()
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn view_inputs_row (
|
||||
tracks: impl TracksSizes<'_>,
|
||||
port: ()
|
||||
) -> impl Draw<Tui> {
|
||||
east(w_exact(20, origin_w(east(" ● ", bold(true, fg(Rgb(255,255,255), port.port_name()))))),
|
||||
west(w_exact(4, ()), thunk(move|to: &mut Tui|{
|
||||
for (_index, track, _x1, _x2) in tracks {
|
||||
#[cfg(feature = "track")]
|
||||
bg(track.color.darker.term, origin_w(w_exact(track.width as u16, east!(
|
||||
either(track.sequencer.monitoring, fg(Green, " ● "), " · "),
|
||||
either(track.sequencer.recording, fg(Red, " ● "), " · "),
|
||||
either(track.sequencer.overdub, fg(Yellow, " ● "), " · "),
|
||||
)))).draw(to);
|
||||
}
|
||||
todo!()
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn view_outputs (
|
||||
theme: ItemTheme,
|
||||
tracks: impl TracksSizes<'_>,
|
||||
midi_outs: &[MidiOutput],
|
||||
height: u16,
|
||||
) -> impl Draw<Tui> {
|
||||
|
||||
let list = south(
|
||||
h_exact(1, w_full(origin_w(button_3(
|
||||
"o", "utput", format!("{}", midi_outs.len()), false
|
||||
)))),
|
||||
h_exact(height - 1, wh_full(origin_nw(thunk(|to: &mut Tui|{
|
||||
for (_index, port) in midi_outs.iter().enumerate() {
|
||||
h_exact(1,w_full(east(
|
||||
origin_w(east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name())))),
|
||||
w_full(origin_e(format!("{}/{} ",
|
||||
port.port().get_connections().len(),
|
||||
port.connections.len())))))).draw(to);
|
||||
for (index, conn) in port.connections.iter().enumerate() {
|
||||
h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info()))))
|
||||
.draw(to);
|
||||
}
|
||||
}
|
||||
todo!();
|
||||
}))))
|
||||
);
|
||||
|
||||
h_exact(height, view_track_row_section(theme, list, button_2("O", "+", false),
|
||||
bg(theme.darker.term, origin_w(w_full(
|
||||
thunk(|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in tracks {
|
||||
w_exact(track_width(index, track),
|
||||
thunk(|to: &mut Tui|{
|
||||
h_exact(1, origin_w(east(
|
||||
either(true, fg(Green, "play "), "play "),
|
||||
either(false, fg(Yellow, "solo "), "solo "),
|
||||
))).draw(to);
|
||||
for (_index, port) in midi_outs.iter().enumerate() {
|
||||
h_exact(1, origin_w(east(
|
||||
either(true, fg(Green, " ● "), " · "),
|
||||
either(false, fg(Yellow, " ● "), " · "),
|
||||
))).draw(to);
|
||||
for (_index, _conn) in port.connections.iter().enumerate() {
|
||||
h_exact(1, w_full("")).draw(to);
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
})
|
||||
).draw(to);
|
||||
}
|
||||
todo!()
|
||||
})
|
||||
)))
|
||||
))
|
||||
}
|
||||
|
||||
pub fn view_track_devices (
|
||||
theme: ItemTheme,
|
||||
tracks: &impl TracksSizes<'_>,
|
||||
track: Option<&Track>,
|
||||
h: u16,
|
||||
) -> impl Draw<Tui> {
|
||||
view_track_row_section(theme,
|
||||
button_3("d", "evice", format!("{}", track.map(|t|t.devices.len()).unwrap_or(0)), false),
|
||||
button_2("D", "+", false),
|
||||
iter(tracks, move|(_, track, _x1, _x2), index| wh_exact(
|
||||
Some(track_width(index, track)),
|
||||
Some(h + 1),
|
||||
bg(track.color.dark.term, origin_nw(iter_south(move||0..h,
|
||||
|_, _index|wh_exact(Some(track.width as u16), Some(2),
|
||||
fg_bg(
|
||||
ItemTheme::G[32].lightest.term,
|
||||
ItemTheme::G[32].dark.term,
|
||||
origin_nw(format!(" · {}", "--"))
|
||||
)
|
||||
)))))))
|
||||
}
|
||||
|
||||
pub fn per_track <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
per_track_top(tracks, move|index, track|h_full(origin_y(callback(index, track))))
|
||||
}
|
||||
|
||||
pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
origin_x(bg(Reset, iter_east(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
w_exact((x2 - x1) as u16, fg_bg(
|
||||
track.color.lightest.term,
|
||||
track.color.base.term,
|
||||
callback(index, track))) })))
|
||||
}
|
||||
|
||||
pub fn field_h <T: Screen> (theme: ItemTheme, head: impl Draw<T>, body: impl Draw<T>) -> impl Draw<T> {
|
||||
}
|
||||
|
||||
pub fn field_v <T: Screen> (theme: ItemTheme, head: impl Draw<T>, body: impl Draw<T>) -> impl Draw<T> {
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue