mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 07:46: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 cli; pub use self::cli::*;
|
||||
pub mod config; pub use self::config::*;
|
||||
pub mod view; pub use self::view::*;
|
||||
pub mod draw; pub use self::draw::*;
|
||||
pub mod mode; pub use self::mode::*;
|
||||
pub mod size; pub use self::size::*;
|
||||
|
||||
/// Total application state.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,41 @@
|
|||
use crate::*;
|
||||
|
||||
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.
|
||||
///
|
||||
/// ```
|
||||
/// let axis = tek::ControlAxis::X;
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||
X, Y, Z, I
|
||||
fn tek_commands_collect (app: &App, input: &TuiEvent)
|
||||
-> Usually<Vec<AppCommand>>
|
||||
{
|
||||
let mut commands = vec![];
|
||||
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) {
|
||||
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.
|
||||
|
|
@ -91,6 +116,7 @@ impl Bind<TuiEvent, Arc<str>> {
|
|||
impl<E, C> Default for Bind<E, C> {
|
||||
fn default () -> Self { Self(Default::default()) }
|
||||
}
|
||||
|
||||
impl<C: Default> Default for Binding<C> {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
|
|
@ -158,38 +184,14 @@ impl<'a> Namespace<'a, AppCommand> for App {
|
|||
});
|
||||
}
|
||||
|
||||
//impl_handle!(TuiIn: |self: App, input|{
|
||||
//let commands = tek_collect_commands(self, input)?;
|
||||
//let history = tek_execute_commands(self, commands)?;
|
||||
//self.history.extend(history.into_iter());
|
||||
//Ok(None)
|
||||
//});
|
||||
/// A control axis.
|
||||
///
|
||||
/// ```
|
||||
/// let axis = tek::ControlAxis::X;
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||
X, Y, Z, I
|
||||
}
|
||||
|
||||
//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually<Vec<AppCommand>> {
|
||||
//let mut commands = vec![];
|
||||
//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)
|
||||
//}
|
||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
|
|
|||
|
|
@ -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!();
|
||||
for v in v.name.iter() { print!("{}", Green.bold().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"));
|
||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
for (k, v) in v.modes.read().unwrap().iter() {
|
||||
print!("{} {} {:?}",
|
||||
Blue.paint("MODE"),
|
||||
Green.bold().paint(format!("{k:<16}")),
|
||||
v.name);
|
||||
print!(" INFO={:?}",
|
||||
v.info);
|
||||
print!(" VIEW={:?}",
|
||||
v.view);
|
||||
println!(" KEYS={:?}",
|
||||
v.keys);
|
||||
}
|
||||
v.modes.for_each(|k, v|{
|
||||
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
||||
print!( " INFO={:?}", v.info);
|
||||
print!( " VIEW={:?}", v.view);
|
||||
println!(" KEYS={:?}", v.keys);
|
||||
});
|
||||
print!("{}", Blue.paint("VIEW"));
|
||||
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||
println!();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn tek_print_status (project: &Arrangement) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{*, bind::*, view::*};
|
||||
use crate::*;
|
||||
|
||||
/// Configuration: mode, view, and bind definitions.
|
||||
///
|
||||
|
|
@ -48,12 +48,14 @@ impl Config {
|
|||
config.init()?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Create a new app configuration from a set of XDG base directories,
|
||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||
let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB);
|
||||
let dirs = dirs.unwrap_or_else(default);
|
||||
Self { dirs, ..Default::default() }
|
||||
}
|
||||
|
||||
/// Write initial contents of configuration.
|
||||
pub fn init (&mut self) -> Usually<()> {
|
||||
self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{
|
||||
|
|
@ -62,6 +64,7 @@ impl Config {
|
|||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write initial contents of a configuration file.
|
||||
pub fn init_one (
|
||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||
|
|
@ -78,11 +81,13 @@ impl Config {
|
|||
return Err(format!("{path}: not found").into())
|
||||
})
|
||||
}
|
||||
|
||||
/// Add statements to configuration from [Dsl] source.
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> {
|
||||
dsl.each(|item|self.add_one(item))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn add_one (&self, item: impl Language) -> Usually<()> {
|
||||
if let Some(expr) = item.expr()? {
|
||||
let head = expr.head()?;
|
||||
|
|
@ -91,7 +96,7 @@ impl Config {
|
|||
let body = tail.tail()?;
|
||||
//println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||
match head {
|
||||
Some("mode") if let Some(name) = name => load_mode(&self.modes, &name, &body)?,
|
||||
Some("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("view") if let Some(name) = name => load_view(&self.views, &name, &body)?,
|
||||
_ => 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())
|
||||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
||||
pub(crate) fn load_mode (modes: &Modes, name: &impl AsRef<str>, body: &impl Language) -> Usually<()> {
|
||||
let mut mode = Mode::default();
|
||||
body.each(|item|mode.add(item))?;
|
||||
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||
Ok(())
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Collection of interaction modes.
|
||||
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||
|
||||
impl Mode<Arc<str>> {
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
|
|
@ -71,23 +75,9 @@ impl Mode<Arc<str>> {
|
|||
}
|
||||
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(if let Some(id) = dsl.head()? {
|
||||
load_mode(&self.modes, &id, &dsl.tail())?;
|
||||
self.modes.add(&id, &dsl.tail())?;
|
||||
} else {
|
||||
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Group of view and keys definitions.
|
||||
///
|
||||
/// ```
|
||||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
||||
/// ```
|
||||
#[derive(Default, Debug)] pub struct Mode<D: Language + Ord> {
|
||||
pub path: PathBuf,
|
||||
pub name: Vec<D>,
|
||||
pub info: Vec<D>,
|
||||
pub view: Vec<D>,
|
||||
pub keys: Vec<D>,
|
||||
pub modes: Modes,
|
||||
}
|
||||
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::*;
|
||||
|
||||
tui_view!(|self: App| {
|
||||
""
|
||||
});
|
||||
|
||||
/// The [Draw] implementation for [App] handles the loaded view,
|
||||
/// 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)
|
||||
-> Usually<XYWH<u16>>
|
||||
{
|
||||
let modes = state.config.modes.clone();
|
||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
||||
let height = (state.config.modes.len() * 2) as u16;
|
||||
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 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>");
|
||||
|
|
@ -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_id = w_full(origin_e(fg(fg2, id)));
|
||||
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(
|
||||
above(field_name, field_id),
|
||||
field_info
|
||||
))))).draw(to);
|
||||
}
|
||||
index += 1;
|
||||
});
|
||||
Ok(to.area().into())
|
||||
}))).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!(TracksSizes => Track);
|
||||
|
||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
||||
track.width as u16
|
||||
}
|
||||
|
||||
pub trait HasWidth {
|
||||
const MIN_WIDTH: usize;
|
||||
/// Increment track width.
|
||||
|
|
@ -27,31 +23,3 @@ pub trait HasWidth {
|
|||
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
||||
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 {
|
||||
/// Draw clips per scene
|
||||
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
||||
crate::view::view_scenes_clips(
|
||||
view_scenes_clips(
|
||||
||self.scenes_with_sizes(),
|
||||
self.tracks_with_sizes(),
|
||||
self.selection(),
|
||||
|
|
@ -87,3 +87,7 @@ impl Arrangement {
|
|||
}
|
||||
|
||||
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 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()
|
||||
)
|
||||
}
|
||||
|
|
@ -130,16 +130,14 @@ impl HasSceneScroll for Arrangement {
|
|||
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 {
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(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 h_scenes (&self) -> u16 { (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) }
|
||||
}
|
||||
|
||||
pub type SceneWith<'a, T> =
|
||||
|
|
@ -148,7 +146,7 @@ pub type SceneWith<'a, T> =
|
|||
def_command!(SceneCommand: |scene: Scene| {
|
||||
SetSize { 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}),
|
||||
SetColor { color: ItemTheme } =>
|
||||
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_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
||||
#[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<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
|
||||
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
|
||||
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: 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 {
|
||||
view_track_per(tracks, callback)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sampler")]
|
||||
impl Track {
|
||||
|
||||
/// 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>,
|
||||
color: Option<ItemTheme>,
|
||||
jack: &Jack<'static>,
|
||||
|
|
@ -78,7 +82,7 @@ impl 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() {
|
||||
match device {
|
||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||
|
|
@ -88,7 +92,7 @@ impl Track {
|
|||
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() {
|
||||
match device {
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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")]
|
||||
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 {
|
||||
/// Draw name of each track
|
||||
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()
|
||||
)
|
||||
}
|
||||
/// Draw outputs per track
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
|
@ -195,7 +199,7 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
|||
for track in self.tracks().iter() {
|
||||
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.
|
||||
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
||||
|
|
@ -247,19 +251,19 @@ impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
|||
impl Arrangement {
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
|
@ -334,3 +338,13 @@ impl Arrangement {
|
|||
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
|
||||
#[macro_export] macro_rules! impl_time_unit {
|
||||
($T:ident) => {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ impl Dialog {
|
|||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
||||
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
|
||||
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(())))),
|
||||
].into()))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
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.
|
||||
#[derive(Debug)] #[cfg(feature = "lv2")] pub struct Lv2 {
|
||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ use ConnectName::*;
|
|||
use ConnectScope::*;
|
||||
use ConnectStatus::*;
|
||||
|
||||
pub mod audio; pub use self::audio::*;
|
||||
pub mod midi; pub use self::midi::*;
|
||||
pub mod connect; pub use self::connect::*;
|
||||
mod audio; pub use self::audio::*;
|
||||
mod midi; pub use self::midi::*;
|
||||
mod connect; pub use self::connect::*;
|
||||
mod register; pub use self::register::*;
|
||||
|
||||
pub trait JackPort: HasJack<'static> {
|
||||
|
||||
type Port: PortSpec + Default;
|
||||
|
||||
type Pair: PortSpec + Default;
|
||||
|
||||
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::*};
|
||||
|
||||
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 sample; pub use self::sample::*;
|
||||
mod sample_add; pub use self::sample_add::*;
|
||||
|
|
|
|||
101
src/tek.rs
101
src/tek.rs
|
|
@ -1,92 +1,19 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
#![feature(adt_const_params, associated_type_defaults, anonymous_lifetime_in_impl_trait,
|
||||
closure_lifetime_binder, impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait,
|
||||
type_changing_struct_update)]
|
||||
|
||||
/// CLI banner.
|
||||
pub(crate) const HEADER: &'static str = r#"
|
||||
|
||||
~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~
|
||||
█ █▀ █▀▀▄ ~ v0.4.0, 2026 heatwave edition ~
|
||||
~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#;
|
||||
|
||||
#[cfg(feature = "cli")] use clap::{self, Parser, Subcommand};
|
||||
#![feature(
|
||||
adt_const_params,
|
||||
anonymous_lifetime_in_impl_trait,
|
||||
impl_trait_in_assoc_type,
|
||||
trait_alias,
|
||||
type_changing_struct_update
|
||||
)]
|
||||
|
||||
pub extern crate atomic_float;
|
||||
|
||||
pub extern crate xdg;
|
||||
pub(crate) use ::xdg::BaseDirectories;
|
||||
|
||||
pub extern crate midly;
|
||||
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
||||
|
||||
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.
|
||||
#[cfg(feature = "cli")]
|
||||
pub fn main () -> Usually<()> {
|
||||
#[cfg(feature = "cli")] pub fn main () -> Usually<()> {
|
||||
tui_setup_panic();
|
||||
Config::watch(|config|{
|
||||
tui_run_main(Jack::new_run("tek", move|jack|{
|
||||
|
|
@ -98,8 +25,14 @@ pub fn main () -> Usually<()> {
|
|||
}).map(|_|())
|
||||
}
|
||||
|
||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
pub mod deps; pub use self::deps::*;
|
||||
pub mod device; pub use self::device::*;
|
||||
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