mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
the definitions are unified alright. it's just not supported yet :D the idea being that tek offers to write out the default configs to ~/.config/tek-v0 where the user can customize them.
This commit is contained in:
parent
9e147cda69
commit
2e5462c4e7
42 changed files with 426 additions and 411 deletions
|
|
@ -15,6 +15,7 @@ clap = { workspace = true, optional = true }
|
|||
palette = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
xdg = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
use tek::*;
|
||||
use tengri::input::*;
|
||||
use std::sync::*;
|
||||
struct ExampleClips(Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>);
|
||||
impl HasClips for ExampleClips {
|
||||
fn clips (&self) -> RwLockReadGuard<'_, Vec<Arc<RwLock<MidiClip>>>> {
|
||||
self.0.read().unwrap()
|
||||
}
|
||||
fn clips_mut (&self) -> RwLockWriteGuard<'_, Vec<Arc<RwLock<MidiClip>>>> {
|
||||
self.0.write().unwrap()
|
||||
}
|
||||
}
|
||||
fn main () -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut clips = Pool::default();//ExampleClips(Arc::new(vec![].into()));
|
||||
PoolClipCommand::Import {
|
||||
index: 0,
|
||||
path: std::path::PathBuf::from("./example.mid")
|
||||
}.execute(&mut clips)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@ macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
|||
handle!(TuiIn: |self: App, input|self.handle_tui_key_with_history(input));
|
||||
impl App {
|
||||
fn handle_tui_key_with_history (&mut self, input: &TuiIn) -> Perhaps<bool> {
|
||||
Ok(if let Some(binding) = self.config.keys.dispatch(input.event()) {
|
||||
Ok(if let Some(binding) = self.configs.current.as_ref()
|
||||
.map(|c|c.keys.dispatch(input.event())).flatten()
|
||||
{
|
||||
let binding = binding.clone();
|
||||
let undo = binding.command.clone().execute(self)?;
|
||||
// FIXME failed commands are not persisted in undo history
|
||||
|
|
|
|||
112
crates/app/src/config.rs
Normal file
112
crates/app/src/config.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use crate::*;
|
||||
use xdg::BaseDirectories;
|
||||
|
||||
/// Configurations
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Configurations {
|
||||
pub dirs: BaseDirectories,
|
||||
pub modules: Arc<RwLock<std::collections::BTreeMap<Arc<str>, Arc<str>>>>,
|
||||
pub current: Option<Configuration>
|
||||
}
|
||||
|
||||
/// Configuration
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Configuration {
|
||||
/// Path of configuration entrypoint
|
||||
pub path: std::path::PathBuf,
|
||||
/// Name of configuration
|
||||
pub name: Option<Arc<str>>,
|
||||
/// Description of configuration
|
||||
pub info: Option<Arc<str>>,
|
||||
/// View definition
|
||||
pub view: Arc<str>,
|
||||
// Input keymap
|
||||
pub keys: EventMap<TuiEvent, AppCommand>,
|
||||
}
|
||||
|
||||
macro_rules! dsl_for_each (($dsl:expr => $body:expr)=>{
|
||||
let mut dsl: Arc<str> = $dsl.src().into();
|
||||
let mut head: Option<Arc<str>> = dsl.head()?.map(Into::into);
|
||||
let mut tail: Option<Arc<str>> = dsl.tail()?.map(Into::into);
|
||||
loop {
|
||||
$body;
|
||||
if let Some(next) = tail {
|
||||
head = next.head()?.map(Into::into);
|
||||
tail = next.tail()?.map(Into::into);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
impl Configurations {
|
||||
pub fn init () -> Usually<Self> {
|
||||
let mut directories = BaseDirectories::with_profile("tek", "v0");
|
||||
let mut configurations = Self { dirs: directories, ..Default::default() };
|
||||
configurations.init_templates()?;
|
||||
configurations.init_bindings()?;
|
||||
Ok(configurations)
|
||||
}
|
||||
|
||||
const DEFAULT_TEMPLATES: &'static str = include_str!("../../../config/templates.edn");
|
||||
fn init_templates (&mut self) -> Usually<()> {
|
||||
if let Some(path) = self.dirs.find_config_file("templates.edn") {
|
||||
todo!()
|
||||
} else {
|
||||
let path = self.dirs.place_config_file("templates.edn")?;
|
||||
dsl_for_each!(Self::DEFAULT_TEMPLATES => todo!());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_BINDINGS: &'static str = include_str!("../../../config/bindings.edn");
|
||||
fn init_bindings (&mut self) -> Usually<()> {
|
||||
if let Some(path) = self.dirs.find_config_file("bindings.edn") {
|
||||
todo!()
|
||||
} else {
|
||||
let path = self.dirs.place_config_file("bindings.edn")?;
|
||||
dsl_for_each!(Self::DEFAULT_BINDINGS => todo!());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
fn load_template (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
|
||||
dsl_for_each!(dsl => match () {
|
||||
_ if let Some(exp) = dsl.exp()? => match exp.head()?.key()? {
|
||||
Some("name") => match exp.tail()?.text()? {
|
||||
Some(name) => self.name = Some(name.into()),
|
||||
_ => return Err(format!("missing name definition").into())
|
||||
},
|
||||
Some("info") => match exp.tail()?.text()? {
|
||||
Some(info) => self.info = Some(info.into()),
|
||||
_ => return Err(format!("missing info definition").into())
|
||||
},
|
||||
Some("bind") => match exp.tail()? {
|
||||
Some(keys) => self.keys = EventMap::from_dsl(&mut &keys)?,
|
||||
_ => return Err(format!("missing keys definition").into())
|
||||
},
|
||||
Some("view") => match exp.tail()? {
|
||||
Some(tail) => self.view = tail.src().into(),
|
||||
_ => return Err(format!("missing view definition").into())
|
||||
},
|
||||
dsl => return Err(format!("unexpected: {dsl:?}").into())
|
||||
},
|
||||
|
||||
_ => return Err(format!("unexpected: {dsl:?}").into())
|
||||
});
|
||||
Ok(self)
|
||||
}
|
||||
fn load_binding (&mut self, dsl: impl Dsl) -> Usually<&mut Self> {
|
||||
todo!();
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn unquote (x: &str) -> &str {
|
||||
let mut chars = x.chars();
|
||||
chars.next();
|
||||
//chars.next_back();
|
||||
chars.as_str()
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
|
|||
|
||||
mod api; pub use self::api::*;
|
||||
mod audio; pub use self::audio::*;
|
||||
mod config; pub use self::config::*;
|
||||
mod model; pub use self::model::*;
|
||||
mod view; pub use self::view::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,66 +1,28 @@
|
|||
use crate::*;
|
||||
use std::path::PathBuf;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct App {
|
||||
/// Must not be dropped for the duration of the process
|
||||
pub jack: Jack<'static>,
|
||||
pub jack: Jack<'static>,
|
||||
/// Display size
|
||||
pub size: Measure<TuiOut>,
|
||||
pub size: Measure<TuiOut>,
|
||||
/// Performance counter
|
||||
pub perf: PerfModel,
|
||||
// View and input definition
|
||||
pub config: Configuration,
|
||||
pub perf: PerfModel,
|
||||
/// Available view definitions and input bindings
|
||||
pub configs: Configurations,
|
||||
/// Contains all recently created clips.
|
||||
pub pool: Pool,
|
||||
pub pool: Pool,
|
||||
/// Contains the currently edited musical arrangement
|
||||
pub project: Arrangement,
|
||||
/// Undo history
|
||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||
// Dialog overlay
|
||||
pub dialog: Option<Dialog>,
|
||||
pub dialog: Option<Dialog>,
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
pub color: ItemTheme,
|
||||
}
|
||||
has!(Jack<'static>: |self: App|self.jack);
|
||||
has!(Pool: |self: App|self.pool);
|
||||
has!(Option<Dialog>: |self: App|self.dialog);
|
||||
has!(Clock: |self: App|self.project.clock);
|
||||
has!(Option<MidiEditor>: |self: App|self.project.editor);
|
||||
has!(Selection: |self: App|self.project.selection);
|
||||
has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
has!(Vec<Scene>: |self: App|self.project.scenes);
|
||||
has!(Vec<Track>: |self: App|self.project.tracks);
|
||||
has!(Measure<TuiOut>: |self: App|self.size);
|
||||
maybe_has!(Track: |self: App|
|
||||
{ MaybeHas::<Track>::get(&self.project) };
|
||||
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||
maybe_has!(Scene: |self: App|
|
||||
{ MaybeHas::<Scene>::get(&self.project) };
|
||||
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||
has_clips!(|self: App|self.pool.clips);
|
||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||
//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));
|
||||
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
||||
//has_editor!(|self: App|{
|
||||
//editor = self.editor;
|
||||
//editor_w = {
|
||||
//let size = self.size.w();
|
||||
//let editor = self.editor.as_ref().expect("missing editor");
|
||||
//let time_len = editor.time_len().get();
|
||||
//let time_zoom = editor.time_zoom().get().max(1);
|
||||
//(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
|
||||
//};
|
||||
//editor_h = 15;
|
||||
//is_editing = self.editor.is_some();
|
||||
//});
|
||||
|
||||
impl App {
|
||||
pub fn update_clock (&self) {
|
||||
|
|
@ -190,20 +152,16 @@ impl App {
|
|||
self.browser().is_some()
|
||||
}
|
||||
fn focus_clip (&self) -> bool {
|
||||
!self.focus_editor() && matches!(self.selection(),
|
||||
Selection::TrackClip{..})
|
||||
!self.focus_editor() && matches!(self.selection(), Selection::TrackClip{..})
|
||||
}
|
||||
fn focus_track (&self) -> bool {
|
||||
!self.focus_editor() && matches!(self.selection(),
|
||||
Selection::Track(..))
|
||||
!self.focus_editor() && matches!(self.selection(), Selection::Track(..))
|
||||
}
|
||||
fn focus_scene (&self) -> bool {
|
||||
!self.focus_editor() && matches!(self.selection(),
|
||||
Selection::Scene(..))
|
||||
!self.focus_editor() && matches!(self.selection(), Selection::Scene(..))
|
||||
}
|
||||
fn focus_mix (&self) -> bool {
|
||||
!self.focus_editor() && matches!(self.selection(),
|
||||
Selection::Mix)
|
||||
!self.focus_editor() && matches!(self.selection(), Selection::Mix)
|
||||
}
|
||||
fn focus_pool_import (&self) -> bool {
|
||||
matches!(self.pool.mode, Some(PoolMode::Import(..)))
|
||||
|
|
@ -318,99 +276,43 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configuration
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Configuration {
|
||||
/// Path of configuration entrypoint
|
||||
pub path: PathBuf,
|
||||
/// Name of configuration
|
||||
pub name: Option<Arc<str>>,
|
||||
/// Description of configuration
|
||||
pub info: Option<Arc<str>>,
|
||||
/// View definition
|
||||
pub view: Arc<str>,
|
||||
// Input keymap
|
||||
pub keys: EventMap<TuiEvent, AppCommand>,
|
||||
}
|
||||
has!(Jack<'static>: |self: App|self.jack);
|
||||
has!(Pool: |self: App|self.pool);
|
||||
has!(Option<Dialog>: |self: App|self.dialog);
|
||||
has!(Clock: |self: App|self.project.clock);
|
||||
has!(Option<MidiEditor>: |self: App|self.project.editor);
|
||||
has!(Selection: |self: App|self.project.selection);
|
||||
has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
has!(Vec<Scene>: |self: App|self.project.scenes);
|
||||
has!(Vec<Track>: |self: App|self.project.tracks);
|
||||
has!(Measure<TuiOut>: |self: App|self.size);
|
||||
maybe_has!(Track: |self: App|
|
||||
{ MaybeHas::<Track>::get(&self.project) };
|
||||
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||
maybe_has!(Scene: |self: App|
|
||||
{ MaybeHas::<Scene>::get(&self.project) };
|
||||
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||
has_clips!(|self: App|self.pool.clips);
|
||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||
|
||||
impl Configuration {
|
||||
pub fn from_path (path: &impl AsRef<Path>, _watch: bool) -> Usually<Self> {
|
||||
let mut config = Self { path: path.as_ref().into(), ..Default::default() };
|
||||
let mut dsl = read_and_leak(path.as_ref())?;
|
||||
let mut head: Option<Arc<str>> = dsl.head()?.map(Into::into);
|
||||
let mut tail: Option<Arc<str>> = dsl.tail()?.map(Into::into);
|
||||
loop {
|
||||
if let Some(exp) = head.exp()? {
|
||||
match exp.head()?.key()? {
|
||||
Some("name") => match exp.tail()?.text()? {
|
||||
Some(name) => config.name = Some(name.into()),
|
||||
_ => return Err(format!("missing name definition").into())
|
||||
},
|
||||
Some("info") => match exp.tail()?.text()? {
|
||||
Some(info) => config.info = Some(info.into()),
|
||||
_ => return Err(format!("missing info definition").into())
|
||||
},
|
||||
Some("keys") => match exp.tail()? {
|
||||
Some(keys) => config.keys = EventMap::from_dsl(&mut &keys)?,
|
||||
_ => return Err(format!("missing keys definition").into())
|
||||
},
|
||||
Some("view") => match exp.tail()? {
|
||||
Some(tail) => config.view = tail.src().into(),
|
||||
_ => return Err(format!("missing view definition").into())
|
||||
},
|
||||
Some(k) => return Err(format!("(e3) unexpected key {k:?} in {exp:?}").into()),
|
||||
None => return Err(format!("(e2) unexpected exp {exp:?}").into()),
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
if let Some(next) = tail {
|
||||
head = next.head()?.map(Into::into);
|
||||
tail = next.tail()?.map(Into::into);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_and_leak (path: impl AsRef<Path>) -> Usually<&'static str> {
|
||||
Ok(leak(String::from_utf8(std::fs::read(path.as_ref())?)?))
|
||||
}
|
||||
|
||||
fn leak (x: impl AsRef<str>) -> &'static str {
|
||||
Box::leak(x.as_ref().into())
|
||||
}
|
||||
|
||||
fn unquote (x: &str) -> &str {
|
||||
let mut chars = x.chars();
|
||||
chars.next();
|
||||
//chars.next_back();
|
||||
chars.as_str()
|
||||
}
|
||||
|
||||
macro_rules! default_config { ($path:literal) => { ($path, include_str!($path)) }; }
|
||||
pub const DEFAULT_CONFIGS: &'static [(&'static str, &'static str)] = &[
|
||||
default_config!("../../../config/config_arranger.edn"),
|
||||
default_config!("../../../config/config_groovebox.edn"),
|
||||
default_config!("../../../config/config_sampler.edn"),
|
||||
default_config!("../../../config/config_sequencer.edn"),
|
||||
default_config!("../../../config/config_transport.edn"),
|
||||
|
||||
default_config!("../../../config/keys_arranger.edn"),
|
||||
default_config!("../../../config/keys_clip.edn"),
|
||||
default_config!("../../../config/keys_clock.edn"),
|
||||
default_config!("../../../config/keys_editor.edn"),
|
||||
default_config!("../../../config/keys_global.edn"),
|
||||
default_config!("../../../config/keys_groovebox.edn"),
|
||||
default_config!("../../../config/keys_length.edn"),
|
||||
default_config!("../../../config/keys_mix.edn"),
|
||||
default_config!("../../../config/keys_pool.edn"),
|
||||
default_config!("../../../config/keys_pool_file.edn"),
|
||||
default_config!("../../../config/keys_rename.edn"),
|
||||
default_config!("../../../config/keys_sampler.edn"),
|
||||
default_config!("../../../config/keys_scene.edn"),
|
||||
default_config!("../../../config/keys_sequencer.edn"),
|
||||
default_config!("../../../config/keys_track.edn"),
|
||||
];
|
||||
//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));
|
||||
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
||||
//has_editor!(|self: App|{
|
||||
//editor = self.editor;
|
||||
//editor_w = {
|
||||
//let size = self.size.w();
|
||||
//let editor = self.editor.as_ref().expect("missing editor");
|
||||
//let time_len = editor.time_len().get();
|
||||
//let time_zoom = editor.time_zoom().get().max(1);
|
||||
//(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
|
||||
//};
|
||||
//editor_h = 15;
|
||||
//is_editing = self.editor.is_some();
|
||||
//});
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ impl<T: Content<TuiOut>> Content<TuiOut> for ErrorBoundary<TuiOut, T> {
|
|||
}
|
||||
|
||||
impl App {
|
||||
pub fn view (model: &Self) -> impl Content<TuiOut> + '_ {
|
||||
ErrorBoundary::new(Ok(Some(Tui::bg(Black, model.view_menu()))))
|
||||
pub fn view (&self) -> impl Content<TuiOut> + '_ {
|
||||
ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view_menu()))))
|
||||
//ErrorBoundary::new(Take::take(model, &mut model.config.view.clone()))
|
||||
//ErrorBoundary::new(Give::give(model, &mut model.config.view.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
content!(TuiOut: |self: App| ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view_nil())))));
|
||||
content!(TuiOut: |self: App| ErrorBoundary::new(Ok(Some(Tui::bg(Black, self.view())))));
|
||||
|
||||
#[tengri_proc::view(TuiOut)]
|
||||
impl App {
|
||||
|
|
@ -39,7 +39,7 @@ impl App {
|
|||
}
|
||||
pub fn view_menu (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||
add(&Tui::bold(true, "tek"));
|
||||
add(&Tui::bold(true, "tek 0.3.0-rc0"));
|
||||
add(&"");
|
||||
add(&"+ new session");
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue