mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 15:56:57 +02:00
refactor: Mode, View, Bind
This commit is contained in:
parent
51faa82d74
commit
701a651fbd
21 changed files with 901 additions and 880 deletions
|
|
@ -3,9 +3,7 @@ pub mod audio; #[allow(unused)] pub use self::audio::*;
|
||||||
pub mod bind; pub use self::bind::*;
|
pub mod bind; pub use self::bind::*;
|
||||||
pub mod cli; pub use self::cli::*;
|
pub mod cli; pub use self::cli::*;
|
||||||
pub mod config; pub use self::config::*;
|
pub mod config; pub use self::config::*;
|
||||||
pub mod view; pub use self::view::*;
|
|
||||||
pub mod draw; pub use self::draw::*;
|
pub mod draw; pub use self::draw::*;
|
||||||
pub mod mode; pub use self::mode::*;
|
|
||||||
pub mod size; pub use self::size::*;
|
pub mod size; pub use self::size::*;
|
||||||
|
|
||||||
/// Total application state.
|
/// Total application state.
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,41 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
tui_keys!(|self: App, input| {
|
tui_keys!(|self: App, input| {
|
||||||
todo!()
|
let commands = tek_commands_collect(self, input)?;
|
||||||
|
let results = tek_commands_execute(self, commands)?;
|
||||||
|
self.history.extend(results.into_iter());
|
||||||
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
/// A control axis.
|
fn tek_commands_collect (app: &App, input: &TuiEvent)
|
||||||
///
|
-> Usually<Vec<AppCommand>>
|
||||||
/// ```
|
{
|
||||||
/// let axis = tek::ControlAxis::X;
|
let mut commands = vec![];
|
||||||
/// ```
|
for id in app.mode.keys.iter() {
|
||||||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||||
X, Y, Z, I
|
&& let Some(bindings) = event_map.query(input) {
|
||||||
|
for binding in bindings {
|
||||||
|
for command in binding.commands.iter() {
|
||||||
|
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||||
|
commands.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(commands)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tek_commands_execute (app: &mut App, commands: Vec<AppCommand>)
|
||||||
|
-> Usually<Vec<(AppCommand, Option<AppCommand>)>>
|
||||||
|
{
|
||||||
|
let mut history = vec![];
|
||||||
|
for command in commands.into_iter() {
|
||||||
|
let result = command.act(app);
|
||||||
|
match result { Err(err) => { history.push((command, None)); return Err(err) }
|
||||||
|
Ok(undo) => { history.push((command, undo)); } };
|
||||||
|
}
|
||||||
|
Ok(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collection of input bindings.
|
/// Collection of input bindings.
|
||||||
|
|
@ -91,6 +116,7 @@ impl Bind<TuiEvent, Arc<str>> {
|
||||||
impl<E, C> Default for Bind<E, C> {
|
impl<E, C> Default for Bind<E, C> {
|
||||||
fn default () -> Self { Self(Default::default()) }
|
fn default () -> Self { Self(Default::default()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Default> Default for Binding<C> {
|
impl<C: Default> Default for Binding<C> {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -158,38 +184,14 @@ impl<'a> Namespace<'a, AppCommand> for App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//impl_handle!(TuiIn: |self: App, input|{
|
/// A control axis.
|
||||||
//let commands = tek_collect_commands(self, input)?;
|
///
|
||||||
//let history = tek_execute_commands(self, commands)?;
|
/// ```
|
||||||
//self.history.extend(history.into_iter());
|
/// let axis = tek::ControlAxis::X;
|
||||||
//Ok(None)
|
/// ```
|
||||||
//});
|
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||||
|
X, Y, Z, I
|
||||||
|
}
|
||||||
|
|
||||||
//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually<Vec<AppCommand>> {
|
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||||
//let mut commands = vec![];
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
//for id in app.mode.keys.iter() {
|
|
||||||
//if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
|
||||||
//&& let Some(bindings) = event_map.query(input.event()) {
|
|
||||||
//for binding in bindings {
|
|
||||||
//for command in binding.commands.iter() {
|
|
||||||
//if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
|
||||||
//commands.push(command)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(commands)
|
|
||||||
//}
|
|
||||||
|
|
||||||
//fn tek_execute_commands (
|
|
||||||
//app: &mut App, commands: Vec<AppCommand>
|
|
||||||
//) -> Usually<Vec<(AppCommand, Option<AppCommand>)>> {
|
|
||||||
//let mut history = vec![];
|
|
||||||
//for command in commands.into_iter() {
|
|
||||||
//let result = command.act(app);
|
|
||||||
//match result { Err(err) => { history.push((command, None)); return Err(err) }
|
|
||||||
//Ok(undo) => { history.push((command, undo)); } };
|
|
||||||
//}
|
|
||||||
//Ok(history)
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,7 @@ pub fn tek_print_config (config: &Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (k, v) in config.modes.read().unwrap().iter() {
|
config.modes.for_each(|k, v|{
|
||||||
println!();
|
println!();
|
||||||
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
|
||||||
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
|
||||||
|
|
@ -216,22 +216,16 @@ pub fn tek_print_config (config: &Config) {
|
||||||
print!("\n{}", Blue.paint("KEYS"));
|
print!("\n{}", Blue.paint("KEYS"));
|
||||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
println!();
|
println!();
|
||||||
for (k, v) in v.modes.read().unwrap().iter() {
|
v.modes.for_each(|k, v|{
|
||||||
print!("{} {} {:?}",
|
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
||||||
Blue.paint("MODE"),
|
print!( " INFO={:?}", v.info);
|
||||||
Green.bold().paint(format!("{k:<16}")),
|
print!( " VIEW={:?}", v.view);
|
||||||
v.name);
|
println!(" KEYS={:?}", v.keys);
|
||||||
print!(" INFO={:?}",
|
});
|
||||||
v.info);
|
|
||||||
print!(" VIEW={:?}",
|
|
||||||
v.view);
|
|
||||||
println!(" KEYS={:?}",
|
|
||||||
v.keys);
|
|
||||||
}
|
|
||||||
print!("{}", Blue.paint("VIEW"));
|
print!("{}", Blue.paint("VIEW"));
|
||||||
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
println!();
|
println!();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tek_print_status (project: &Arrangement) {
|
pub fn tek_print_status (project: &Arrangement) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{*, bind::*, view::*};
|
use crate::*;
|
||||||
|
|
||||||
/// Configuration: mode, view, and bind definitions.
|
/// Configuration: mode, view, and bind definitions.
|
||||||
///
|
///
|
||||||
|
|
@ -48,12 +48,14 @@ impl Config {
|
||||||
config.init()?;
|
config.init()?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new app configuration from a set of XDG base directories,
|
/// Create a new app configuration from a set of XDG base directories,
|
||||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||||
let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB);
|
let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB);
|
||||||
let dirs = dirs.unwrap_or_else(default);
|
let dirs = dirs.unwrap_or_else(default);
|
||||||
Self { dirs, ..Default::default() }
|
Self { dirs, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write initial contents of configuration.
|
/// Write initial contents of configuration.
|
||||||
pub fn init (&mut self) -> Usually<()> {
|
pub fn init (&mut self) -> Usually<()> {
|
||||||
self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{
|
self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{
|
||||||
|
|
@ -62,6 +64,7 @@ impl Config {
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write initial contents of a configuration file.
|
/// Write initial contents of a configuration file.
|
||||||
pub fn init_one (
|
pub fn init_one (
|
||||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||||
|
|
@ -78,11 +81,13 @@ impl Config {
|
||||||
return Err(format!("{path}: not found").into())
|
return Err(format!("{path}: not found").into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add statements to configuration from [Dsl] source.
|
/// Add statements to configuration from [Dsl] source.
|
||||||
pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> {
|
pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> {
|
||||||
dsl.each(|item|self.add_one(item))?;
|
dsl.each(|item|self.add_one(item))?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_one (&self, item: impl Language) -> Usually<()> {
|
fn add_one (&self, item: impl Language) -> Usually<()> {
|
||||||
if let Some(expr) = item.expr()? {
|
if let Some(expr) = item.expr()? {
|
||||||
let head = expr.head()?;
|
let head = expr.head()?;
|
||||||
|
|
@ -91,7 +96,7 @@ impl Config {
|
||||||
let body = tail.tail()?;
|
let body = tail.tail()?;
|
||||||
//println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
//println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||||
match head {
|
match head {
|
||||||
Some("mode") if let Some(name) = name => load_mode(&self.modes, &name, &body)?,
|
Some("mode") if let Some(name) = name => self.modes.add(&name, &body)?,
|
||||||
Some("keys") if let Some(name) = name => load_bind(&self.binds, &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)?,
|
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())
|
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
||||||
|
|
@ -101,7 +106,12 @@ impl Config {
|
||||||
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||||
self.modes.clone().read().unwrap().get(mode.as_ref()).cloned()
|
self.modes.get(mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod views; pub use self::views::*;
|
||||||
|
mod modes; pub use self::modes::*;
|
||||||
|
mod mode; pub use self::mode::*;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub(crate) fn load_mode (modes: &Modes, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
/// Group of view and keys definitions.
|
||||||
let mut mode = Mode::default();
|
///
|
||||||
body.each(|item|mode.add(item))?;
|
/// ```
|
||||||
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||||
Ok(())
|
/// ```
|
||||||
|
#[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,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collection of interaction modes.
|
|
||||||
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
|
||||||
|
|
||||||
impl Mode<Arc<str>> {
|
impl Mode<Arc<str>> {
|
||||||
/// Add a definition to the mode.
|
/// Add a definition to the mode.
|
||||||
///
|
///
|
||||||
|
|
@ -71,23 +75,9 @@ impl Mode<Arc<str>> {
|
||||||
}
|
}
|
||||||
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||||
Ok(Some(if let Some(id) = dsl.head()? {
|
Ok(Some(if let Some(id) = dsl.head()? {
|
||||||
load_mode(&self.modes, &id, &dsl.tail())?;
|
self.modes.add(&id, &dsl.tail())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
|
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,
|
|
||||||
}
|
|
||||||
27
src/app/config/modes.rs
Normal file
27
src/app/config/modes.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Collection of UI modes.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Modes(
|
||||||
|
Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Modes {
|
||||||
|
pub fn add (&self, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||||
|
let mut mode = Mode::default();
|
||||||
|
body.each(|item|mode.add(item))?;
|
||||||
|
self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||||
|
self.0.read().unwrap().get(name.as_ref()).cloned()
|
||||||
|
}
|
||||||
|
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode<Arc<str>>)->T) {
|
||||||
|
for (k, v) in self.0.read().unwrap().iter() {
|
||||||
|
let _ = ator(k.as_ref(), v.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn len (&self) -> usize {
|
||||||
|
self.0.read().unwrap().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/app/config/views.rs
Normal file
10
src/app/config/views.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
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(())
|
||||||
|
}
|
||||||
615
src/app/draw.rs
615
src/app/draw.rs
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
tui_view!(|self: App| {
|
||||||
|
""
|
||||||
|
});
|
||||||
|
|
||||||
/// The [Draw] implementation for [App] handles the loaded view,
|
/// The [Draw] implementation for [App] handles the loaded view,
|
||||||
/// which is defined in terms of [dizzle] DSL.
|
/// which is defined in terms of [dizzle] DSL.
|
||||||
///
|
///
|
||||||
|
|
@ -137,10 +141,10 @@ pub fn draw_dialog (
|
||||||
pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App)
|
pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App)
|
||||||
-> Usually<XYWH<u16>>
|
-> Usually<XYWH<u16>>
|
||||||
{
|
{
|
||||||
let modes = state.config.modes.clone();
|
let height = (state.config.modes.len() * 2) as u16;
|
||||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
|
||||||
h_exact(height, w_min(Some(30), thunk(move |to: &mut Tui|{
|
h_exact(height, w_min(Some(30), thunk(move |to: &mut Tui|{
|
||||||
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
|
let mut index = 0;
|
||||||
|
state.config.modes.for_each(|id, profile| {
|
||||||
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
||||||
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||||
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||||
|
|
@ -149,12 +153,613 @@ pub fn draw_templates (to: &mut Tui, frags: std::str::Split<&str>, state: &App)
|
||||||
let field_name = w_full(origin_w(fg(fg1, name)));
|
let field_name = w_full(origin_w(fg(fg1, name)));
|
||||||
let field_id = w_full(origin_e(fg(fg2, id)));
|
let field_id = w_full(origin_e(fg(fg2, id)));
|
||||||
let field_info = w_full(origin_w(info));
|
let field_info = w_full(origin_w(info));
|
||||||
y_push((2 * index) as u16,
|
let _ = y_push((2 * index) as u16,
|
||||||
h_exact(2, w_full(bg(b, south(
|
h_exact(2, w_full(bg(b, south(
|
||||||
above(field_name, field_id),
|
above(field_name, field_id),
|
||||||
field_info
|
field_info
|
||||||
))))).draw(to);
|
))))).draw(to);
|
||||||
}
|
index += 1;
|
||||||
|
});
|
||||||
Ok(to.area().into())
|
Ok(to.area().into())
|
||||||
}))).draw(to)
|
}))).draw(to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// 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 = "";
|
||||||
|
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// ```
|
||||||
|
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, false))),
|
||||||
|
wh_full(origin_e(east!(
|
||||||
|
field_h(theme, "BPM", bpm),
|
||||||
|
field_h(theme, "Beat", beat),
|
||||||
|
field_h(theme, "Time", time),
|
||||||
|
)))
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let x = "";
|
||||||
|
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// ```
|
||||||
|
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, true);
|
||||||
|
/// let _ = tek::button_play_pause(true, false);
|
||||||
|
/// let _ = tek::button_play_pause(false, true);
|
||||||
|
/// let _ = tek::button_play_pause(false, false);
|
||||||
|
/// ```
|
||||||
|
pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw<Tui> {
|
||||||
|
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 <'a, S: ScenesSizes<'a>> (
|
||||||
|
scenes: impl Fn()->S,
|
||||||
|
tracks: impl TracksSizes<'a>,
|
||||||
|
select: &Selection,
|
||||||
|
editor: Option<&MidiEditor>,
|
||||||
|
size: &Size,
|
||||||
|
editing: bool,
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
let status = wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h()))));
|
||||||
|
let tracks = iter_once(tracks, move|(track_index, track, _, _), _| {
|
||||||
|
let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| {
|
||||||
|
let (name, theme): (Arc<str>, ItemTheme) = scene_name_theme(scene, track_index);
|
||||||
|
let f = theme.lightest.term;
|
||||||
|
let (b, o) = scene_bg(theme, select, track_index, scene_index);
|
||||||
|
let w = scene_w(track, select, track_index, editor);
|
||||||
|
let y = scene_y(select, scene_index, editor);
|
||||||
|
let is_selected = scene_sel(select, track_index, scene_index, editing);
|
||||||
|
wh_exact(Some(w), Some(y), below(
|
||||||
|
wh_full(Outer(true, Style::default().fg(o))),
|
||||||
|
wh_full(below(
|
||||||
|
below(
|
||||||
|
fg_bg(o, b, wh_full("")),
|
||||||
|
wh_full(origin_nw(fg_bg(f, b, bold(true, name)))),
|
||||||
|
),
|
||||||
|
wh_full(when(is_selected, editor.map(|e|e.view())))))))
|
||||||
|
});
|
||||||
|
w_exact(track.width as u16, h_full(scenes))
|
||||||
|
});
|
||||||
|
|
||||||
|
return size.of(wh_full(above(status, tracks)));
|
||||||
|
|
||||||
|
fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
||||||
|
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||||
|
let clip = clip.read().unwrap();
|
||||||
|
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||||
|
} else {
|
||||||
|
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene_bg (
|
||||||
|
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
||||||
|
) -> (Color, Color) {
|
||||||
|
let mut outline = theme.base.term;
|
||||||
|
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
||||||
|
outline = theme.lighter.term;
|
||||||
|
theme.light.term
|
||||||
|
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
||||||
|
outline = theme.darkest.term;
|
||||||
|
theme.base.term
|
||||||
|
} else {
|
||||||
|
theme.dark.term
|
||||||
|
}, outline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene_w (
|
||||||
|
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
||||||
|
) -> u16 {
|
||||||
|
if select.track() == Some(track_index) && let Some(editor) = editor {
|
||||||
|
(editor.size.w() as usize).max(24).max(track.width) as u16
|
||||||
|
} else {
|
||||||
|
track.width as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene_y (
|
||||||
|
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
||||||
|
) -> u16 {
|
||||||
|
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
||||||
|
editor.size.h().max(12)
|
||||||
|
} else {
|
||||||
|
H_SCENE as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
||||||
|
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = &MidiOutput>,
|
||||||
|
) -> 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<'_>, height: 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(height + 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<'_>,
|
||||||
|
select: &Selection,
|
||||||
|
editor: Option<&MidiEditor>,
|
||||||
|
editing: bool,
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
w_exact(20, thunk(move |to: &mut Tui|{
|
||||||
|
for (index, scene, ..) in scenes {
|
||||||
|
view_scene_name(select, editor, index, scene, editing).draw(to);
|
||||||
|
}
|
||||||
|
Ok(XYWH(1, 1, 1, 1))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_name (
|
||||||
|
select: &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 {
|
||||||
|
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 <'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> {
|
||||||
|
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: &[MidiInput]) -> impl Draw<Tui> {
|
||||||
|
let title_1 = wh_exact(Some(20), Some(1), origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false)));
|
||||||
|
let title_2 = wh_exact(Some(4), Some(1), w_exact(4, button_2("I", "+", false)));
|
||||||
|
east(title_1, west(title_2, thunk(move|to: &mut Tui|{
|
||||||
|
for (_index, track, x1, _x2) in tracks {
|
||||||
|
south(
|
||||||
|
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 "),
|
||||||
|
))))),
|
||||||
|
thunk(move |to: &mut Tui|{
|
||||||
|
for (index, port) in midi_ins.iter().enumerate() {
|
||||||
|
x_push(index as u16 * 10, h_exact(1, 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|{
|
||||||
|
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!()
|
||||||
|
}))))).draw(to);
|
||||||
|
}
|
||||||
|
todo!()
|
||||||
|
})
|
||||||
|
).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_once(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> {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sessions () -> impl Draw<Tui> {
|
||||||
|
let h = 6;
|
||||||
|
let w = Some(30);
|
||||||
|
let f = Rgb(224, 192, 128);
|
||||||
|
h_exact(h, w_min(w, thunk(move |to: &mut Tui|{
|
||||||
|
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||||
|
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||||
|
let y = (2 * index) as u16;
|
||||||
|
let h = 2;
|
||||||
|
y_push(y, &h_exact(h, w_full(bg(b, origin_w(fg(f, name)))))).draw(to);
|
||||||
|
}
|
||||||
|
Ok(to.area().into())
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||||
|
w_full(origin_w(field_v(ItemTheme::default(),
|
||||||
|
match state.dialog.browser_target().unwrap() {
|
||||||
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
|
}, h_exact(1, fg(g(96), x_repeat("🭻")))
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||||
|
let selected = state.dialog.device_kind().unwrap();
|
||||||
|
south(bold(true, "Add device"), iter_south(
|
||||||
|
move||device_kinds().iter(),
|
||||||
|
move|_label: &&'static str, i|{
|
||||||
|
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||||
|
let l = if i == selected { "[ " } else { " " };
|
||||||
|
let r = if i == selected { " ]" } else { " " };
|
||||||
|
w_full(bg(b, east(l, west(r, "FIXME device name")))) }))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
||||||
def_sizes_iter!(ScenesSizes => Scene);
|
def_sizes_iter!(ScenesSizes => Scene);
|
||||||
def_sizes_iter!(TracksSizes => Track);
|
def_sizes_iter!(TracksSizes => Track);
|
||||||
|
|
||||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
|
||||||
track.width as u16
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasWidth {
|
pub trait HasWidth {
|
||||||
const MIN_WIDTH: usize;
|
const MIN_WIDTH: usize;
|
||||||
/// Increment track width.
|
/// Increment track width.
|
||||||
|
|
@ -27,31 +23,3 @@ pub trait HasWidth {
|
||||||
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
||||||
fn width_dec (&mut self);
|
fn width_dec (&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasClipsSize for App {
|
|
||||||
fn clips_size (&self) -> &Size { &self.project.size_inner }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasTrackScroll for App {
|
|
||||||
fn track_scroll (&self) -> usize {
|
|
||||||
self.project.track_scroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasSceneScroll for App {
|
|
||||||
fn scene_scroll (&self) -> usize {
|
|
||||||
self.project.scene_scroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScenesView for App {
|
|
||||||
fn w_mid (&self) -> u16 {
|
|
||||||
(self.size.w() as u16).saturating_sub(self.w_side())
|
|
||||||
}
|
|
||||||
fn w_side (&self) -> u16 {
|
|
||||||
20
|
|
||||||
}
|
|
||||||
fn h_scenes (&self) -> u16 {
|
|
||||||
(self.size.h() as u16).saturating_sub(20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
614
src/app/view.rs
614
src/app/view.rs
|
|
@ -1,614 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
tui_view!(|self: App| {
|
|
||||||
""
|
|
||||||
});
|
|
||||||
|
|
||||||
/// 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 = "";
|
|
||||||
/// let _ = tek::view_transport(true, x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// let _ = tek::view_transport(false, x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// ```
|
|
||||||
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, false))),
|
|
||||||
wh_full(origin_e(east!(
|
|
||||||
field_h(theme, "BPM", bpm),
|
|
||||||
field_h(theme, "Beat", beat),
|
|
||||||
field_h(theme, "Time", time),
|
|
||||||
)))
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let x = "";
|
|
||||||
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// ```
|
|
||||||
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, true);
|
|
||||||
/// let _ = tek::button_play_pause(true, false);
|
|
||||||
/// let _ = tek::button_play_pause(false, true);
|
|
||||||
/// let _ = tek::button_play_pause(false, false);
|
|
||||||
/// ```
|
|
||||||
pub fn button_play_pause (playing: bool, compact: bool) -> impl Draw<Tui> {
|
|
||||||
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 <'a, S: ScenesSizes<'a>> (
|
|
||||||
scenes: impl Fn()->S,
|
|
||||||
tracks: impl TracksSizes<'a>,
|
|
||||||
select: &Selection,
|
|
||||||
editor: Option<&MidiEditor>,
|
|
||||||
size: &Size,
|
|
||||||
editing: bool,
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
let status = wh_full(origin_se(fg(Green, format!("{}x{}", size.w(), size.h()))));
|
|
||||||
let tracks = iter_once(tracks, move|(track_index, track, _, _), _| {
|
|
||||||
let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| {
|
|
||||||
let (name, theme): (Arc<str>, ItemTheme) = scene_name_theme(scene, track_index);
|
|
||||||
let f = theme.lightest.term;
|
|
||||||
let (b, o) = scene_bg(theme, select, track_index, scene_index);
|
|
||||||
let w = scene_w(track, select, track_index, editor);
|
|
||||||
let y = scene_y(select, scene_index, editor);
|
|
||||||
let is_selected = scene_sel(select, track_index, scene_index, editing);
|
|
||||||
wh_exact(Some(w), Some(y), below(
|
|
||||||
wh_full(Outer(true, Style::default().fg(o))),
|
|
||||||
wh_full(below(
|
|
||||||
below(
|
|
||||||
fg_bg(o, b, wh_full("")),
|
|
||||||
wh_full(origin_nw(fg_bg(f, b, bold(true, name)))),
|
|
||||||
),
|
|
||||||
wh_full(when(is_selected, editor.map(|e|e.view())))))))
|
|
||||||
});
|
|
||||||
w_exact(track.width as u16, h_full(scenes))
|
|
||||||
});
|
|
||||||
|
|
||||||
return size.of(wh_full(above(status, tracks)));
|
|
||||||
|
|
||||||
fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
|
||||||
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
|
||||||
let clip = clip.read().unwrap();
|
|
||||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
|
||||||
} else {
|
|
||||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scene_bg (
|
|
||||||
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
|
||||||
) -> (Color, Color) {
|
|
||||||
let mut outline = theme.base.term;
|
|
||||||
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
|
||||||
outline = theme.lighter.term;
|
|
||||||
theme.light.term
|
|
||||||
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
|
||||||
outline = theme.darkest.term;
|
|
||||||
theme.base.term
|
|
||||||
} else {
|
|
||||||
theme.dark.term
|
|
||||||
}, outline)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scene_w (
|
|
||||||
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
|
||||||
) -> u16 {
|
|
||||||
if select.track() == Some(track_index) && let Some(editor) = editor {
|
|
||||||
(editor.size.w() as usize).max(24).max(track.width) as u16
|
|
||||||
} else {
|
|
||||||
track.width as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scene_y (
|
|
||||||
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
|
||||||
) -> u16 {
|
|
||||||
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
|
||||||
editor.size.h().max(12)
|
|
||||||
} else {
|
|
||||||
H_SCENE as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
|
||||||
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = &MidiOutput>,
|
|
||||||
) -> 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<'_>, height: 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(height + 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<'_>,
|
|
||||||
select: &Selection,
|
|
||||||
editor: Option<&MidiEditor>,
|
|
||||||
editing: bool,
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
w_exact(20, thunk(move |to: &mut Tui|{
|
|
||||||
for (index, scene, ..) in scenes {
|
|
||||||
view_scene_name(select, editor, index, scene, editing).draw(to);
|
|
||||||
}
|
|
||||||
Ok(XYWH(1, 1, 1, 1))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_scene_name (
|
|
||||||
select: &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 {
|
|
||||||
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 <'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> {
|
|
||||||
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: &[MidiInput]) -> impl Draw<Tui> {
|
|
||||||
let title_1 = wh_exact(Some(20), Some(1), origin_w(button_3("i", "nput ", format!("{}", midi_ins.len()), false)));
|
|
||||||
let title_2 = wh_exact(Some(4), Some(1), w_exact(4, button_2("I", "+", false)));
|
|
||||||
east(title_1, west(title_2, thunk(move|to: &mut Tui|{
|
|
||||||
for (_index, track, x1, _x2) in tracks {
|
|
||||||
south(
|
|
||||||
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 "),
|
|
||||||
))))),
|
|
||||||
thunk(move |to: &mut Tui|{
|
|
||||||
for (index, port) in midi_ins.iter().enumerate() {
|
|
||||||
x_push(index as u16 * 10, h_exact(1, 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|{
|
|
||||||
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!()
|
|
||||||
}))))).draw(to);
|
|
||||||
}
|
|
||||||
todo!()
|
|
||||||
})
|
|
||||||
).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_once(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> {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_sessions () -> impl Draw<Tui> {
|
|
||||||
let h = 6;
|
|
||||||
let w = Some(30);
|
|
||||||
let f = Rgb(224, 192, 128);
|
|
||||||
h_exact(h, w_min(w, thunk(move |to: &mut Tui|{
|
|
||||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
|
||||||
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
|
||||||
let y = (2 * index) as u16;
|
|
||||||
let h = 2;
|
|
||||||
y_push(y, &h_exact(h, w_full(bg(b, origin_w(fg(f, name)))))).draw(to);
|
|
||||||
}
|
|
||||||
Ok(to.area().into())
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
|
||||||
w_full(origin_w(field_v(ItemTheme::default(),
|
|
||||||
match state.dialog.browser_target().unwrap() {
|
|
||||||
BrowseTarget::SaveProject => "Save project:",
|
|
||||||
BrowseTarget::LoadProject => "Load project:",
|
|
||||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
|
||||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
|
||||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
|
||||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
|
||||||
}, h_exact(1, fg(g(96), x_repeat("🭻")))
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
|
||||||
let selected = state.dialog.device_kind().unwrap();
|
|
||||||
south(bold(true, "Add device"), iter_south(
|
|
||||||
move||device_kinds().iter(),
|
|
||||||
move|_label: &&'static str, i|{
|
|
||||||
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
|
||||||
let l = if i == selected { "[ " } else { " " };
|
|
||||||
let r = if i == selected { " ]" } else { " " };
|
|
||||||
w_full(bg(b, east(l, west(r, "FIXME device name")))) }))
|
|
||||||
}
|
|
||||||
33
src/deps.rs
Normal file
33
src/deps.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub(crate) use ::clap::{self, Parser, Subcommand};
|
||||||
|
|
||||||
|
pub(crate) use ::xdg::BaseDirectories;
|
||||||
|
|
||||||
|
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
||||||
|
|
||||||
|
pub(crate) use tengri::{
|
||||||
|
*, lang::*, exit::*, eval::*, sing::*, time::*, draw::*, term::*, color::*, task::*,
|
||||||
|
crossterm::event::{Event, KeyEvent},
|
||||||
|
ratatui::{
|
||||||
|
self,
|
||||||
|
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
||||||
|
widgets::{Widget, canvas::{Canvas, Line}},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) use ::{
|
||||||
|
std::{
|
||||||
|
cmp::Ord,
|
||||||
|
collections::BTreeMap,
|
||||||
|
error::Error,
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::{Write, Debug, Formatter},
|
||||||
|
fs::File,
|
||||||
|
ops::{Add, Sub, Mul, Div, Rem},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}},
|
||||||
|
time::Duration,
|
||||||
|
thread::{spawn, JoinHandle},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -26,7 +26,7 @@ use crate::*;
|
||||||
pub trait ClipsView: TracksView + ScenesView {
|
pub trait ClipsView: TracksView + ScenesView {
|
||||||
/// Draw clips per scene
|
/// Draw clips per scene
|
||||||
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
||||||
crate::view::view_scenes_clips(
|
view_scenes_clips(
|
||||||
||self.scenes_with_sizes(),
|
||self.scenes_with_sizes(),
|
||||||
self.tracks_with_sizes(),
|
self.tracks_with_sizes(),
|
||||||
self.selection(),
|
self.selection(),
|
||||||
|
|
@ -87,3 +87,7 @@ impl Arrangement {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasClipsSize { fn clips_size (&self) -> &Size; }
|
pub trait HasClipsSize { fn clips_size (&self) -> &Size; }
|
||||||
|
|
||||||
|
impl HasClipsSize for App {
|
||||||
|
fn clips_size (&self) -> &Size { &self.project.size_inner }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
|
||||||
fn w_mid (&self) -> u16;
|
fn w_mid (&self) -> u16;
|
||||||
|
|
||||||
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
||||||
crate::view::view_scenes_names(
|
view_scenes_names(
|
||||||
self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing()
|
self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -130,16 +130,14 @@ impl HasSceneScroll for Arrangement {
|
||||||
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasSceneScroll for App {
|
||||||
|
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
||||||
|
}
|
||||||
|
|
||||||
impl ScenesView for Arrangement {
|
impl ScenesView for Arrangement {
|
||||||
fn h_scenes (&self) -> u16 {
|
fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) }
|
||||||
(self.size.h() as u16).saturating_sub(20)
|
fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) }
|
||||||
}
|
fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) }
|
||||||
fn w_side (&self) -> u16 {
|
|
||||||
(self.size.w() as u16 * 2 / 10).max(20)
|
|
||||||
}
|
|
||||||
fn w_mid (&self) -> u16 {
|
|
||||||
(self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SceneWith<'a, T> =
|
pub type SceneWith<'a, T> =
|
||||||
|
|
@ -148,7 +146,7 @@ pub type SceneWith<'a, T> =
|
||||||
def_command!(SceneCommand: |scene: Scene| {
|
def_command!(SceneCommand: |scene: Scene| {
|
||||||
SetSize { size: usize } => { todo!() },
|
SetSize { size: usize } => { todo!() },
|
||||||
SetZoom { size: usize } => { todo!() },
|
SetZoom { size: usize } => { todo!() },
|
||||||
SetName { name: Arc<str> } =>
|
SetName { name: Arc<str> } =>
|
||||||
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
||||||
SetColor { color: ItemTheme } =>
|
SetColor { color: ItemTheme } =>
|
||||||
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
||||||
|
|
@ -158,7 +156,19 @@ def_command!(SceneCommand: |scene: Scene| {
|
||||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
||||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
||||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
||||||
impl<T: TracksView+ScenesView+Send+Sync> ClipsView for T {}
|
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
impl<T: TracksView+ScenesView+Send+Sync> ClipsView for T {}
|
||||||
impl<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
|
impl<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
|
||||||
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
|
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
|
||||||
|
|
||||||
|
impl ScenesView for App {
|
||||||
|
fn w_mid (&self) -> u16 {
|
||||||
|
(self.size.w() as u16).saturating_sub(self.w_side())
|
||||||
|
}
|
||||||
|
fn w_side (&self) -> u16 {
|
||||||
|
20
|
||||||
|
}
|
||||||
|
fn h_scenes (&self) -> u16 {
|
||||||
|
(self.size.h() as u16).saturating_sub(20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,13 @@ impl Track {
|
||||||
) -> impl Draw<Tui> + 'a {
|
) -> impl Draw<Tui> + 'a {
|
||||||
view_track_per(tracks, callback)
|
view_track_per(tracks, callback)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sampler")]
|
||||||
|
impl Track {
|
||||||
|
|
||||||
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
||||||
#[cfg(feature = "sampler")] pub fn new_with_sampler (
|
pub fn new_with_sampler (
|
||||||
name: &impl AsRef<str>,
|
name: &impl AsRef<str>,
|
||||||
color: Option<ItemTheme>,
|
color: Option<ItemTheme>,
|
||||||
jack: &Jack<'static>,
|
jack: &Jack<'static>,
|
||||||
|
|
@ -78,7 +82,7 @@ impl Track {
|
||||||
Ok(track)
|
Ok(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sampler")] pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
||||||
for device in self.devices.iter() {
|
for device in self.devices.iter() {
|
||||||
match device {
|
match device {
|
||||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||||
|
|
@ -88,7 +92,7 @@ impl Track {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sampler")] pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
||||||
for device in self.devices.iter_mut() {
|
for device in self.devices.iter_mut() {
|
||||||
match device {
|
match device {
|
||||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||||
|
|
@ -153,22 +157,22 @@ pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
#[cfg(feature = "port")]
|
||||||
fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> + 'a {
|
fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> + 'a {
|
||||||
crate::view::view_midi_ins_status(theme, self.track())
|
view_midi_ins_status(theme, self.track())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
#[cfg(feature = "port")]
|
||||||
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||||
crate::view::view_midi_outs_status(theme, self.track())
|
view_midi_outs_status(theme, self.track())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
#[cfg(feature = "port")]
|
||||||
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
crate::view::view_audio_ins_status(theme, self.track())
|
view_audio_ins_status(theme, self.track())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "port")]
|
#[cfg(feature = "port")]
|
||||||
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
crate::view::view_audio_outs_status(theme, self.track())
|
view_audio_outs_status(theme, self.track())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,13 +183,13 @@ pub trait HasTrackScroll: HasTracks {
|
||||||
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
/// Draw name of each track
|
/// Draw name of each track
|
||||||
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
crate::view::view_track_names(
|
view_track_names(
|
||||||
theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection()
|
theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/// Draw outputs per track
|
/// Draw outputs per track
|
||||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
||||||
crate::view::view_track_outputs(
|
view_track_outputs(
|
||||||
theme, self.tracks_with_sizes(), self.midi_outs().iter()
|
theme, self.tracks_with_sizes(), self.midi_outs().iter()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +199,7 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
for track in self.tracks().iter() {
|
for track in self.tracks().iter() {
|
||||||
h = h.max(track.sequencer.midi_ins.len() as u16);
|
h = h.max(track.sequencer.midi_ins.len() as u16);
|
||||||
}
|
}
|
||||||
crate::view::view_track_inputs(theme, self.tracks_with_sizes(), h)
|
view_track_inputs(theme, self.tracks_with_sizes(), h)
|
||||||
}
|
}
|
||||||
/// Iterate over tracks with their corresponding sizes.
|
/// Iterate over tracks with their corresponding sizes.
|
||||||
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
||||||
|
|
@ -247,19 +251,19 @@ impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
|
|
||||||
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||||
crate::view::view_inputs(
|
view_inputs(
|
||||||
self.tracks_with_sizes(), self.midi_ins().as_slice()
|
self.tracks_with_sizes(), self.midi_ins().as_slice()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
crate::view::view_outputs(
|
view_outputs(
|
||||||
theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height()
|
theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
crate::view::view_track_devices(
|
view_track_devices(
|
||||||
theme, self.tracks_with_sizes(), self.track(), self.devices_height()
|
theme, self.tracks_with_sizes(), self.track(), self.devices_height()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -334,3 +338,13 @@ impl Arrangement {
|
||||||
Ok((index, &mut self.tracks_mut()[index]))
|
Ok((index, &mut self.tracks_mut()[index]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HasTrackScroll for App {
|
||||||
|
fn track_scroll (&self) -> usize {
|
||||||
|
self.project.track_scroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
||||||
|
track.width as u16
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,27 @@ impl Quantize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement an arithmetic operation for a unit of time
|
||||||
|
#[macro_export] macro_rules! impl_op {
|
||||||
|
($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
|
||||||
|
impl $Op<Self> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other.get(); Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $Op<usize> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other as f64; Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl $Op<f64> for $T {
|
||||||
|
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
|
||||||
|
let $a = self.get(); let $b = other; Self($impl.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Define and implement a unit of time
|
/// Define and implement a unit of time
|
||||||
#[macro_export] macro_rules! impl_time_unit {
|
#[macro_export] macro_rules! impl_time_unit {
|
||||||
($T:ident) => {
|
($T:ident) => {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ impl Dialog {
|
||||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
||||||
app.dialog = Dialog::None;
|
app.dialog = Dialog::None;
|
||||||
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
|
app.mode = app.config.modes.get(":arranger").unwrap();
|
||||||
})))),
|
})))),
|
||||||
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||||
].into()))
|
].into()))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "lv2_gui")] use ::winit::{
|
||||||
|
application::ApplicationHandler,
|
||||||
|
event::WindowEvent,
|
||||||
|
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
||||||
|
window::{Window, WindowId},
|
||||||
|
platform::x11::EventLoopBuilderExtX11
|
||||||
|
};
|
||||||
|
|
||||||
/// A LV2 plugin.
|
/// A LV2 plugin.
|
||||||
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ use ConnectName::*;
|
||||||
use ConnectScope::*;
|
use ConnectScope::*;
|
||||||
use ConnectStatus::*;
|
use ConnectStatus::*;
|
||||||
|
|
||||||
pub mod audio; pub use self::audio::*;
|
mod audio; pub use self::audio::*;
|
||||||
pub mod midi; pub use self::midi::*;
|
mod midi; pub use self::midi::*;
|
||||||
pub mod connect; pub use self::connect::*;
|
mod connect; pub use self::connect::*;
|
||||||
|
mod register; pub use self::register::*;
|
||||||
|
|
||||||
pub trait JackPort: HasJack<'static> {
|
pub trait JackPort: HasJack<'static> {
|
||||||
|
|
||||||
type Port: PortSpec + Default;
|
type Port: PortSpec + Default;
|
||||||
|
|
||||||
type Pair: PortSpec + Default;
|
type Pair: PortSpec + Default;
|
||||||
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
||||||
|
|
@ -135,29 +134,3 @@ pub trait JackPort: HasJack<'static> {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RegisterPorts: HasJack<'static> {
|
|
||||||
/// Register a MIDI input port.
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
|
||||||
/// Register a MIDI output port.
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput>;
|
|
||||||
/// Register an audio input port.
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput>;
|
|
||||||
/// Register an audio output port.
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<J: HasJack<'static>> RegisterPorts for J {
|
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
|
||||||
MidiInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
|
||||||
MidiOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
|
||||||
AudioInput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
|
||||||
AudioOutput::new(self.jack(), name, connect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
27
src/device/port/register.rs
Normal file
27
src/device/port/register.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait RegisterPorts: HasJack<'static> {
|
||||||
|
/// Register a MIDI input port.
|
||||||
|
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
||||||
|
/// Register a MIDI output port.
|
||||||
|
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput>;
|
||||||
|
/// Register an audio input port.
|
||||||
|
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput>;
|
||||||
|
/// Register an audio output port.
|
||||||
|
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<J: HasJack<'static>> RegisterPorts for J {
|
||||||
|
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
||||||
|
MidiInput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
||||||
|
MidiOutput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
||||||
|
AudioInput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
||||||
|
AudioOutput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
use crate::{*, device::*, browse::*, mix::*};
|
use crate::{*, device::*, browse::*, mix::*};
|
||||||
|
|
||||||
|
pub(crate) use symphonia::{
|
||||||
|
default::get_codecs,
|
||||||
|
core::{//errors::Error as SymphoniaError,
|
||||||
|
audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint,
|
||||||
|
codecs::{Decoder, CODEC_TYPE_NULL},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
mod voice; pub use self::voice::*;
|
mod voice; pub use self::voice::*;
|
||||||
mod sample; pub use self::sample::*;
|
mod sample; pub use self::sample::*;
|
||||||
mod sample_add; pub use self::sample_add::*;
|
mod sample_add; pub use self::sample_add::*;
|
||||||
|
|
|
||||||
101
src/tek.rs
101
src/tek.rs
|
|
@ -1,92 +1,19 @@
|
||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
#![feature(adt_const_params, associated_type_defaults, anonymous_lifetime_in_impl_trait,
|
#![feature(
|
||||||
closure_lifetime_binder, impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait,
|
adt_const_params,
|
||||||
type_changing_struct_update)]
|
anonymous_lifetime_in_impl_trait,
|
||||||
|
impl_trait_in_assoc_type,
|
||||||
/// CLI banner.
|
trait_alias,
|
||||||
pub(crate) const HEADER: &'static str = r#"
|
type_changing_struct_update
|
||||||
|
)]
|
||||||
~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
|
|
||||||
█ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~
|
|
||||||
~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")] use clap::{self, Parser, Subcommand};
|
|
||||||
|
|
||||||
pub extern crate atomic_float;
|
pub extern crate atomic_float;
|
||||||
|
|
||||||
pub extern crate xdg;
|
pub extern crate xdg;
|
||||||
pub(crate) use ::xdg::BaseDirectories;
|
|
||||||
|
|
||||||
pub extern crate midly;
|
pub extern crate midly;
|
||||||
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
|
||||||
|
|
||||||
pub extern crate tengri;
|
pub extern crate tengri;
|
||||||
pub(crate) use tengri::{
|
|
||||||
*, lang::*, exit::*, eval::*, sing::*, time::*, draw::*, term::*, color::*, task::*,
|
|
||||||
crossterm::event::{Event, KeyEvent},
|
|
||||||
ratatui::{
|
|
||||||
self,
|
|
||||||
prelude::{Rect, Style, Stylize, Buffer, Color::{self, *}},
|
|
||||||
widgets::{Widget, canvas::{Canvas, Line}},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Implement an arithmetic operation for a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_op {
|
|
||||||
($T:ident, $Op:ident, $method:ident, |$a:ident,$b:ident|{$impl:expr}) => {
|
|
||||||
impl $Op<Self> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: Self) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other.get(); Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<usize> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: usize) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other as f64; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl $Op<f64> for $T {
|
|
||||||
type Output = Self; #[inline] fn $method (self, other: f64) -> Self::Output {
|
|
||||||
let $a = self.get(); let $b = other; Self($impl.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sampler")] pub(crate) use symphonia::{
|
|
||||||
default::get_codecs,
|
|
||||||
core::{//errors::Error as SymphoniaError,
|
|
||||||
audio::SampleBuffer, formats::Packet, io::MediaSourceStream, probe::Hint,
|
|
||||||
codecs::{Decoder, CODEC_TYPE_NULL},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2_gui")] use ::winit::{
|
|
||||||
application::ApplicationHandler,
|
|
||||||
event::WindowEvent,
|
|
||||||
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
|
||||||
window::{Window, WindowId},
|
|
||||||
platform::x11::EventLoopBuilderExtX11
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(unused)] pub(crate) use ::{
|
|
||||||
std::{
|
|
||||||
cmp::Ord,
|
|
||||||
collections::BTreeMap,
|
|
||||||
error::Error,
|
|
||||||
ffi::OsString,
|
|
||||||
fmt::{Write, Debug, Formatter},
|
|
||||||
fs::File,
|
|
||||||
ops::{Add, Sub, Mul, Div, Rem},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}},
|
|
||||||
time::Duration,
|
|
||||||
thread::{spawn, JoinHandle},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Command-line entrypoint.
|
/// Command-line entrypoint.
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")] pub fn main () -> Usually<()> {
|
||||||
pub fn main () -> Usually<()> {
|
|
||||||
tui_setup_panic();
|
tui_setup_panic();
|
||||||
Config::watch(|config|{
|
Config::watch(|config|{
|
||||||
tui_run_main(Jack::new_run("tek", move|jack|{
|
tui_run_main(Jack::new_run("tek", move|jack|{
|
||||||
|
|
@ -98,8 +25,14 @@ pub fn main () -> Usually<()> {
|
||||||
}).map(|_|())
|
}).map(|_|())
|
||||||
}
|
}
|
||||||
|
|
||||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
pub mod deps; pub use self::deps::*;
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
|
|
||||||
pub mod device; pub use self::device::*;
|
pub mod device; pub use self::device::*;
|
||||||
pub mod app; pub use self::app::*;
|
pub mod app; pub use self::app::*;
|
||||||
|
|
||||||
|
|
||||||
|
/// CLI banner.
|
||||||
|
pub(crate) const HEADER: &'static str = r#"
|
||||||
|
|
||||||
|
~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
|
||||||
|
█ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~
|
||||||
|
~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue