mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
flatten conf
This commit is contained in:
parent
b050e0e014
commit
56df5c5b78
9 changed files with 1840 additions and 1868 deletions
275
src/conf.rs
275
src/conf.rs
|
|
@ -1,275 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
|
|
||||||
|
|
||||||
impl_as_ref!(Config: |self: Config| self);
|
|
||||||
impl_as_ref!(Config: |self: ConfigWatcher| &self.config);
|
|
||||||
impl_as_mut!(Config: |self: ConfigWatcher| &mut self.config);
|
|
||||||
impl_as_ref!(ConfigWatcher: |self: ConfigWatcher| &self);
|
|
||||||
|
|
||||||
mod bind; pub use self::bind::*;
|
|
||||||
mod lang; pub use self::lang::*;
|
|
||||||
mod mode; pub use self::mode::*;
|
|
||||||
mod view; pub use self::view::*;
|
|
||||||
|
|
||||||
pub fn config_init <C: AsRef<ConfigWatcher> + AsRef<Config>> (config: C) -> Usually<C> {
|
|
||||||
// Write initial contents of configuration:
|
|
||||||
let watcher: &ConfigWatcher = config.as_ref();
|
|
||||||
if watcher.find_file().is_none() {
|
|
||||||
std::fs::write(watcher.place_file()?, CONFIG_DEFAULT)?;
|
|
||||||
}
|
|
||||||
// Load config from file:
|
|
||||||
Ok(if let Some(path) = watcher.find_file() {
|
|
||||||
config_reload(config, std::fs::read_to_string(&path)?)?
|
|
||||||
} else {
|
|
||||||
return Err(format!("config_init: not found").into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reload configuration from source.
|
|
||||||
pub fn config_reload <C: AsRef<Config>, L: Language> (state: C, src: L) -> Usually<C> {
|
|
||||||
state.as_ref().config_clear();
|
|
||||||
src.each(state, |c, s|config_add(c, s.trim()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load configuration item.
|
|
||||||
fn config_add <C: AsRef<Config>, L: Language> (state: C, src: L) -> Usually<C> {
|
|
||||||
if let Some(expr) = src.expr()? {
|
|
||||||
match expr.head()? {
|
|
||||||
Some("mode") => state.as_ref().modes_add(expr.tail()),
|
|
||||||
Some("view") => state.as_ref().views_add(expr.tail()),
|
|
||||||
Some("bind") => state.as_ref().binds_def_keys(expr.tail()),
|
|
||||||
Some("axis") => state.as_ref().binds_def_axis(expr.tail()),
|
|
||||||
_ => return Err(format!("config_add: unexpected: {src:?}").into())
|
|
||||||
}?;
|
|
||||||
Ok(state)
|
|
||||||
} else {
|
|
||||||
Err(format!("Config::add_one: tried to add empty item").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration: mode, view, and bind definitions.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use ::{std::sync::Arc, tek::*};
|
|
||||||
///
|
|
||||||
/// // Default empty config:
|
|
||||||
/// let config: Config = Default::default();
|
|
||||||
///
|
|
||||||
/// // Define a binding:
|
|
||||||
/// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap();
|
|
||||||
///
|
|
||||||
/// // Define an axis:
|
|
||||||
/// config.binds_def_axis(stringify!(Y @up @down)).unwrap();
|
|
||||||
///
|
|
||||||
/// // Define a mode:
|
|
||||||
/// config.modes_add(stringify!(Menu
|
|
||||||
/// (name Menu)
|
|
||||||
/// (info Mode selector.)
|
|
||||||
/// (axis Y Prev Next)
|
|
||||||
/// (bind Confirm)
|
|
||||||
/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog))))
|
|
||||||
/// )).unwrap();
|
|
||||||
///
|
|
||||||
/// // Find the mode:
|
|
||||||
/// let mode: Arc<Mode> = config.modes_get("Menu").unwrap();
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
/// Error caught during (re)loading
|
|
||||||
pub error: RwLock<Option<Arc<str>>>,
|
|
||||||
/// Timestamp
|
|
||||||
pub stamp: AtomicU64,
|
|
||||||
/// Active collection of interaction modes.
|
|
||||||
pub modes: Modes,
|
|
||||||
/// Active collection of event bindings.
|
|
||||||
pub binds: Binds,
|
|
||||||
/// Active collection of view definitions.
|
|
||||||
pub views: Views,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new () -> Self {
|
|
||||||
Self { stamp: quanta::Clock::new().raw().into(), ..Default::default() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Config>> HasError<Arc<str>> for T {
|
|
||||||
fn error (&self) -> &RwLock<Option<Arc<str>>> {
|
|
||||||
&self.as_ref().error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasError<Arc<str>> for Arc<ConfigWatcher> {
|
|
||||||
fn error (&self) -> &RwLock<Option<Arc<str>>> {
|
|
||||||
&self.as_ref().config.error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasConfig for Config {
|
|
||||||
fn config (&self) -> &Config {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
fn config_mut (&mut self) -> &mut Config {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration watcher and manager.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let config = tek::ConfigWatcher::new(None);
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct ConfigWatcher {
|
|
||||||
/// XDG base directories of running user.
|
|
||||||
pub dirs: BaseDirectories,
|
|
||||||
/// Watcher
|
|
||||||
pub watch: RwLock<Option<PollWatcher>>,
|
|
||||||
/// Actual configuration
|
|
||||||
pub config: Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default configuration contents.
|
|
||||||
const CONFIG_DEFAULT: &'static str = include_str!("tek.edn");
|
|
||||||
|
|
||||||
impl ConfigWatcher {
|
|
||||||
/// Default configuration directory.
|
|
||||||
const DIR: &'static str = "tek";
|
|
||||||
/// Default configuration subdirectory.
|
|
||||||
const SUB: &'static str = "v0";
|
|
||||||
/// Default configuration file name.
|
|
||||||
const EDN: &'static str = "tek.edn";
|
|
||||||
/// Create a new watched app configuration
|
|
||||||
/// from a set of XDG base directories,
|
|
||||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
|
||||||
Self {
|
|
||||||
dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile(Self::DIR, Self::SUB)),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Create, initialize, and watch a new configuration.
|
|
||||||
pub fn run_watched <T: 'static> (
|
|
||||||
callback: impl FnOnce(Arc<Self>)->T,
|
|
||||||
dirs: impl Into<Option<BaseDirectories>>,
|
|
||||||
) -> Usually<T> {
|
|
||||||
let watch = Arc::new(Self::new(dirs.into()));
|
|
||||||
Self::watch(watch.clone(), None)?;
|
|
||||||
let result = callback(watch);
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
/// Watch a config's file for changes.
|
|
||||||
pub fn watch (config: Arc<Self>, poll: Option<Duration>) -> Usually<()> {
|
|
||||||
let poll = poll.unwrap_or(Duration::from_millis(250));
|
|
||||||
let opts = NotifyConfig::default().with_poll_interval(poll);
|
|
||||||
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new({
|
|
||||||
let config = config.clone();
|
|
||||||
move|result|{
|
|
||||||
match result {
|
|
||||||
Ok(_events) => if let Err(e) = config_init(config.as_ref()) {
|
|
||||||
config.error_set(Some(format!("{e:?}").into()));
|
|
||||||
panic!("{e:?}");
|
|
||||||
} else {
|
|
||||||
//println!("config updated");
|
|
||||||
},
|
|
||||||
Err(errors) => {
|
|
||||||
panic!("{errors:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, opts)?;
|
|
||||||
if let Some(path) = config.get_file() {
|
|
||||||
//println!("watching: {path:?}");
|
|
||||||
watcher.watch(&path, RecursiveMode::NonRecursive)?;
|
|
||||||
*config.as_ref().watch.try_write().unwrap() = Some(watcher);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(format!("no config path").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Find the config file.
|
|
||||||
fn find_file (&self) -> Option<PathBuf> {
|
|
||||||
self.dirs.find_config_file(Self::EDN)
|
|
||||||
}
|
|
||||||
/// Place config file in default location.
|
|
||||||
fn place_file (&self) -> Result<PathBuf, std::io::Error> {
|
|
||||||
self.dirs.place_config_file(Self::EDN)
|
|
||||||
}
|
|
||||||
/// Get path to config file.
|
|
||||||
fn get_file (&self) -> Option<PathBuf> {
|
|
||||||
self.dirs.get_config_file(Self::EDN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasConfig for ConfigWatcher {
|
|
||||||
fn config (&self) -> &Config {
|
|
||||||
&self.config
|
|
||||||
}
|
|
||||||
fn config_mut (&mut self) -> &mut Config {
|
|
||||||
&mut self.config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eval_word <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
|
|
||||||
src.word()?.map(eval).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
//pub fn eval_expr <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
|
|
||||||
//Ok(match expr.head()? {
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
|
|
||||||
pub fn print_config_views (config: &Config) {
|
|
||||||
use ::ansi_term::Color::*;
|
|
||||||
config.views_for_each(|k, v|{
|
|
||||||
println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_config_binds (config: &Config) {
|
|
||||||
use ::ansi_term::Color::*;
|
|
||||||
config.binds_for_each(|k, v|{
|
|
||||||
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
|
||||||
for (k, v) in v.0.iter() {
|
|
||||||
print!("{} ", &Yellow.paint(match &k.0 {
|
|
||||||
Event::Key(KeyEvent { modifiers, .. }) =>
|
|
||||||
format!("{:>16}", format!("{modifiers}")),
|
|
||||||
_ => unimplemented!()
|
|
||||||
}));
|
|
||||||
print!("{}", &Yellow.bold().paint(match &k.0 {
|
|
||||||
Event::Key(KeyEvent { code, .. }) =>
|
|
||||||
format!("{:<10}", format!("{code}")),
|
|
||||||
_ => unimplemented!()
|
|
||||||
}));
|
|
||||||
for v in v.iter() {
|
|
||||||
print!(" => {:?}", v.commands);
|
|
||||||
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
|
||||||
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
|
||||||
//println!(" {:?}", v.source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_config_modes (config: &Config) {
|
|
||||||
use ::ansi_term::Color::*;
|
|
||||||
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}"))); }
|
|
||||||
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
|
||||||
print!("\n{}", Blue.paint("KEYS"));
|
|
||||||
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
|
||||||
println!();
|
|
||||||
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!();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
139
src/conf/bind.rs
139
src/conf/bind.rs
|
|
@ -220,6 +220,54 @@ pub struct ControlAxis(pub Arc<str>);
|
||||||
|
|
||||||
impl_debug!(Condition: |self, w| { write!(w, "*") });
|
impl_debug!(Condition: |self, w| { write!(w, "*") });
|
||||||
|
|
||||||
|
pub trait HasBinds {
|
||||||
|
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>;
|
||||||
|
fn binds_axis (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Axis<Arc<str>>>>>;
|
||||||
|
|
||||||
|
fn binds_clear (&self) {
|
||||||
|
*self.binds_keys().write() = Default::default();
|
||||||
|
*self.binds_axis().write() = Default::default();
|
||||||
|
}
|
||||||
|
fn binds_find (&self, id: impl AsRef<str>) -> Option<Arc<Bind<TuiEvent, Arc<str>>>> {
|
||||||
|
self.binds_keys().read().get(id.as_ref()).cloned()
|
||||||
|
}
|
||||||
|
fn binds_for_each <T> (
|
||||||
|
&self, mut cb: impl FnMut(&Arc<str>, &Bind<TuiEvent, Arc<str>>)->T
|
||||||
|
) {
|
||||||
|
for (k, v) in self.binds_keys().read().iter() {
|
||||||
|
cb(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Define a top-level key binding.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ::{std::sync::Arc, tek::*};
|
||||||
|
/// let config: Config = Default::default();
|
||||||
|
/// config.binds_def_keys(stringify!(Confirm (@enter confirm))).unwrap();
|
||||||
|
/// ```
|
||||||
|
fn binds_def_keys (&self, expr: impl Language) -> Usually<()> {
|
||||||
|
self.binds_keys().write().insert(
|
||||||
|
expr.head()?.ok_or("bind: missing name")?.into(),
|
||||||
|
Bind::new(expr.tail()?.unwrap_or(""))?.into()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Define an top-level axis binding (keypair).
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ::{std::sync::Arc, tek::*};
|
||||||
|
/// let config: Config = Default::default();
|
||||||
|
/// config.binds_def_axis(stringify!(Y @up @down)).unwrap();
|
||||||
|
/// ```
|
||||||
|
fn binds_def_axis (&self, expr: impl Language) -> Usually<()> {
|
||||||
|
self.binds_axis().write().insert(
|
||||||
|
expr.head()?.ok_or("axis: missing name")?.into(),
|
||||||
|
Axis::new(expr.tail()?.unwrap_or(""))?.into()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasBinds for App {
|
impl HasBinds for App {
|
||||||
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
|
fn binds_keys (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>> {
|
||||||
&self.config.config.binds.keys
|
&self.config.config.binds.keys
|
||||||
|
|
@ -246,3 +294,94 @@ impl HasBinds for Config {
|
||||||
&self.binds.axis
|
&self.binds.axis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DispatchBind<C: Clone>: HasBinds + HasModeStack + HasModes + Namespace<C> {
|
||||||
|
fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> {
|
||||||
|
if let Some(bindings) = self.input_cmds_find(input) {
|
||||||
|
return self.input_cmds_exec(bindings)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// ```
|
||||||
|
/// # use ::{std::sync::Arc, tek::*};
|
||||||
|
/// let app = config_reload(App::default(), stringify!(
|
||||||
|
/// (axis Y @up @down)
|
||||||
|
/// (mode Menu
|
||||||
|
/// (axis Y prev next)
|
||||||
|
/// (bind (@enter confirm)))
|
||||||
|
/// )).unwrap();
|
||||||
|
/// panic!("{:?}\n{:?}", app.config.config.binds, app.config.config.modes);
|
||||||
|
/// assert!(app.input_cmds_find(&"left".into()).is_none());
|
||||||
|
/// assert!(app.input_cmds_find(&"enter".into()).is_some());
|
||||||
|
/// assert!(app.input_cmds_find(&"down".into()).is_some());
|
||||||
|
/// assert!(app.input_cmds_find(&"up".into()).is_some());
|
||||||
|
/// ```
|
||||||
|
fn input_cmds_find (&self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
|
||||||
|
for name in self.mode_stack().read().iter().rev() {
|
||||||
|
let mode = self.modes_get(name).as_ref().map(Arc::clone);
|
||||||
|
if let Some(mode) = mode {
|
||||||
|
for (ctrl, (dec, inc)) in mode.axis.iter() {
|
||||||
|
panic!("{ctrl:?} {dec:?} {inc:?}");
|
||||||
|
}
|
||||||
|
for id in mode.keys.iter() {
|
||||||
|
if let Some(event_map) = self.binds_find(id)
|
||||||
|
&& let Some(bindings) = event_map.query(input) {
|
||||||
|
return Some(bindings.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn input_cmds_exec (&mut self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
|
||||||
|
for binding in bindings.iter() {
|
||||||
|
for src in binding.commands.iter() {
|
||||||
|
if let Some(command) = Namespace::<C>::namespace(self, &src)? {
|
||||||
|
self.cmd_dispatch(command)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn cmd_dispatch (&mut self, command: C) -> Usually<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DispatchBind<AppCmd> for App {
|
||||||
|
fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> {
|
||||||
|
match command.clone().dispatch(self) {
|
||||||
|
Ok(undo) => {
|
||||||
|
self.history.push((command, undo));
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
self.history.push((command, None));
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_config_binds (config: &Config) {
|
||||||
|
use ::ansi_term::Color::*;
|
||||||
|
config.binds_for_each(|k, v|{
|
||||||
|
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
|
||||||
|
for (k, v) in v.0.iter() {
|
||||||
|
print!("{} ", &Yellow.paint(match &k.0 {
|
||||||
|
Event::Key(KeyEvent { modifiers, .. }) =>
|
||||||
|
format!("{:>16}", format!("{modifiers}")),
|
||||||
|
_ => unimplemented!()
|
||||||
|
}));
|
||||||
|
print!("{}", &Yellow.bold().paint(match &k.0 {
|
||||||
|
Event::Key(KeyEvent { code, .. }) =>
|
||||||
|
format!("{:<10}", format!("{code}")),
|
||||||
|
_ => unimplemented!()
|
||||||
|
}));
|
||||||
|
for v in v.iter() {
|
||||||
|
print!(" => {:?}", v.commands);
|
||||||
|
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
|
||||||
|
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
|
||||||
|
//println!(" {:?}", v.source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
784
src/conf/lang.rs
784
src/conf/lang.rs
|
|
@ -1,5 +1,54 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
pub fn eval_word <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
|
||||||
|
src.word()?.map(eval).transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||||
|
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||||
|
) -> Perhaps<U> {
|
||||||
|
if *target == *value {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let mut value = value.clone();
|
||||||
|
std::mem::swap(target, &mut value);
|
||||||
|
Ok(Some(returned(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_bool <U> (
|
||||||
|
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
||||||
|
) -> Perhaps<U> {
|
||||||
|
let mut value = value.unwrap_or(!*target);
|
||||||
|
if value == *target {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
std::mem::swap(target, &mut value);
|
||||||
|
Ok(Some(returned(Some(value))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Increment a wrapping counter.
|
||||||
|
pub const fn wrap_inc (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { (index + 1) % count } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrement a wrapping counter.
|
||||||
|
pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 {
|
||||||
|
let a = index.overflowing_sub(1).0;
|
||||||
|
let b = count.saturating_sub(1);
|
||||||
|
if a < b { a } else { b }
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//pub fn eval_expr <T> (src: impl Language, eval: impl Fn(&str)->Usually<T>) -> Perhaps<T> {
|
||||||
|
//Ok(match expr.head()? {
|
||||||
|
//})
|
||||||
|
//}
|
||||||
|
|
||||||
pub fn tek_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
pub fn tek_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
||||||
Ok(src.src()?.map(|x|x.into()))
|
Ok(src.src()?.map(|x|x.into()))
|
||||||
}
|
}
|
||||||
|
|
@ -152,3 +201,738 @@ pub fn tek_path_buf <'a> (_: &App, _: impl Language) -> Perhaps<PathBuf> {
|
||||||
pub fn tek_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Vec<Arc<str>>> {
|
pub fn tek_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Vec<Arc<str>>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(AppCmd)] impl App {
|
||||||
|
/// Exit the application.
|
||||||
|
#[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
self.exit.exit();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Switch to another set of contextual modes.
|
||||||
|
#[command(Mode = "mode")] pub fn cmd_mode (&mut self, modes: Vec<Arc<str>>) -> Perhaps<AppCmd> {
|
||||||
|
*self.mode_stack().write() = modes.into();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Enter a contextual mode.
|
||||||
|
#[command(Enter = "enter")] pub fn enter_mode (&mut self, mode: Arc<str>) -> Perhaps<AppCmd> {
|
||||||
|
let mut modes = self.mode_stack().write();
|
||||||
|
Ok(if modes.last() != Some(&mode) {
|
||||||
|
modes.push(mode);
|
||||||
|
Some(AppCmd::Leave)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Leave a contextual mode.
|
||||||
|
#[command(Leave = "leave")] fn leave_mode (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
let mut modes = self.mode_stack().write();
|
||||||
|
if modes.len() > 1 {
|
||||||
|
let mode = modes.pop();
|
||||||
|
Ok(mode.map(|mode|AppCmd::Enter { mode }))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Increment the given data axis.
|
||||||
|
#[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc<str>) -> Perhaps<AppCmd> {
|
||||||
|
panic!("inc {axis:?}");
|
||||||
|
//Ok(match (&self.dialog, axis) {
|
||||||
|
//(Dialog::None, _) => todo!(),
|
||||||
|
//(Dialog::Menu(_, _), ControlAxis::Y) => {
|
||||||
|
//AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
|
||||||
|
//None
|
||||||
|
//},
|
||||||
|
//_ => todo!()
|
||||||
|
//})
|
||||||
|
}
|
||||||
|
/// Decrement the given data axis.
|
||||||
|
#[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc<str>) -> Perhaps<AppCmd> {
|
||||||
|
panic!("dec {axis:?}");
|
||||||
|
//Ok(match (&self.dialog, axis) {
|
||||||
|
//(Dialog::None, _) => None,
|
||||||
|
//(Dialog::Menu(_, _), ControlAxis::Y) => {
|
||||||
|
//AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
|
||||||
|
//None
|
||||||
|
//},
|
||||||
|
//_ => todo!()
|
||||||
|
//})
|
||||||
|
}
|
||||||
|
/// Cancel current selection.
|
||||||
|
#[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
todo!("cancel")
|
||||||
|
}
|
||||||
|
/// Confirm current selection.
|
||||||
|
#[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
Ok(match self.dialog.clone() {
|
||||||
|
Dialog::Menu(index, items) => {
|
||||||
|
for command in items[index].1.iter() {
|
||||||
|
App::cmd_dispatch(self, command.clone())?;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Swap currently active modal dialog.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome());
|
||||||
|
/// ```
|
||||||
|
#[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCmd> {
|
||||||
|
let mut dialog = dialog.clone();
|
||||||
|
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||||
|
Ok(Some(AppCmd::Dialog { dialog }))
|
||||||
|
}
|
||||||
|
/// Command the file browser.
|
||||||
|
#[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
/// Commands that alter the current session.
|
||||||
|
#[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
/// Commands pertaining to MIDI clips.
|
||||||
|
#[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
/// Commands pertaining to the MIDI clip editor.
|
||||||
|
#[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps<AppCmd> {
|
||||||
|
if matches!(command, MidiEditCmd::Toggle) {
|
||||||
|
if self.is_editing() {
|
||||||
|
self.leave_mode()?;
|
||||||
|
} else {
|
||||||
|
self.enter_mode("Edit".into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
//#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps<AppCmd> {
|
||||||
|
//Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasProject> ProjectControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject {
|
||||||
|
#[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps<ProjectCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(Load = "load")] fn load (&mut self) -> Perhaps<ProjectCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ClockControl for T where T: HasClock
|
||||||
|
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
|
||||||
|
{}
|
||||||
|
#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock
|
||||||
|
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
|
||||||
|
{
|
||||||
|
#[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().playhead.update_from_usec(usec);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().playhead.update_from_sample(sample);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().playhead.update_from_pulse(pulse);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetBpm {
|
||||||
|
bpm: self.clock().timebase().bpm.set(bpm)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetQuant {
|
||||||
|
quant: self.clock().quant.set(quant)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetSync {
|
||||||
|
sync: self.clock().sync.set(sync)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(Play = "play")] fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().play_from(position)?;
|
||||||
|
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
||||||
|
}
|
||||||
|
#[command(Pause = "pause")] fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().pause_at(position)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(if self.clock().is_rolling() {
|
||||||
|
self.clock().pause_at(Some(position))?;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.clock().play_from(Some(position))?;
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TracksControl for T where T: HasTracks
|
||||||
|
+ HasScenes
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<ClipPool>
|
||||||
|
{}
|
||||||
|
|
||||||
|
#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks
|
||||||
|
+ HasScenes
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<ClipPool>
|
||||||
|
{
|
||||||
|
/// Stop all playing clips
|
||||||
|
#[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Launch multiple clips
|
||||||
|
#[command(Launch = "launch")] fn tracks_launch (&mut self, clips: ClipPool)
|
||||||
|
-> Perhaps<TracksCmd>
|
||||||
|
{
|
||||||
|
if clips.len() == 0 {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) {
|
||||||
|
track.sequencer.enqueue_next(clip.read().as_ref());
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps<TracksCmd>
|
||||||
|
where Self: HasScenes + HasJack<'static> + Sized
|
||||||
|
{
|
||||||
|
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Select = "select")] fn track_select (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let tracks = self.tracks().len();
|
||||||
|
let select = self.selection_mut().select_track(tracks);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let tracks = self.tracks().len();
|
||||||
|
let select = self.selection().select_track_next(tracks);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let select = self.selection_mut().select_track_prev();
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run audio callbacks for every track and every device
|
||||||
|
fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control
|
||||||
|
where Self: HasJack<'static>
|
||||||
|
{
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
for device in track.devices.iter_mut() {
|
||||||
|
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
||||||
|
return Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add multiple tracks
|
||||||
|
fn tracks_add_many (
|
||||||
|
&mut self,
|
||||||
|
count: usize,
|
||||||
|
width: Option<usize>,
|
||||||
|
mins: Arc<[Connect]>,
|
||||||
|
mouts: Arc<[Connect]>,
|
||||||
|
) -> Usually<()>
|
||||||
|
where Self: HasJack<'static> + HasScenes
|
||||||
|
{
|
||||||
|
let track_color_1 = ItemColor::random();
|
||||||
|
let track_color_2 = ItemColor::random();
|
||||||
|
for i in 0..count {
|
||||||
|
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
||||||
|
let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1;
|
||||||
|
if let Some(width) = width {
|
||||||
|
track.width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a track
|
||||||
|
fn tracks_add_one (
|
||||||
|
&mut self,
|
||||||
|
name: Option<Arc<str>>,
|
||||||
|
color: Option<ItemTheme>,
|
||||||
|
mins: Arc<[Connect]>,
|
||||||
|
mouts: Arc<[Connect]>,
|
||||||
|
) -> Usually<(usize, &mut Track)>
|
||||||
|
where Self: HasJack<'static> + HasScenes
|
||||||
|
{
|
||||||
|
let name: Arc<str> = name.map_or_else(
|
||||||
|
||format!("trk{}", self.tracks_last()).into(),
|
||||||
|
|x|x.to_string().into()
|
||||||
|
);
|
||||||
|
*self.tracks_last_mut() += 1;
|
||||||
|
let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?;
|
||||||
|
self.tracks_mut().push(track);
|
||||||
|
let len = self.tracks().len();
|
||||||
|
let index = len - 1;
|
||||||
|
for scene in self.scenes_mut().iter_mut() {
|
||||||
|
while scene.clips.len() < len {
|
||||||
|
scene.clips.append(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((index, &mut self.tracks_mut()[index]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_longest_name (&self) -> usize {
|
||||||
|
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasTrack
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<str>>
|
||||||
|
+ Namespace<ItemTheme>
|
||||||
|
+ Namespace<Option<bool>>
|
||||||
|
> TrackControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<str>>
|
||||||
|
+ Namespace<ItemTheme>
|
||||||
|
+ Namespace<Option<bool>>
|
||||||
|
{
|
||||||
|
#[command(Stop = "track/stop")]
|
||||||
|
fn track_stop (&mut self) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SetMute = "track/mute")]
|
||||||
|
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetSolo = "track/solo")]
|
||||||
|
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetSize = "track/size")]
|
||||||
|
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetZoom = "track/zoom")]
|
||||||
|
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetName = "track/name")]
|
||||||
|
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetColor = "track/color")]
|
||||||
|
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetRec = "track/rec")]
|
||||||
|
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetMon = "track/mon")]
|
||||||
|
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasScenes
|
||||||
|
+ HasTracks
|
||||||
|
+ HasTrackScroll
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
> ScenesControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes
|
||||||
|
+ HasTracks
|
||||||
|
+ HasTrackScroll
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
{
|
||||||
|
#[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps<ScenesCmd> where Self: Sized {
|
||||||
|
let (_index, _) = self.scenes_add_one(None, None)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Select = "select")] fn scene_select (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let scenes = self.scenes().len();
|
||||||
|
let select = self.selection_mut().select_scene(scenes);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let scenes = self.scenes().len();
|
||||||
|
let select = self.selection().select_scene_next(scenes);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let select = self.selection_mut().select_scene_prev();
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Generate the default name for a new scene
|
||||||
|
fn scenes_default_name (&self) -> Arc<str> {
|
||||||
|
format!("s{:3>}", self.scenes().len() + 1).into()
|
||||||
|
}
|
||||||
|
fn scenes_longest_name (&self) -> usize {
|
||||||
|
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
|
}
|
||||||
|
/// Add multiple scenes
|
||||||
|
fn scenes_add_many (&mut self, n: usize)
|
||||||
|
-> Usually<()> where Self: HasTracks
|
||||||
|
{
|
||||||
|
let scene_color_1 = ItemColor::random();
|
||||||
|
let scene_color_2 = ItemColor::random();
|
||||||
|
for i in 0..n {
|
||||||
|
let _ = self.scenes_add_one(None, Some(
|
||||||
|
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Add a scene
|
||||||
|
fn scenes_add_one (&mut self, name: Option<Arc<str>>, color: Option<ItemTheme>)
|
||||||
|
-> Usually<(usize, &mut Scene)>
|
||||||
|
where
|
||||||
|
Self: HasTracks
|
||||||
|
{
|
||||||
|
let scene = Scene {
|
||||||
|
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
|
||||||
|
clips: Pool::from(vec![None;self.tracks().len()]),
|
||||||
|
color: color.unwrap_or_else(ItemTheme::random),
|
||||||
|
};
|
||||||
|
self.scenes_mut().push(scene);
|
||||||
|
let index = self.scenes().len() - 1;
|
||||||
|
Ok((index, &mut self.scenes_mut()[index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasScene
|
||||||
|
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
||||||
|
> SceneControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene
|
||||||
|
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
||||||
|
{
|
||||||
|
#[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCmd> {
|
||||||
|
Ok(self.scene_mut().map(|scene|swap_value(
|
||||||
|
&mut scene.name,
|
||||||
|
&name,
|
||||||
|
|name|SceneCmd::SetName { name }
|
||||||
|
)).transpose()?.flatten())
|
||||||
|
}
|
||||||
|
#[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCmd> {
|
||||||
|
Ok(self.scene_mut().map(|scene|swap_value(
|
||||||
|
&mut scene.color,
|
||||||
|
&color,
|
||||||
|
|color|SceneCmd::SetColor { color }
|
||||||
|
)).transpose()?.flatten())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler
|
||||||
|
+ Namespace<usize>
|
||||||
|
{
|
||||||
|
#[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
|
||||||
|
where Self: Sized
|
||||||
|
{
|
||||||
|
let sampler = self.sampler_mut();
|
||||||
|
let recording = sampler.recording.as_ref().map(|x|x.0);
|
||||||
|
let _ = SamplerCmd::RecFinish.dispatch(self)?;
|
||||||
|
// autoslice: continue recording at next slot
|
||||||
|
if recording != Some(slot) {
|
||||||
|
SamplerCmd::RecBegin { slot }.dispatch(self)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||||
|
let sampler = self.sampler_mut();
|
||||||
|
sampler.recording = Some((
|
||||||
|
slot,
|
||||||
|
Some(Arc::new(RwLock::new(Sample::new(
|
||||||
|
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
|
||||||
|
))))
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps<SamplerCmd> {
|
||||||
|
let sampler = self.sampler_mut();
|
||||||
|
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||||
|
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
||||||
|
sample
|
||||||
|
}); // TODO: undo
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps<SamplerCmd> {
|
||||||
|
self.sampler_mut().recording = None;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||||
|
let sampler = self.sampler_mut();
|
||||||
|
if let Some(ref sample) = sampler.samples.0[slot] {
|
||||||
|
sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||||
|
todo!();
|
||||||
|
//Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasClip
|
||||||
|
+ HasEditor
|
||||||
|
+ Namespace<u32>
|
||||||
|
+ Namespace<f64>
|
||||||
|
+ Namespace<bool>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<u32>>
|
||||||
|
+ Namespace<ClipSlot>
|
||||||
|
> MidiEditControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasClip
|
||||||
|
+ HasEditor
|
||||||
|
+ Namespace<u32>
|
||||||
|
+ Namespace<f64>
|
||||||
|
+ Namespace<bool>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<u32>>
|
||||||
|
+ Namespace<ClipSlot>
|
||||||
|
{
|
||||||
|
#[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> {
|
||||||
|
if self.is_editing() {
|
||||||
|
*self.editor_mut() = None;
|
||||||
|
} else if let Some(clip) = self.clip() {
|
||||||
|
let editor = MidiEditor::new();
|
||||||
|
*editor.mode.clip.0.write() = clip.clone();
|
||||||
|
editor.redraw();
|
||||||
|
*self.editor_mut() = Some(editor);
|
||||||
|
} else {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
Ok(Some(MidiEditCmd::Toggle))
|
||||||
|
}
|
||||||
|
#[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.mode.clip.0 = clip.0;
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.redraw();
|
||||||
|
todo!()
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.put_note(advance);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_pos((pos).min(127));
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_len(len);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_lo((scroll).min(127));
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_pos(pos);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_start(scroll);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_zoom(zoom);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_lock(lock);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MidiClipControl for T where T: HasClip
|
||||||
|
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>> {}
|
||||||
|
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip
|
||||||
|
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>>
|
||||||
|
{
|
||||||
|
/// Create a clip at the current position if it does not already exist.
|
||||||
|
#[command(Upsert = "upsert")] fn clip_upsert (
|
||||||
|
&mut self
|
||||||
|
) -> Perhaps<MidiClipCmd> {
|
||||||
|
Ok(self.clip_mut().map(|mut clip|if clip.is_none() {
|
||||||
|
*clip = Some(Arc::new(RwLock::new(MidiClip {
|
||||||
|
ppq: 24,
|
||||||
|
length: 96,
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
..Default::default()
|
||||||
|
})));
|
||||||
|
None // TODO undo
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetColor = "color")] fn clip_set_color (
|
||||||
|
&mut self, color: Option<ItemTheme>
|
||||||
|
) -> Perhaps<MidiClipCmd> {
|
||||||
|
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetColor {
|
||||||
|
color: Some(clip.set_color(color))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/// Toggle looping for the active clip
|
||||||
|
#[command(SetLoop = "loop")] fn clip_toggle_loop (
|
||||||
|
&mut self, looping: Option<bool>
|
||||||
|
) -> Perhaps<MidiClipCmd> {
|
||||||
|
Ok(self.clip_slot().map_inner_mut(|clip|MidiClipCmd::SetLoop {
|
||||||
|
looping: Some(clip.toggle_loop(looping))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> BrowseControl for T where T:
|
||||||
|
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>> {}
|
||||||
|
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
|
||||||
|
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
|
||||||
|
{
|
||||||
|
/// Toggle visibility of browser
|
||||||
|
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set current directory
|
||||||
|
#[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set current filter
|
||||||
|
#[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set selected item
|
||||||
|
#[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(AxisCmd)] impl App {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(AudioInput)]
|
||||||
|
#[tek_proc::keyword(AudioInput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AudioInputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(AudioOutput)]
|
||||||
|
#[tek_proc::keyword(AudioOutput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AudioOutputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(MidiInput)]
|
||||||
|
#[tek_proc::keyword(MidiInput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MidiInputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(MidiOutput)]
|
||||||
|
#[tek_proc::keyword(MidiOutput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MidiOutputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlAxis {
|
||||||
|
pub fn eval <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
|
||||||
|
eval_word(src, |word|Ok(ControlAxis(word.into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
143
src/conf/mode.rs
143
src/conf/mode.rs
|
|
@ -2,6 +2,95 @@ use crate::*;
|
||||||
|
|
||||||
impl_as_mut!(Mode: |self: Mode| self);
|
impl_as_mut!(Mode: |self: Mode| self);
|
||||||
|
|
||||||
|
/// Has a stack of currently active interaction modes.
|
||||||
|
pub trait HasModeStack {
|
||||||
|
/// Stack of active modes.
|
||||||
|
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModeStack for App {
|
||||||
|
fn mode_stack (&self) -> &RwLock<Vec<Arc<str>>> {
|
||||||
|
&self.modes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasModes {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>>;
|
||||||
|
|
||||||
|
/// Register a mode.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ::{std::sync::Arc, tek::*};
|
||||||
|
/// let config: Config = Default::default();
|
||||||
|
///
|
||||||
|
/// config.modes_add(stringify!(Menu
|
||||||
|
/// (name Menu)
|
||||||
|
/// (info Mode selector.)
|
||||||
|
/// (axis Y Prev Next)
|
||||||
|
/// (bind Confirm)
|
||||||
|
/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog))))
|
||||||
|
/// )).unwrap();
|
||||||
|
///
|
||||||
|
/// let _: Arc<Mode> = config.modes_get("Menu").unwrap();
|
||||||
|
/// ```
|
||||||
|
fn modes_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||||
|
let name = expr.head()?.ok_or("mode: missing name")?;
|
||||||
|
let body = expr.tail()?.ok_or("mode: missing body")?;
|
||||||
|
let mut mode = Mode::default();
|
||||||
|
body.each(&mut mode, |c, s|c.add(s))?;
|
||||||
|
self.modes().write().insert(name.into(), Arc::new(mode));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Get a mode by name.
|
||||||
|
fn modes_get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
|
||||||
|
self.modes().read().get(name.as_ref()).cloned()
|
||||||
|
}
|
||||||
|
/// Run something for each mode.
|
||||||
|
fn modes_for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
||||||
|
for (k, v) in self.modes().read().iter() {
|
||||||
|
let _ = ator(k.as_ref(), v.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Count modes.
|
||||||
|
fn modes_count (&self) -> usize {
|
||||||
|
self.modes().read().len()
|
||||||
|
}
|
||||||
|
/// Clear modes.
|
||||||
|
fn modes_clear (&self) {
|
||||||
|
*self.modes().write() = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModes for App {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||||
|
&self.config.config.modes.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModes for ConfigWatcher {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||||
|
&self.config.modes.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModes for Config {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||||
|
&self.modes.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModes for Modes {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasModes for Mode {
|
||||||
|
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
||||||
|
&self.modes.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Collection of UI modes.
|
/// Collection of UI modes.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -114,38 +203,24 @@ impl Mode {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlAxis {
|
pub fn print_config_modes (config: &Config) {
|
||||||
pub fn eval <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
|
use ::ansi_term::Color::*;
|
||||||
eval_word(src, |word|Ok(ControlAxis(word.into())))
|
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}"))); }
|
||||||
impl HasModes for App {
|
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
|
||||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
print!("\n{}", Blue.paint("KEYS"));
|
||||||
&self.config.config.modes.0
|
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
}
|
println!();
|
||||||
}
|
v.modes_for_each(|k, v|{
|
||||||
|
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
|
||||||
impl HasModes for ConfigWatcher {
|
print!( " INFO={:?}", v.info);
|
||||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
print!( " VIEW={:?}", v.view);
|
||||||
&self.config.modes.0
|
println!(" KEYS={:?}", v.keys);
|
||||||
}
|
});
|
||||||
}
|
print!("{}", Blue.paint("VIEW"));
|
||||||
|
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
|
||||||
impl HasModes for Config {
|
println!();
|
||||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
});
|
||||||
&self.modes.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasModes for Modes {
|
|
||||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasModes for Mode {
|
|
||||||
fn modes (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<Mode>>> {
|
|
||||||
&self.modes.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1691,12 +1691,33 @@ impl ClockView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HasViews {
|
||||||
|
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>;
|
||||||
|
/// Load custom view definition.
|
||||||
|
fn views_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||||
|
self.views().write().insert(
|
||||||
|
expr.head()?.ok_or("view: missing name")?.into(),
|
||||||
|
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn views_clear (&self) {
|
||||||
|
*self.views().write() = Default::default();
|
||||||
|
}
|
||||||
|
fn views_get (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
|
||||||
|
self.views().read().get(name.as_ref()).cloned()
|
||||||
|
}
|
||||||
|
fn views_for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->T) -> () {
|
||||||
|
for (k, v) in self.views().read().iter() {
|
||||||
|
cb(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
impl HasViews for App {
|
impl HasViews for App {
|
||||||
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
||||||
&self.config.config.views.0
|
&self.config.config.views.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasViews for Config {
|
impl HasViews for Config {
|
||||||
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
|
||||||
&self.views.0
|
&self.views.0
|
||||||
|
|
@ -1722,3 +1743,10 @@ impl HasViews for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_config_views (config: &Config) {
|
||||||
|
use ::ansi_term::Color::*;
|
||||||
|
config.views_for_each(|k, v|{
|
||||||
|
println!("{} {} {}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")), v.source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ pub(crate) use ::{
|
||||||
RawRwLock,
|
RawRwLock,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
notify_debouncer_full::notify::{
|
||||||
|
RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "prof2")]
|
#[cfg(feature = "prof2")]
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl App {
|
||||||
/// every integer between 1 and 2^53 can be represented exactly.
|
/// every integer between 1 and 2^53 can be represented exactly.
|
||||||
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
/// This should mean that, even at 192kHz sampling rate, over 1 year of audio
|
||||||
/// can be clocked in microseconds with f64 without losing precision.
|
/// can be clocked in microseconds with f64 without losing precision.
|
||||||
pub trait TimeUnit: InteriorMutable<f64> {}
|
pub trait TimeUnit {}
|
||||||
|
|
||||||
pub const DEFAULT_PPQ: f64 = 96.0;
|
pub const DEFAULT_PPQ: f64 = 96.0;
|
||||||
|
|
||||||
|
|
@ -324,62 +324,6 @@ impl Clock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_has!(Clock: |self: Track|self.sequencer.clock);
|
|
||||||
impl_default!(Timebase: Self::new(48000f64, 150f64, DEFAULT_PPQ));
|
|
||||||
|
|
||||||
/// Define and implement a unit of time
|
|
||||||
#[macro_export] macro_rules! impl_time_unit {
|
|
||||||
($T:ident) => {
|
|
||||||
impl Gettable<f64> for $T {
|
|
||||||
fn get (&self) -> f64 { self.0.load(Relaxed) }
|
|
||||||
}
|
|
||||||
impl InteriorMutable<f64> for $T {
|
|
||||||
fn set (&self, value: f64) -> f64 {
|
|
||||||
let old = self.get();
|
|
||||||
self.0.store(value, Relaxed);
|
|
||||||
old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TimeUnit for $T {}
|
|
||||||
impl_op!($T, Add, add, |a, b|{a + b});
|
|
||||||
impl_op!($T, Sub, sub, |a, b|{a - b});
|
|
||||||
impl_op!($T, Mul, mul, |a, b|{a * b});
|
|
||||||
impl_op!($T, Div, div, |a, b|{a / b});
|
|
||||||
impl_op!($T, Rem, rem, |a, b|{a % b});
|
|
||||||
impl From<f64> for $T { fn from (value: f64) -> Self { Self(value.into()) } }
|
|
||||||
impl From<usize> for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } }
|
|
||||||
impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } }
|
|
||||||
impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } }
|
|
||||||
impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } }
|
|
||||||
impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } }
|
|
||||||
impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_time_unit!(SampleCount);
|
|
||||||
impl_time_unit!(SampleRate);
|
|
||||||
impl_time_unit!(Microsecond);
|
|
||||||
impl_time_unit!(Quantize);
|
|
||||||
impl_time_unit!(Ppq);
|
|
||||||
impl_time_unit!(Pulse);
|
|
||||||
impl_time_unit!(Bpm);
|
|
||||||
impl_time_unit!(LaunchSync);
|
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat)
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -156,3 +156,63 @@ impl Slot<Sample> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modes for clip pool
|
||||||
|
#[derive(Debug, Clone)] pub enum PoolMode {
|
||||||
|
/// Renaming a pattern
|
||||||
|
Rename(usize, Arc<str>),
|
||||||
|
/// Editing the length of a pattern
|
||||||
|
Length(usize, usize, ClipLengthFocus),
|
||||||
|
/// Load clip from disk
|
||||||
|
Import(usize, Browse),
|
||||||
|
/// Save clip to disk
|
||||||
|
Export(usize, Browse),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focused field of `ClipLength`
|
||||||
|
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
|
||||||
|
/// Editing the number of bars
|
||||||
|
Bar,
|
||||||
|
/// Editing the number of beats
|
||||||
|
Beat,
|
||||||
|
/// Editing the number of ticks
|
||||||
|
Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipLengthFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
use ClipLengthFocus::*;
|
||||||
|
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
use ClipLengthFocus::*;
|
||||||
|
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays and edits clip length.
|
||||||
|
#[derive(Clone, Debug, Default)] pub struct ClipLength {
|
||||||
|
/// Pulses per beat (quaver)
|
||||||
|
pub ppq: usize,
|
||||||
|
/// Beats per bar
|
||||||
|
pub bpb: usize,
|
||||||
|
/// Length of clip in pulses
|
||||||
|
pub pulses: usize,
|
||||||
|
/// Selected subdivision
|
||||||
|
pub focus: Option<ClipLengthFocus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipLength {
|
||||||
|
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
|
||||||
|
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
||||||
|
}
|
||||||
|
pub fn bars (&self) -> usize {
|
||||||
|
self.pulses / (self.bpb * self.ppq)
|
||||||
|
}
|
||||||
|
pub fn beats (&self) -> usize {
|
||||||
|
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
||||||
|
}
|
||||||
|
pub fn ticks (&self) -> usize {
|
||||||
|
self.pulses % self.ppq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
2200
src/tek.rs
2200
src/tek.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue