mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-01-31 08:36:40 +01:00
Compare commits
8 commits
dcde588c7b
...
6ec445aab7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ec445aab7 | ||
|
|
044f60ebcb | ||
|
|
ac7fbdb779 | ||
|
|
a8f0fbb897 | ||
|
|
204e26a324 | ||
|
|
b1074bd831 | ||
|
|
211b433b3a | ||
|
|
2fe2cc47db |
17 changed files with 1499 additions and 1443 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -8,3 +8,6 @@
|
||||||
[submodule "deps/rust-jack"]
|
[submodule "deps/rust-jack"]
|
||||||
path = deps/rust-jack
|
path = deps/rust-jack
|
||||||
url = https://codeberg.org/unspeaker/rust-jack
|
url = https://codeberg.org/unspeaker/rust-jack
|
||||||
|
[submodule "deps/dizzle"]
|
||||||
|
path = deps/dizzle
|
||||||
|
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
||||||
|
|
|
||||||
47
Cargo.lock
generated
47
Cargo.lock
generated
|
|
@ -79,6 +79,15 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.20"
|
version = "0.6.20"
|
||||||
|
|
@ -646,6 +655,16 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dizzle"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"const_panic",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"konst",
|
||||||
|
"thiserror 2.0.16",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlib"
|
name = "dlib"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -2383,6 +2402,7 @@ dependencies = [
|
||||||
name = "tek"
|
name = "tek"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -2458,41 +2478,24 @@ dependencies = [
|
||||||
name = "tengri"
|
name = "tengri"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tengri_core",
|
"dizzle",
|
||||||
"tengri_dsl",
|
|
||||||
"tengri_input",
|
"tengri_input",
|
||||||
"tengri_output",
|
"tengri_output",
|
||||||
"tengri_tui",
|
"tengri_tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tengri_core"
|
|
||||||
version = "0.14.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tengri_dsl"
|
|
||||||
version = "0.14.0"
|
|
||||||
dependencies = [
|
|
||||||
"const_panic",
|
|
||||||
"itertools 0.14.0",
|
|
||||||
"konst",
|
|
||||||
"tengri_core",
|
|
||||||
"thiserror 2.0.16",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tengri_input"
|
name = "tengri_input"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tengri_core",
|
"dizzle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tengri_output"
|
name = "tengri_output"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tengri_core",
|
"dizzle",
|
||||||
"tengri_dsl",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2502,13 +2505,11 @@ dependencies = [
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"better-panic",
|
"better-panic",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"konst",
|
"dizzle",
|
||||||
"palette",
|
"palette",
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"tengri_core",
|
|
||||||
"tengri_dsl",
|
|
||||||
"tengri_input",
|
"tengri_input",
|
||||||
"tengri_output",
|
"tengri_output",
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "./app", "./engine", "./device" ]
|
members = [ "./app", "./engine", "./device" ]
|
||||||
exclude = [ "./deps/tengri" ]
|
exclude = [ "./deps/tengri", "./deps/dizzle" ]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ path = "tek.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tek"
|
name = "tek"
|
||||||
path = "tek_cli.rs"
|
path = "tek.rs"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")']
|
[target.'cfg(target_os = "linux")']
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
@ -32,6 +32,7 @@ uuid = { workspace = true, optional = true }
|
||||||
wavers = { workspace = true, optional = true }
|
wavers = { workspace = true, optional = true }
|
||||||
winit = { workspace = true, optional = true }
|
winit = { workspace = true, optional = true }
|
||||||
xdg = { workspace = true }
|
xdg = { workspace = true }
|
||||||
|
ansi_term = "0.12.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
|
|
|
||||||
10
app/tek.edn
10
app/tek.edn
|
|
@ -38,12 +38,6 @@
|
||||||
:ports/out
|
:ports/out
|
||||||
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill :dialog/menu)))))))
|
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill :dialog/menu)))))))
|
||||||
|
|
||||||
(view :menu (bsp/s
|
|
||||||
(push/y 4 (fixed/xy 20 2 (bg (g 0) :debug)))
|
|
||||||
(fixed 20 2 (bg (g 20) (push/x 2 :debug)))))
|
|
||||||
|
|
||||||
(view :menu (bsp/s (fixed/y 4 :debug) :debug))
|
|
||||||
|
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
||||||
(fill/x (align/w (text L-AUDIO-OUT)))
|
(fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
||||||
|
|
@ -111,9 +105,7 @@
|
||||||
(mode :add-device (keys :add-device)) (mode :browse (keys :browse)) (mode :rename (keys :input))
|
(mode :add-device (keys :add-device)) (mode :browse (keys :browse)) (mode :rename (keys :input))
|
||||||
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
||||||
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
||||||
(keys :clock :arranger :global) :arranger)
|
(keys :clock :arranger :global) (bsp/n
|
||||||
|
|
||||||
(view :arranger (bsp/n
|
|
||||||
:status
|
:status
|
||||||
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
||||||
|
|
||||||
|
|
|
||||||
1861
app/tek.rs
1861
app/tek.rs
File diff suppressed because it is too large
Load diff
325
app/tek_bind.rs
325
app/tek_bind.rs
|
|
@ -1,325 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>;
|
|
||||||
|
|
||||||
/// A collection of input bindings.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EventMap<E, C>(
|
|
||||||
/// Map of each event (e.g. key combination) to
|
|
||||||
/// all command expressions bound to it by
|
|
||||||
/// all loaded input layers.
|
|
||||||
pub BTreeMap<E, Vec<Binding<C>>>
|
|
||||||
);
|
|
||||||
|
|
||||||
/// An input binding.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Binding<C> {
|
|
||||||
pub commands: Arc<[C]>,
|
|
||||||
pub condition: Option<Condition>,
|
|
||||||
pub description: Option<Arc<str>>,
|
|
||||||
pub source: Option<Arc<PathBuf>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Input bindings are only returned if this evaluates to true
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
|
|
||||||
|
|
||||||
/// Default is always empty map regardless if `E` and `C` implement [Default].
|
|
||||||
impl<E, C> Default for EventMap<E, C> {
|
|
||||||
fn default () -> Self { Self(Default::default()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Clone + Ord, C> EventMap<E, C> {
|
|
||||||
/// Create a new event map
|
|
||||||
pub fn new () -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
/// Add a binding to an owned event map.
|
|
||||||
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
|
||||||
self.add(event, binding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Add a binding to an event map.
|
|
||||||
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
|
|
||||||
if !self.0.contains_key(&event) {
|
|
||||||
self.0.insert(event.clone(), Default::default());
|
|
||||||
}
|
|
||||||
self.0.get_mut(&event).unwrap().push(binding);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
/// Return the binding(s) that correspond to an event.
|
|
||||||
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
|
|
||||||
self.0.get(event).map(|x|x.as_slice())
|
|
||||||
}
|
|
||||||
/// Return the first binding that corresponds to an event, considering conditions.
|
|
||||||
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
|
|
||||||
self.query(event)
|
|
||||||
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventMap<TuiEvent, Arc<str>> {
|
|
||||||
pub fn load_into (binds: &Binds, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
|
||||||
println!("EventMap::load_into: {}: {body:?}", name.as_ref());
|
|
||||||
let mut map = Self::new();
|
|
||||||
body.each(|item|if item.expr().head() == Ok(Some("see")) {
|
|
||||||
// TODO
|
|
||||||
Ok(())
|
|
||||||
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
|
||||||
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
|
|
||||||
map.add(key, Binding {
|
|
||||||
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
|
||||||
condition: None,
|
|
||||||
description: None,
|
|
||||||
source: None
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
} else if Some(":char") == item.expr()?.head()? {
|
|
||||||
// TODO
|
|
||||||
return Ok(())
|
|
||||||
} else {
|
|
||||||
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
|
||||||
})?;
|
|
||||||
binds.write().unwrap().insert(name.as_ref().into(), map);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C> Binding<C> {
|
|
||||||
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
|
||||||
let command: Option<C> = None;
|
|
||||||
let condition: Option<Condition> = None;
|
|
||||||
let description: Option<Arc<str>> = None;
|
|
||||||
let source: Option<Arc<PathBuf>> = None;
|
|
||||||
if let Some(command) = command {
|
|
||||||
Ok(Self { commands: [command].into(), condition, description, source })
|
|
||||||
} else {
|
|
||||||
Err(format!("no command in {dsl:?}").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_debug!(Condition |self, w| { write!(w, "*") });
|
|
||||||
|
|
||||||
handle!(TuiIn:|self: App, input|{
|
|
||||||
let mut commands = vec![];
|
|
||||||
for id in self.mode.keys.iter() {
|
|
||||||
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
|
||||||
if let Some(bindings) = event_map.query(input.event()) {
|
|
||||||
for binding in bindings {
|
|
||||||
for command in binding.commands.iter() {
|
|
||||||
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
|
||||||
commands.push(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for command in commands.into_iter() {
|
|
||||||
let result = command.execute(self);
|
|
||||||
match result {
|
|
||||||
Ok(undo) => {
|
|
||||||
self.history.push((command, undo));
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
self.history.push((command, None));
|
|
||||||
return Err(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
});
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
pub enum Axis { X, Y, Z, I }
|
|
||||||
|
|
||||||
impl<'a> DslNs<'a, AppCommand> for App {}
|
|
||||||
impl<'a> DslNsExprs<'a, AppCommand> for App {}
|
|
||||||
impl<'a> DslNsWords<'a, AppCommand> for App {
|
|
||||||
dsl_words!('a |app| -> AppCommand {
|
|
||||||
"x/inc" => AppCommand::Inc { axis: Axis::X },
|
|
||||||
"x/dec" => AppCommand::Dec { axis: Axis::X },
|
|
||||||
"y/inc" => AppCommand::Inc { axis: Axis::Y },
|
|
||||||
"y/dec" => AppCommand::Dec { axis: Axis::Y },
|
|
||||||
"confirm" => AppCommand::Confirm,
|
|
||||||
"cancel" => AppCommand::Cancel,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
|
||||||
|
|
||||||
def_command!(AppCommand: |app: App| {
|
|
||||||
Nop => Ok(None),
|
|
||||||
Confirm => Ok(match &app.dialog {
|
|
||||||
Dialog::Menu(index, items) => {
|
|
||||||
let callback = items.0[*index].1.clone();
|
|
||||||
callback(app)?;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => todo!(),
|
|
||||||
}),
|
|
||||||
Cancel => todo!(), // TODO delegate:
|
|
||||||
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
|
||||||
(Dialog::None, _) => todo!(),
|
|
||||||
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
|
||||||
.execute(app)?,
|
|
||||||
_ => todo!()
|
|
||||||
}),
|
|
||||||
Dec { axis: Axis } => Ok(match (&app.dialog, axis) {
|
|
||||||
(Dialog::None, _) => None,
|
|
||||||
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_prev() }
|
|
||||||
.execute(app)?,
|
|
||||||
_ => todo!()
|
|
||||||
}),
|
|
||||||
SetDialog { dialog: Dialog } => {
|
|
||||||
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog })
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//AppCommand => {
|
|
||||||
//("x/inc" /
|
|
||||||
//("stop-all") => todo!(),//app.project.stop_all(),
|
|
||||||
//("enqueue", clip: Option<Arc<RwLock<MidiClip>>>) => todo!(),
|
|
||||||
//("history", delta: isize) => todo!(),
|
|
||||||
//("zoom", zoom: usize) => todo!(),
|
|
||||||
//("select", selection: Selection) => todo!(),
|
|
||||||
//("dialog" / command: DialogCommand) => todo!(),
|
|
||||||
//("project" / command: ArrangementCommand) => todo!(),
|
|
||||||
//("clock" / command: ClockCommand) => todo!(),
|
|
||||||
//("sampler" / command: SamplerCommand) => todo!(),
|
|
||||||
//("pool" / command: PoolCommand) => todo!(),
|
|
||||||
//("edit" / editor: MidiEditCommand) => todo!(),
|
|
||||||
//};
|
|
||||||
|
|
||||||
//DialogCommand;
|
|
||||||
|
|
||||||
//ArrangementCommand;
|
|
||||||
|
|
||||||
//ClockCommand;
|
|
||||||
|
|
||||||
//SamplerCommand;
|
|
||||||
|
|
||||||
//PoolCommand;
|
|
||||||
|
|
||||||
//MidiEditCommand;
|
|
||||||
|
|
||||||
|
|
||||||
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
|
||||||
//#[derive(Clone, Debug)]
|
|
||||||
//pub enum DialogCommand {
|
|
||||||
//Open { dialog: Dialog },
|
|
||||||
//Close
|
|
||||||
//}
|
|
||||||
|
|
||||||
//impl Command<Option<Dialog>> for DialogCommand {
|
|
||||||
//fn execute (self, state: &mut Option<Dialog>) -> Perhaps<Self> {
|
|
||||||
//match self {
|
|
||||||
//Self::Open { dialog } => {
|
|
||||||
//*state = Some(dialog);
|
|
||||||
//},
|
|
||||||
//Self::Close => {
|
|
||||||
//*state = None;
|
|
||||||
//}
|
|
||||||
//};
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//dsl!(DialogCommand: |self: Dialog, iter|todo!());
|
|
||||||
//Dsl::take(&mut self.dialog, iter));
|
|
||||||
|
|
||||||
//#[tengri_proc::command(Option<Dialog>)]//Nope.
|
|
||||||
//impl DialogCommand {
|
|
||||||
//fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
|
||||||
//*dialog = Some(new);
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
|
||||||
//*dialog = None;
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//dsl_bind!(AppCommand: App {
|
|
||||||
//enqueue = |app, clip: Option<Arc<RwLock<MidiClip>>>| { todo!() };
|
|
||||||
//history = |app, delta: isize| { todo!() };
|
|
||||||
//zoom = |app, zoom: usize| { todo!() };
|
|
||||||
//stop_all = |app| { app.tracks_stop_all(); Ok(None) };
|
|
||||||
////dialog = |app, command: DialogCommand|
|
|
||||||
////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?);
|
|
||||||
//project = |app, command: ArrangementCommand|
|
|
||||||
//Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?);
|
|
||||||
//clock = |app, command: ClockCommand|
|
|
||||||
//Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c}));
|
|
||||||
//sampler = |app, command: SamplerCommand|
|
|
||||||
//Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command}))
|
|
||||||
//.transpose()?.flatten());
|
|
||||||
//pool = |app, command: PoolCommand| {
|
|
||||||
//let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?;
|
|
||||||
//// update linked editor after pool action
|
|
||||||
//match command {
|
|
||||||
//// autoselect: automatically load selected clip in editor
|
|
||||||
//PoolCommand::Select { .. } |
|
|
||||||
//// autocolor: update color in all places simultaneously
|
|
||||||
//PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
|
|
||||||
//let clip = app.pool.clip().clone();
|
|
||||||
//app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
|
|
||||||
//},
|
|
||||||
//_ => None
|
|
||||||
//};
|
|
||||||
//Ok(undo)
|
|
||||||
//};
|
|
||||||
//select = |app, selection: Selection| {
|
|
||||||
//*app.project.selection_mut() = selection;
|
|
||||||
////todo!
|
|
||||||
////if let Some(ref mut editor) = app.editor_mut() {
|
|
||||||
////editor.set_clip(match selection {
|
|
||||||
////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app
|
|
||||||
////.project
|
|
||||||
////.scenes.get(scene)
|
|
||||||
////.map(|s|s.clips.get(track))
|
|
||||||
////=>
|
|
||||||
////Some(clip),
|
|
||||||
////_ =>
|
|
||||||
////None
|
|
||||||
////});
|
|
||||||
////}
|
|
||||||
//Ok(None)
|
|
||||||
////("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) {
|
|
||||||
////(0, 0) => Self::Select(Selection::Mix),
|
|
||||||
////(t, 0) => Self::Select(Selection::Track(t)),
|
|
||||||
////(0, s) => Self::Select(Selection::Scene(s)),
|
|
||||||
////(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) })))
|
|
||||||
//// autoedit: load focused clip in editor.
|
|
||||||
//};
|
|
||||||
////fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
|
|
||||||
////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
|
|
||||||
////}
|
|
||||||
////fn launch (app: &mut App) -> Perhaps<Self> {
|
|
||||||
////app.project.launch();
|
|
||||||
////Ok(None)
|
|
||||||
////}
|
|
||||||
//toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) };
|
|
||||||
//editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() {
|
|
||||||
//let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
|
|
||||||
//// update linked sampler after editor action
|
|
||||||
//app.project.sampler_mut().map(|sampler|match command {
|
|
||||||
//// autoselect: automatically select sample in sampler
|
|
||||||
//MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
|
|
||||||
//_ => {}
|
|
||||||
//});
|
|
||||||
//undo
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//});
|
|
||||||
//});
|
|
||||||
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
|
||||||
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
|
||||||
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
|
||||||
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Configuration.
|
|
||||||
///
|
|
||||||
/// Contains mode, view, and bind definitions.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
pub dirs: BaseDirectories,
|
|
||||||
pub modes: Modes,
|
|
||||||
pub views: Views,
|
|
||||||
pub binds: Binds,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
const CONFIG: &'static str = "tek.edn";
|
|
||||||
const DEFAULTS: &'static str = include_str!("./tek.edn");
|
|
||||||
|
|
||||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
|
||||||
Self {
|
|
||||||
dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn init (&mut self) -> Usually<()> {
|
|
||||||
self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn init_file (
|
|
||||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
|
||||||
) -> Usually<()> {
|
|
||||||
if self.dirs.find_config_file(path).is_none() {
|
|
||||||
println!("Creating {path:?}");
|
|
||||||
std::fs::write(self.dirs.place_config_file(path)?, defaults)?;
|
|
||||||
}
|
|
||||||
Ok(if let Some(path) = self.dirs.find_config_file(path) {
|
|
||||||
println!("Loading {path:?}");
|
|
||||||
let src = std::fs::read_to_string(&path)?;
|
|
||||||
src.as_str().each(move|item|each(self, item))?;
|
|
||||||
} else {
|
|
||||||
return Err(format!("{path}: not found").into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> {
|
|
||||||
dsl.each(|item|if let Some(expr) = item.expr()? {
|
|
||||||
let head = expr.head()?;
|
|
||||||
let tail = expr.tail()?;
|
|
||||||
let name = tail.head()?;
|
|
||||||
let body = tail.tail()?;
|
|
||||||
println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
|
||||||
match head {
|
|
||||||
Some("mode") if let Some(name) = name =>
|
|
||||||
Mode::<Arc<str>>::load_into(&self.modes, &name, &body)?,
|
|
||||||
Some("keys") if let Some(name) = name =>
|
|
||||||
EventMap::<TuiEvent, Arc<str>>::load_into(&self.binds, &name, &body)?,
|
|
||||||
Some("view") if let Some(name) = name => {
|
|
||||||
self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into());
|
|
||||||
},
|
|
||||||
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
138
app/tek_cli.rs
138
app/tek_cli.rs
|
|
@ -1,138 +0,0 @@
|
||||||
pub(crate) use tek::*;
|
|
||||||
pub(crate) use tek_device::*;
|
|
||||||
pub(crate) use tek_engine::*;
|
|
||||||
pub(crate) use tengri::{*, tui::*};
|
|
||||||
pub(crate) use clap::{self, Parser, Subcommand};
|
|
||||||
|
|
||||||
/// Application entrypoint.
|
|
||||||
pub fn main () -> Usually<()> {
|
|
||||||
Cli::parse().run()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CLI header
|
|
||||||
const HEADER: &'static str = r#"
|
|
||||||
~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ ~~~~~~ ~ ~~~
|
|
||||||
~~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0, 2025 sum(m)er @ the nose of the cat. ~
|
|
||||||
~~~ ╨ ~ ╙──╜ ╨ ╜ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ ~~ ~~ ~ ~~
|
|
||||||
On first run, Tek will create configuration and state dirs:
|
|
||||||
* [x] ~/.config/tek - config
|
|
||||||
* [ ] ~/.local/share/tek - projects
|
|
||||||
* [ ] ~/.local/lib/tek - plugins
|
|
||||||
* [ ] ~/.cache/tek - cache
|
|
||||||
~"#;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
|
||||||
pub struct Cli {
|
|
||||||
/// Pre-defined configuration modes.
|
|
||||||
///
|
|
||||||
/// TODO: Replace these with scripted configurations.
|
|
||||||
#[command(subcommand)] mode: Option<LaunchMode>,
|
|
||||||
/// Name of JACK client
|
|
||||||
#[arg(short='n', long)] name: Option<String>,
|
|
||||||
/// Whether to attempt to become transport master
|
|
||||||
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
|
||||||
/// Whether to sync to external transport master
|
|
||||||
#[arg(short='s', long, default_value_t = true)] sync_follow: bool,
|
|
||||||
/// Initial tempo in beats per minute
|
|
||||||
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
|
||||||
/// Whether to include a transport toolbar (default: true)
|
|
||||||
#[arg(short='t', long, default_value_t = true)] show_clock: bool,
|
|
||||||
/// MIDI outs to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='I', long)] midi_from: Vec<String>,
|
|
||||||
/// MIDI outs to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
|
||||||
/// MIDI ins to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='O', long)] midi_to: Vec<String>,
|
|
||||||
/// MIDI ins to connect to (multiple instances accepted)
|
|
||||||
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
|
||||||
/// Audio outs to connect to left input
|
|
||||||
#[arg(short='l', long)] left_from: Vec<String>,
|
|
||||||
/// Audio outs to connect to right input
|
|
||||||
#[arg(short='r', long)] right_from: Vec<String>,
|
|
||||||
/// Audio ins to connect from left output
|
|
||||||
#[arg(short='L', long)] left_to: Vec<String>,
|
|
||||||
/// Audio ins to connect from right output
|
|
||||||
#[arg(short='R', long)] right_to: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application modes
|
|
||||||
#[derive(Debug, Clone, Subcommand)]
|
|
||||||
pub enum LaunchMode {
|
|
||||||
/// Create a new session instead of loading the previous one.
|
|
||||||
New,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cli {
|
|
||||||
fn midi_froms (&self) -> Vec<Connect> {
|
|
||||||
Connect::collect(&self.midi_from, &[] as &[&str], &self.midi_from_re)
|
|
||||||
}
|
|
||||||
fn midi_tos (&self) -> Vec<Connect> {
|
|
||||||
Connect::collect(&self.midi_to, &[] as &[&str], &self.midi_to_re)
|
|
||||||
}
|
|
||||||
pub fn run (&self) -> Usually<()> {
|
|
||||||
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
|
|
||||||
let tracks = vec![];
|
|
||||||
let scenes = vec![];
|
|
||||||
let empty = &[] as &[&str];
|
|
||||||
let left_froms = Connect::collect(&self.left_from, empty, empty);
|
|
||||||
let left_tos = Connect::collect(&self.left_to, empty, empty);
|
|
||||||
let right_froms = Connect::collect(&self.right_from, empty, empty);
|
|
||||||
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
|
||||||
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
|
||||||
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
|
||||||
let mut config = Config::new(None);
|
|
||||||
config.init()?;
|
|
||||||
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
|
||||||
let midi_ins = {
|
|
||||||
let mut midi_ins = vec![];
|
|
||||||
for (index, connect) in self.midi_froms().iter().enumerate() {
|
|
||||||
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
|
||||||
}
|
|
||||||
midi_ins
|
|
||||||
};
|
|
||||||
let midi_outs = {
|
|
||||||
let mut midi_outs = vec![];
|
|
||||||
for (index, connect) in self.midi_tos().iter().enumerate() {
|
|
||||||
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
|
||||||
};
|
|
||||||
midi_outs
|
|
||||||
};
|
|
||||||
let project = Arrangement {
|
|
||||||
name: Default::default(),
|
|
||||||
color: ItemTheme::random(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
clock: Clock::new(&jack, self.bpm)?,
|
|
||||||
tracks,
|
|
||||||
scenes,
|
|
||||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
|
||||||
midi_ins,
|
|
||||||
midi_outs,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let app = App {
|
|
||||||
jack: jack.clone(),
|
|
||||||
color: ItemTheme::random(),
|
|
||||||
dialog: Dialog::welcome(),
|
|
||||||
mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(),
|
|
||||||
config,
|
|
||||||
project,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
jack.sync_lead(self.sync_lead, |mut state|{
|
|
||||||
let clock = app.clock();
|
|
||||||
clock.playhead.update_from_sample(state.position.frame() as f64);
|
|
||||||
state.position.bbt = Some(clock.bbt());
|
|
||||||
state.position
|
|
||||||
})?;
|
|
||||||
jack.sync_follow(self.sync_follow)?;
|
|
||||||
Ok(app)
|
|
||||||
})?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_cli () {
|
|
||||||
use clap::CommandFactory;
|
|
||||||
Cli::command().debug_assert();
|
|
||||||
//let jack = Jack::default();
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#[allow(unused)]
|
|
||||||
pub(crate) use ::{
|
|
||||||
tek_device::{*, tek_engine::*},
|
|
||||||
tengri::{
|
|
||||||
Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug, from,
|
|
||||||
wrap_inc, wrap_dec,
|
|
||||||
dsl::*,
|
|
||||||
input::*,
|
|
||||||
output::*,
|
|
||||||
tui::{
|
|
||||||
*,
|
|
||||||
ratatui::{
|
|
||||||
self,
|
|
||||||
prelude::{Rect, Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}},
|
|
||||||
widgets::{Widget, canvas::{Canvas, Line}},
|
|
||||||
},
|
|
||||||
crossterm::{
|
|
||||||
self,
|
|
||||||
event::{Event, KeyCode::{self, *}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
|
|
||||||
error::Error,
|
|
||||||
collections::BTreeMap,
|
|
||||||
fmt::Write,
|
|
||||||
cmp::Ord,
|
|
||||||
ffi::OsString,
|
|
||||||
fmt::{Debug, Formatter},
|
|
||||||
fs::File,
|
|
||||||
ops::{Add, Sub, Mul, Div, Rem},
|
|
||||||
thread::JoinHandle
|
|
||||||
},
|
|
||||||
xdg::BaseDirectories,
|
|
||||||
atomic_float::*
|
|
||||||
};
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
|
||||||
|
|
||||||
/// A set of currently active view and keys definitions,
|
|
||||||
/// with optional name and description.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Mode<D: Dsl + Ord> {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub name: Vec<D>,
|
|
||||||
pub info: Vec<D>,
|
|
||||||
pub view: Vec<D>,
|
|
||||||
pub keys: Vec<D>,
|
|
||||||
pub modes: Modes,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: Dsl + Ord> Draw<TuiOut> for Mode<D> {
|
|
||||||
fn draw (&self, _to: &mut TuiOut) {
|
|
||||||
//self.content().draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mode<Arc<str>> {
|
|
||||||
|
|
||||||
pub fn load_into (modes: &Modes, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
|
||||||
let mut mode = Self::default();
|
|
||||||
println!("Mode::load_into: {}: {body:?}", name.as_ref());
|
|
||||||
body.each(|item|mode.load_one(item))?;
|
|
||||||
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> {
|
|
||||||
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
|
|
||||||
println!("Mode::load_one: {head} {:?}", expr.tail());
|
|
||||||
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
|
||||||
match head {
|
|
||||||
"name" => self.name.push(tail.into()),
|
|
||||||
"info" => self.info.push(tail.into()),
|
|
||||||
"view" => self.view.push(tail.into()),
|
|
||||||
"keys" => tail.each(|expr|{
|
|
||||||
self.keys.push(expr.trim().into());
|
|
||||||
Ok(())
|
|
||||||
})?,
|
|
||||||
"mode" => if let Some(id) = tail.head()? {
|
|
||||||
Self::load_into(&self.modes, &id, &tail.tail())?;
|
|
||||||
} else {
|
|
||||||
return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into());
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into())
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else if let Ok(Some(word)) = dsl.word() {
|
|
||||||
self.view.push(word.into());
|
|
||||||
} else {
|
|
||||||
return Err(format!("Mode::load_one: unexpected: {dsl:?}").into());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_cli () {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
cli::Cli::command().debug_assert();
|
||||||
|
//let jack = Jack::default();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
||||||
let mut app = App::default();
|
let mut app = model::App::default();
|
||||||
let _ = app.scene_add(None, None)?;
|
let _ = app.scene_add(None, None)?;
|
||||||
let _ = app.update_clock();
|
let _ = app.update_clock();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -53,7 +59,7 @@ use crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_iter () {
|
#[cfg(test)] #[test] fn test_view_iter () {
|
||||||
let mut app = App::default();
|
let mut app = model::App::default();
|
||||||
app.project.editor = Some(Default::default());
|
app.project.editor = Some(Default::default());
|
||||||
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||||
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||||
|
|
@ -64,7 +70,7 @@ use crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_sizes () {
|
#[cfg(test)] #[test] fn test_view_sizes () {
|
||||||
let app = App::default();
|
let app = model::App::default();
|
||||||
let _ = app.project.w();
|
let _ = app.project.w();
|
||||||
//let _ = app.project.w_sidebar();
|
//let _ = app.project.w_sidebar();
|
||||||
//let _ = app.project.w_tracks_area();
|
//let _ = app.project.w_tracks_area();
|
||||||
|
|
|
||||||
353
app/tek_view.rs
353
app/tek_view.rs
|
|
@ -1,353 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
|
||||||
|
|
||||||
impl Draw<TuiOut> for App {
|
|
||||||
fn draw (&self, to: &mut TuiOut) {
|
|
||||||
for (index, dsl) in self.mode.view.iter().enumerate() {
|
|
||||||
if let Err(e) = self.view(to, dsl) {
|
|
||||||
panic!("render #{index} failed ({e}): {dsl}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl View<TuiOut, ()> for App {
|
|
||||||
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: &'a impl DslExpr) -> Usually<()> {
|
|
||||||
if evaluate_output_expression(self, to, expr)?
|
|
||||||
|| evaluate_output_expression_tui(self, to, expr)? {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(format!("App::view_expr: unexpected: {expr:?}").into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: &'a impl DslExpr) -> Usually<()> {
|
|
||||||
let mut frags = dsl.src()?.unwrap().split("/");
|
|
||||||
match frags.next() {
|
|
||||||
Some(":logo") => to.place(&view_logo()),
|
|
||||||
Some(":status") => to.place(&Fixed::Y(1, "TODO: Status Bar")),
|
|
||||||
Some(":meters") => match frags.next() {
|
|
||||||
Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::Y(Align::s("Input Meters")))),
|
|
||||||
Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::Y(Align::s("Output Meters")))),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
Some(":tracks") => match frags.next() {
|
|
||||||
None => to.place(&"TODO tracks"),
|
|
||||||
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Names")))),
|
|
||||||
Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Inputs")))),
|
|
||||||
Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Devices")))),
|
|
||||||
Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Outputs")))),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
Some(":scenes") => match frags.next() {
|
|
||||||
None => to.place(&"TODO scenes"),
|
|
||||||
Some(":scenes/names") => to.place(&"TODO Scene Names"),
|
|
||||||
_ => panic!()
|
|
||||||
},
|
|
||||||
Some(":editor") => to.place(&"TODO Editor"),
|
|
||||||
Some(":dialog") => match frags.next() {
|
|
||||||
Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog {
|
|
||||||
let items = items.clone();
|
|
||||||
let selected = selected;
|
|
||||||
Some(Fill::XY(Thunk::new(move|to: &mut TuiOut|{
|
|
||||||
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
|
||||||
to.place(&Push::Y((2 * index) as u16,
|
|
||||||
Tui::fg_bg(
|
|
||||||
if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
|
||||||
if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
|
||||||
Fixed::Y(2, Align::n(Fill::X(item)))
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
_ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"),
|
|
||||||
},
|
|
||||||
Some(":templates") => to.place(&{
|
|
||||||
let modes = self.config.modes.clone();
|
|
||||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
|
||||||
Fixed::Y(height, Min::X(30, Thunk::new(move |to: &mut TuiOut|{
|
|
||||||
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
|
|
||||||
let bg = 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>");
|
|
||||||
let fg1 = Rgb(224, 192, 128);
|
|
||||||
let fg2 = Rgb(224, 128, 32);
|
|
||||||
let field_name = Fill::X(Align::w(Tui::fg(fg1, name)));
|
|
||||||
let field_id = Fill::X(Align::e(Tui::fg(fg2, id)));
|
|
||||||
let field_info = Fill::X(Align::w(info));
|
|
||||||
to.place(&Push::Y((2 * index) as u16,
|
|
||||||
Fixed::Y(2, Fill::X(Tui::bg(bg, Bsp::s(
|
|
||||||
Bsp::a(field_name, field_id), field_info))))));
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}),
|
|
||||||
Some(":sessions") => to.place(&Fixed::Y(6, Min::X(30, Thunk::new(|to: &mut TuiOut|{
|
|
||||||
let fg = Rgb(224, 192, 128);
|
|
||||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
|
||||||
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
|
||||||
to.place(&Push::Y((2 * index) as u16,
|
|
||||||
&Fixed::Y(2, Fill::X(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))));
|
|
||||||
}
|
|
||||||
})))),
|
|
||||||
Some(":browse/title") => to.place(&Fill::X(Align::w(FieldV(ItemColor::default(),
|
|
||||||
match self.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:",
|
|
||||||
}, Shrink::X(3, Fixed::Y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
|
||||||
Some(":device") => {
|
|
||||||
let selected = self.dialog.device_kind().unwrap();
|
|
||||||
to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
|
|
||||||
move||device_kinds().iter(),
|
|
||||||
move|_label: &&'static str, i|{
|
|
||||||
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
|
||||||
let lb = if i == selected { "[ " } else { " " };
|
|
||||||
let rb = if i == selected { " ]" } else { " " };
|
|
||||||
Fill::X(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) })))
|
|
||||||
},
|
|
||||||
Some(":debug") => to.place(&Fixed::Y(1, format!("[{:?}]", to.area()))),
|
|
||||||
Some(_) => {
|
|
||||||
let views = self.config.views.read().unwrap();
|
|
||||||
if let Some(dsl) = views.get(dsl.src()?.unwrap()) {
|
|
||||||
let dsl = dsl.clone();
|
|
||||||
std::mem::drop(views);
|
|
||||||
self.view(to, &dsl)?
|
|
||||||
} else {
|
|
||||||
unimplemented!("{dsl:?}");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view_logo () -> impl Content<TuiOut> {
|
|
||||||
Fixed::XY(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{
|
|
||||||
Fixed::Y(1, ""),
|
|
||||||
Fixed::Y(1, ""),
|
|
||||||
Fixed::Y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
|
|
||||||
Fixed::Y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
|
|
||||||
Fixed::Y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
//pub fn view_nil (_: &App) -> TuiCb {
|
|
||||||
//|to|to.place(&Fill::XY("·"))
|
|
||||||
//}
|
|
||||||
|
|
||||||
//Bsp::s("",
|
|
||||||
//Map::south(1,
|
|
||||||
//move||app.config.binds.layers.iter()
|
|
||||||
//.filter_map(|a|(a.0)(app).then_some(a.1))
|
|
||||||
//.flat_map(|a|a)
|
|
||||||
//.filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None })
|
|
||||||
//.skip(offset)
|
|
||||||
//.take(20),
|
|
||||||
//|mut b,i|Fixed::X(60, Align::w(Bsp::e("(", Bsp::e(
|
|
||||||
//b.next().map(|t|Fixed::X(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))),
|
|
||||||
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
|
|
||||||
|
|
||||||
//Dialog::Browse(BrowseTarget::Load, browser) => {
|
|
||||||
//"bobcat".boxed()
|
|
||||||
////Bsp::s(
|
|
||||||
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
|
||||||
////Tui::bold(true, " Load project: "),
|
|
||||||
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
|
||||||
////Outer(true, Style::default().fg(Tui::g(96)))
|
|
||||||
////.enclose(Fill::XY(browser)))
|
|
||||||
//},
|
|
||||||
//Dialog::Browse(BrowseTarget::Export, browser) => {
|
|
||||||
//"bobcat".boxed()
|
|
||||||
////Bsp::s(
|
|
||||||
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
|
||||||
////Tui::bold(true, " Export: "),
|
|
||||||
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
|
||||||
////Outer(true, Style::default().fg(Tui::g(96)))
|
|
||||||
////.enclose(Fill::XY(browser)))
|
|
||||||
//},
|
|
||||||
//Dialog::Browse(BrowseTarget::Import, browser) => {
|
|
||||||
//"bobcat".boxed()
|
|
||||||
////Bsp::s(
|
|
||||||
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
|
||||||
////Tui::bold(true, " Import: "),
|
|
||||||
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
|
||||||
////Outer(true, Style::default().fg(Tui::g(96)))
|
|
||||||
////.enclose(Fill::XY(browser)))
|
|
||||||
//},
|
|
||||||
//
|
|
||||||
//pub fn view_history (&self) -> impl Content<TuiOut> {
|
|
||||||
//Fixed::Y(1, Fill::X(Align::w(FieldH(self.color,
|
|
||||||
//format!("History ({})", self.history.len()),
|
|
||||||
//self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))))
|
|
||||||
//}
|
|
||||||
//pub fn view_status_h2 (&self) -> impl Content<TuiOut> {
|
|
||||||
//self.update_clock();
|
|
||||||
//let theme = self.color;
|
|
||||||
//let clock = self.clock();
|
|
||||||
//let playing = clock.is_rolling();
|
|
||||||
//let cache = clock.view_cache.clone();
|
|
||||||
////let selection = self.selection().describe(self.tracks(), self.scenes());
|
|
||||||
//let hist_len = self.history.len();
|
|
||||||
//let hist_last = self.history.last();
|
|
||||||
//Fixed::Y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Draw<TuiOut>)|{
|
|
||||||
//add(&Fixed::X(5, Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
|
||||||
//Either::new(false, // TODO
|
|
||||||
//Thunk::new(move||Fixed::X(9, Either::new(playing,
|
|
||||||
//Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
|
||||||
//Tui::fg(Rgb(255, 128, 0), " STOPPED ")))
|
|
||||||
//),
|
|
||||||
//Thunk::new(move||Fixed::X(5, Either::new(playing,
|
|
||||||
//Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
|
||||||
//Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))
|
|
||||||
//)
|
|
||||||
//)
|
|
||||||
//)));
|
|
||||||
//add(&" ");
|
|
||||||
//{
|
|
||||||
//let cache = cache.read().unwrap();
|
|
||||||
//add(&Fixed::X(15, Align::w(Bsp::s(
|
|
||||||
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
|
||||||
//FieldH(theme, "Time", cache.time.view.clone()),
|
|
||||||
//))));
|
|
||||||
//add(&Fixed::X(13, Align::w(Bsp::s(
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
|
||||||
//))));
|
|
||||||
//add(&Fixed::X(12, Align::w(Bsp::s(
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
|
||||||
//))));
|
|
||||||
////add(&Bsp::s(
|
|
||||||
//////Fill::X(Align::w(FieldH(theme, "Selected", Align::w(selection)))),
|
|
||||||
////Fill::X(Align::w(FieldH(theme, format!("History ({})", hist_len),
|
|
||||||
////hist_last.map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))),
|
|
||||||
////""
|
|
||||||
////));
|
|
||||||
//////if let Some(last) = self.history.last() {
|
|
||||||
//////add(&FieldV(theme, format!("History ({})", self.history.len()),
|
|
||||||
//////Fill::X(Align::w(format!("{:?}", last.0)))));
|
|
||||||
//////}
|
|
||||||
//}
|
|
||||||
//}))
|
|
||||||
//}
|
|
||||||
//pub fn view_status_v (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.update_clock();
|
|
||||||
//let cache = self.project.clock.view_cache.read().unwrap();
|
|
||||||
//let theme = self.color;
|
|
||||||
//let playing = self.clock().is_rolling();
|
|
||||||
//Tui::bg(theme.darker.rgb, Fixed::XY(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
//col!(
|
|
||||||
//Fill::X(Align::w(Bsp::e(
|
|
||||||
//Align::w(Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
|
||||||
//Either::new(false, // TODO
|
|
||||||
//Thunk::new(move||Fixed::X(9, Either::new(playing,
|
|
||||||
//Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
|
||||||
//Tui::fg(Rgb(255, 128, 0), " STOPPED ")))
|
|
||||||
//),
|
|
||||||
//Thunk::new(move||Fixed::X(5, Either::new(playing,
|
|
||||||
//Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
|
||||||
//Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))
|
|
||||||
//)
|
|
||||||
//)
|
|
||||||
//)),
|
|
||||||
//Bsp::s(
|
|
||||||
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
|
||||||
//FieldH(theme, "Time", cache.time.view.clone()),
|
|
||||||
//),
|
|
||||||
//))),
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
|
||||||
//Fill::X(Align::w(FieldH(theme, "Buf", Bsp::e(cache.buf.view.clone(), Bsp::e(" = ", cache.lat.view.clone()))))),
|
|
||||||
//))))
|
|
||||||
//}
|
|
||||||
//pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.update_clock();
|
|
||||||
//let cache = self.project.clock.view_cache.read().unwrap();
|
|
||||||
//view_status(Some(self.project.selection.describe(self.tracks(), self.scenes())),
|
|
||||||
//cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone())
|
|
||||||
//}
|
|
||||||
//pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.update_clock();
|
|
||||||
//let cache = self.project.clock.view_cache.read().unwrap();
|
|
||||||
//view_transport(self.project.clock.is_rolling(),
|
|
||||||
//cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone())
|
|
||||||
//}
|
|
||||||
//pub fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//let bg = self.editor()
|
|
||||||
//.and_then(|editor|editor.clip().clone())
|
|
||||||
//.map(|clip|clip.read().unwrap().color.darker)
|
|
||||||
//.unwrap_or(self.color.darker);
|
|
||||||
//Fill::XY(Tui::bg(bg.rgb, self.editor()))
|
|
||||||
//}
|
|
||||||
//pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.editor().map(|e|Fixed::X(20, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
//Fill::Y(Align::n(Bsp::s(e.clip_status(), e.edit_status()))))))
|
|
||||||
//}
|
|
||||||
//pub fn view_midi_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_midi_ins_status(self.color)
|
|
||||||
//}
|
|
||||||
//pub fn view_midi_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_midi_outs_status(self.color)
|
|
||||||
//}
|
|
||||||
//pub fn view_audio_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_audio_ins_status(self.color)
|
|
||||||
//}
|
|
||||||
//pub fn view_audio_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_audio_outs_status(self.color)
|
|
||||||
//}
|
|
||||||
//pub fn view_scenes (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//Bsp::e(
|
|
||||||
//Fixed::X(20, Align::nw(self.project.view_scenes_names())),
|
|
||||||
//self.project.view_scenes_clips(),
|
|
||||||
//)
|
|
||||||
//}
|
|
||||||
//pub fn view_scenes_names (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_scenes_names()
|
|
||||||
//}
|
|
||||||
//pub fn view_scenes_clips (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.view_scenes_clips()
|
|
||||||
//}
|
|
||||||
//pub fn view_tracks_inputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
//Fixed::Y(1 + self.project.midi_ins.len() as u16,
|
|
||||||
//self.project.view_inputs(self.color))
|
|
||||||
//}
|
|
||||||
//pub fn view_tracks_outputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
//self.project.view_outputs(self.color)
|
|
||||||
//}
|
|
||||||
//pub fn view_tracks_devices <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
//Fixed::Y(4, self.project.view_track_devices(self.color))
|
|
||||||
//}
|
|
||||||
//pub fn view_tracks_names <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
//Fixed::Y(2, self.project.view_track_names(self.color))
|
|
||||||
//}
|
|
||||||
//pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//Fixed::X(20, Bsp::s(
|
|
||||||
//Fill::X(Align::w(FieldH(self.color, "Clip pool:", ""))),
|
|
||||||
//Fill::Y(Align::n(Tui::bg(Rgb(0, 0, 0), Outer(true, Style::default().fg(Tui::g(96)))
|
|
||||||
//.enclose(PoolView(&self.pool)))))))
|
|
||||||
//}
|
|
||||||
//pub fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.sampler().map(|s|s.view_list(true, self.editor().unwrap()))
|
|
||||||
//}
|
|
||||||
//pub fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.sampler().map(|s|s.view_grid())
|
|
||||||
//}
|
|
||||||
//pub fn view_sample_viewer (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.sampler().map(|s|s.view_sample(self.editor().unwrap().get_note_pos()))
|
|
||||||
//}
|
|
||||||
//pub fn view_sample_info (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos()))
|
|
||||||
//}
|
|
||||||
//pub fn view_sample_status (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
//self.project.sampler().map(|s|Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
//Fill::Y(Align::n(s.view_sample_status(self.editor().unwrap().get_note_pos())))))
|
|
||||||
//}
|
|
||||||
////let options = ||["Projects", "Settings", "Help", "Quit"].iter();
|
|
||||||
////let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
|
|
||||||
////Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
|
|
||||||
1
deps/dizzle
vendored
Submodule
1
deps/dizzle
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1ce18223c60eac427da617d948ad18808d2f5b38
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8c54510f630e8a81b7d7bdca0a51a69cdb9dffcc
|
Subproject commit a933cbe2857613a439761e38a9634cf85dc17461
|
||||||
|
|
@ -74,6 +74,25 @@ impl Arrangement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
|
/// Create a new arrangement.
|
||||||
|
pub fn new (
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
name: Option<Arc<str>>,
|
||||||
|
clock: Clock,
|
||||||
|
tracks: Vec<Track>,
|
||||||
|
scenes: Vec<Scene>,
|
||||||
|
midi_ins: Vec<MidiInput>,
|
||||||
|
midi_outs: Vec<MidiOutput>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
clock, tracks, scenes, midi_ins, midi_outs,
|
||||||
|
jack: jack.clone(),
|
||||||
|
name: name.unwrap_or_default(),
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Width of display
|
/// Width of display
|
||||||
pub fn w (&self) -> u16 {
|
pub fn w (&self) -> u16 {
|
||||||
self.size.w() as u16
|
self.size.w() as u16
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
name = "tek";
|
name = "tek";
|
||||||
stdenv = pkgs.clang19Stdenv;
|
stdenv = pkgs.clang19Stdenv;
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
|
pkgs.bacon
|
||||||
pkgs.pkg-config
|
pkgs.pkg-config
|
||||||
pkgs.freetype
|
pkgs.freetype
|
||||||
pkgs.libclang
|
pkgs.libclang
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue