mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
This commit is contained in:
parent
186b81f29c
commit
ca774cca41
21 changed files with 2160 additions and 2191 deletions
391
src/tek.rs
391
src/tek.rs
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
mod deps; pub use self::deps::*;
|
||||
|
||||
mod config; pub use self::config::*;
|
||||
mod config_bind; pub use self::config_bind::*;
|
||||
mod config_mode; pub use self::config_mode::*;
|
||||
mod config_view; pub use self::config_view::*;
|
||||
|
||||
mod midi_octave; pub use self::midi_octave::*;
|
||||
mod midi_piano; pub use self::midi_piano::*;
|
||||
|
|
@ -45,12 +41,13 @@ mod proj_port; pub use self::proj_port::*;
|
|||
mod proj_scene; pub use self::proj_scene::*;
|
||||
mod proj_select; pub use self::proj_select::*;
|
||||
mod proj_track; pub use self::proj_track::*;
|
||||
mod proj_pool; pub use self::proj_pool::*;
|
||||
|
||||
mod tek_browse; pub use self::tek_browse::*;
|
||||
mod tek_cli; pub use self::tek_cli::*;
|
||||
mod tek_ctrl; pub use self::tek_ctrl::*;
|
||||
mod tek_view; pub use self::tek_view::*;
|
||||
mod tek_bind; pub use self::tek_bind::*;
|
||||
mod tek_cli; pub use self::tek_cli::*;
|
||||
mod tek_config; pub use self::tek_config::*;
|
||||
mod tek_ctrl; pub use self::tek_ctrl::*;
|
||||
mod tek_mode; pub use self::tek_mode::*;
|
||||
mod tek_view; pub use self::tek_view::*;
|
||||
|
||||
#[cfg(feature = "lv2")] mod device_lv2; #[cfg(feature = "lv2")] pub use self::device_lv2::*;
|
||||
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*;
|
||||
|
|
@ -126,16 +123,18 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
|||
#[namespace(Arc<[Connect]> get_arc_array_connect)]
|
||||
#[namespace(Arc<str> get_arc_str)]
|
||||
#[namespace(Color get_color)]
|
||||
#[namespace(ControlAxis get_axis)]
|
||||
#[namespace(Dialog get_dialog)]
|
||||
#[namespace(ControlAxis ControlAxis::get_axis)]
|
||||
#[namespace(Dialog Dialog::get_dialog)]
|
||||
#[namespace(ItemColor)]
|
||||
#[namespace(ItemTheme)]
|
||||
#[namespace(MidiClip)]
|
||||
#[namespace(ClipSlot ClipSlot::eval)]
|
||||
#[namespace(ClipPool ClipPool::eval)]
|
||||
#[namespace(SampleSlot SampleSlot::eval)]
|
||||
#[namespace(SamplePool SamplePool::eval)]
|
||||
#[namespace(Option<Arc<[Connect]>> get_opt_arc_array_connect)]
|
||||
#[namespace(Option<Arc<str>> get_opt_arc_str)]
|
||||
#[namespace(Option<ItemTheme> get_opt_itemtheme)]
|
||||
#[namespace(Option<Arc<RwLock<MidiClip>>> get_opt_arc_rwlock_midiclip)]
|
||||
#[namespace(Option<Vec<Option<Arc<RwLock<MidiClip>>>>> get_opt_vec_opt_arc_rwlock_midiclip)]
|
||||
#[namespace(Option<bool> get_opt_bool)]
|
||||
#[namespace(Option<u16> get_opt_u16)]
|
||||
#[namespace(Option<u32> get_opt_u32)]
|
||||
|
|
@ -242,14 +241,14 @@ fn get_bool <'a> (state: &App, src: impl Language) -> Perhaps<bool> {
|
|||
":focused/message" => matches!(state.dialog, Dialog::Message(..)),
|
||||
":focused/add_device" => matches!(state.dialog, Dialog::Device(..)),
|
||||
":focused/browser" => state.dialog.browser().is_some(),
|
||||
":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))),
|
||||
":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))),
|
||||
":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))),
|
||||
":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))),
|
||||
":focused/clip" => !state.editor_focused() && matches!(state.selection(), Selection::TrackClip{..}),
|
||||
":focused/track" => !state.editor_focused() && matches!(state.selection(), Selection::Track(..)),
|
||||
":focused/scene" => !state.editor_focused() && matches!(state.selection(), Selection::Scene(..)),
|
||||
":focused/mix" => !state.editor_focused() && matches!(state.selection(), Selection::Mix),
|
||||
//":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))),
|
||||
//":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))),
|
||||
//":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))),
|
||||
//":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))),
|
||||
":focused/clip" => matches!(state.selection(), Selection::TrackClip{..}),
|
||||
":focused/track" => matches!(state.selection(), Selection::Track(..)),
|
||||
":focused/scene" => matches!(state.selection(), Selection::Scene(..)),
|
||||
":focused/mix" => matches!(state.selection(), Selection::Mix),
|
||||
_ => return err!("not bool: {word}")
|
||||
})).transpose()
|
||||
}
|
||||
|
|
@ -322,17 +321,6 @@ fn get_opt_usize <'a> (state: &App, src: impl Language) -> Perhaps<Option<usize>
|
|||
}))
|
||||
}
|
||||
|
||||
fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
|
||||
eval_word(src, |word|Ok(match word {
|
||||
|
||||
"X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine,
|
||||
"Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine,
|
||||
"Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine,
|
||||
|
||||
_ => return err!("unknown axis {word}")
|
||||
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_opt_u16 <'a> (_: &App, _src: impl Language) -> Perhaps<Option<u16>> {
|
||||
Ok(None)
|
||||
|
|
@ -370,11 +358,16 @@ fn get_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Vec<Arc<str>>> {
|
|||
todo!()
|
||||
}
|
||||
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Sizer: |self: App|self.size);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Pool: |self: App|self.project.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Pool<MidiClip>: |self: App|self.project.clips);
|
||||
impl_has!(Pool<Sample>: |self: App|self.project.samples);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
|
||||
impl_as_ref!(Slot<MidiClip>: |self: App|todo!());
|
||||
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
|
||||
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
|
||||
|
||||
|
|
@ -383,21 +376,6 @@ primitive!(u16: try_to_u16);
|
|||
primitive!(usize: try_to_usize);
|
||||
primitive!(isize: try_to_isize);
|
||||
|
||||
impl HasClips for App {
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
self.project.clip()
|
||||
}
|
||||
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>> {
|
||||
self.project.clip_mut()
|
||||
}
|
||||
fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips> {
|
||||
self.project.clips()
|
||||
}
|
||||
fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips> {
|
||||
self.project.clips_mut()
|
||||
}
|
||||
}
|
||||
|
||||
tui_keys!(self: App, input {
|
||||
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
|
||||
Self::input_cmd(self, input)
|
||||
|
|
@ -478,86 +456,20 @@ impl App {
|
|||
I, IFine,
|
||||
}
|
||||
|
||||
/// The [Draw] implementation for [App] handles the loaded view,
|
||||
/// which is defined in terms of [dizzle] DSL.
|
||||
///
|
||||
/// If there is an error, the error is displayed. FIXME: overlay it
|
||||
/// Then, every top-level form of the DSL description is rendered.
|
||||
impl Draw<Tui> for App {
|
||||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
#[cfg(feature = "prof2")] profiling::scope!("App::draw");
|
||||
//self.perf.cycle(&mut |_|{
|
||||
self.draw_error(to)?;
|
||||
self.draw_modes(to)?;
|
||||
self.draw_debug(to)?;
|
||||
#[cfg(feature = "prof2")] profiling::finish_frame!();
|
||||
Ok(Some(to.area().into()))
|
||||
//})
|
||||
impl ControlAxis {
|
||||
fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
|
||||
eval_word(src, |word|Ok(match word {
|
||||
|
||||
"X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine,
|
||||
"Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine,
|
||||
"Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine,
|
||||
|
||||
_ => return err!("unknown axis {word}")
|
||||
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
|
||||
if let Some(e) = self.error.try_read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
|
||||
for name in self.modes.read().iter() {
|
||||
let mut error = false;
|
||||
if let Some(mode) = self.config.get_mode(name).as_ref().map(Arc::clone) {
|
||||
for (index, view) in mode.view.iter().enumerate() {
|
||||
match (view.render)(self, to) {
|
||||
Ok(None) => {},
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let message = format!(
|
||||
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
|
||||
&mode.name,
|
||||
&view.source
|
||||
);
|
||||
*self.error.try_write().unwrap() = Some(message.into());
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if error {
|
||||
break
|
||||
} else {
|
||||
*self.error.try_write().unwrap() = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
Split::Above.bar(0,
|
||||
Split::West.bar(16,
|
||||
format!("{}/{} {} ",
|
||||
self.perf.used.load(Relaxed),
|
||||
self.perf.window.load(Relaxed),
|
||||
self.perf.clock.raw() / 10000000).align_e(),
|
||||
format!("{}x{} ",
|
||||
self.size.0.load(Relaxed),
|
||||
self.size.1.load(Relaxed)).align_w(),
|
||||
).align_se(),
|
||||
Split::East.bar(16,
|
||||
format!("{}", self.modes.read()[0]),
|
||||
format!("{}", self.modes.read()[0])
|
||||
).align_sw()
|
||||
).draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl_has!(Sizer: |self: App|self.size);
|
||||
|
||||
pub trait HasWidth {
|
||||
const MIN_WIDTH: usize;
|
||||
/// Increment track width.
|
||||
|
|
@ -566,46 +478,6 @@ pub trait HasWidth {
|
|||
fn width_dec (&mut self);
|
||||
}
|
||||
|
||||
pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
||||
|
||||
eval_word(&src, |word|Ok(match word {
|
||||
|
||||
":none" => Dialog::None,
|
||||
|
||||
":options" => Dialog::Options,
|
||||
|
||||
":device" => Dialog::Device(0),
|
||||
":device/prev" => Dialog::Device(0),
|
||||
":device/next" => Dialog::Device(0),
|
||||
|
||||
":help" => Dialog::Help(0),
|
||||
":save" => Dialog::Browse(
|
||||
BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":load" => Dialog::Browse(
|
||||
BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/clip" => Dialog::Browse(
|
||||
BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/clip" => Dialog::Browse(
|
||||
BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/sample" => Dialog::Browse(
|
||||
BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/sample" => Dialog::Browse(
|
||||
BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
_ => return Err(format!("not a dialog: {}", src.src()?.unwrap_or("<???>")).into())
|
||||
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
/// Various possible dialog modes.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -623,6 +495,46 @@ pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
|||
|
||||
impl Dialog {
|
||||
|
||||
fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
||||
|
||||
eval_word(&src, |word|Ok(match word {
|
||||
|
||||
":none" => Dialog::None,
|
||||
|
||||
":options" => Dialog::Options,
|
||||
|
||||
":device" => Dialog::Device(0),
|
||||
":device/prev" => Dialog::Device(0),
|
||||
":device/next" => Dialog::Device(0),
|
||||
|
||||
":help" => Dialog::Help(0),
|
||||
":save" => Dialog::Browse(
|
||||
BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":load" => Dialog::Browse(
|
||||
BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/clip" => Dialog::Browse(
|
||||
BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/clip" => Dialog::Browse(
|
||||
BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/sample" => Dialog::Browse(
|
||||
BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/sample" => Dialog::Browse(
|
||||
BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
_ => return Err(format!("not a dialog: {}", src.src()?.unwrap_or("<???>")).into())
|
||||
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::Dialog::welcome();
|
||||
/// ```
|
||||
|
|
@ -758,29 +670,142 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
|||
/// ```
|
||||
/// let item: tek::MenuItem = Default::default();
|
||||
/// ```
|
||||
#[derive(Clone)] pub struct MenuItem(
|
||||
pub Arc<str>,
|
||||
pub Arc<[AppCmd]>,
|
||||
);
|
||||
|
||||
#[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>);
|
||||
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||
impl_default!(MenuItem: Self::new("", []));
|
||||
impl PartialEq for MenuItem { fn eq (&self, other: &Self) -> bool { self.0 == other.0 } }
|
||||
impl MenuItem {
|
||||
pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCmd; N]) -> Self {
|
||||
Self(name.as_ref().into(), Arc::new(actions))
|
||||
}
|
||||
}
|
||||
|
||||
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||
|
||||
impl_default!(MenuItem: Self::new("", []));
|
||||
|
||||
impl PartialEq for MenuItem {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
//impl AsRef<Arc<[MenuItem]>> for MenuItems {
|
||||
//fn as_ref (&self) -> &Arc<[MenuItem]> {
|
||||
//&self.0
|
||||
//}
|
||||
//}
|
||||
|
||||
/// Browses for files to load/save.
|
||||
///
|
||||
/// ```
|
||||
/// let browse = tek::Browse::default();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default, PartialEq)] pub struct Browse {
|
||||
pub cwd: PathBuf,
|
||||
pub dirs: Vec<(OsString, String)>,
|
||||
pub files: Vec<(OsString, String)>,
|
||||
pub filter: String,
|
||||
pub index: usize,
|
||||
pub scroll: usize,
|
||||
pub size: Sizer,
|
||||
}
|
||||
|
||||
//pub(crate) struct EntriesIterator<'a, S: Screen> {
|
||||
//pub browser: &'a Browse,
|
||||
//pub offset: usize,
|
||||
//pub length: usize,
|
||||
//pub index: usize,
|
||||
//_screen: std::marker::PhantomData<S>
|
||||
//}
|
||||
|
||||
#[derive(Clone, Debug)] pub enum BrowseTarget {
|
||||
SaveProject,
|
||||
LoadProject,
|
||||
ImportSample(Arc<RwLock<Option<Sample>>>),
|
||||
ExportSample(Arc<RwLock<Option<Sample>>>),
|
||||
ImportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||
}
|
||||
|
||||
// Commands supported by [Browse]
|
||||
//#[derive(Debug, Clone, PartialEq)]
|
||||
//pub enum BrowseCommand {
|
||||
//Begin,
|
||||
//Cancel,
|
||||
//Confirm,
|
||||
//Select(usize),
|
||||
//Chdir(PathBuf),
|
||||
//Filter(Arc<str>),
|
||||
//}
|
||||
impl Browse {
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||
let mut dirs = vec![];
|
||||
let mut files = vec![];
|
||||
for entry in std::fs::read_dir(&cwd)? {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name();
|
||||
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
|
||||
let meta = entry.metadata()?;
|
||||
if meta.is_dir() {
|
||||
dirs.push((name, format!("📁 {decoded}")));
|
||||
} else if meta.is_file() {
|
||||
files.push((name, format!("📄 {decoded}")));
|
||||
}
|
||||
}
|
||||
Ok(Self { cwd, dirs, files, ..Default::default() })
|
||||
}
|
||||
pub fn to_chdir (&self) -> Usually<Self> { Self::new(Some(self.path())) }
|
||||
pub fn len (&self) -> usize { self.dirs.len() + self.files.len() }
|
||||
pub fn is_dir (&self) -> bool { self.index < self.dirs.len() }
|
||||
pub fn is_file (&self) -> bool { self.index >= self.dirs.len() }
|
||||
pub fn path (&self) -> PathBuf {
|
||||
self.cwd.join(if self.is_dir() {
|
||||
&self.dirs[self.index].0
|
||||
} else if self.is_file() {
|
||||
&self.files[self.index - self.dirs.len()].0
|
||||
} else {
|
||||
unreachable!()
|
||||
})
|
||||
}
|
||||
//fn tui_entries (&self) -> EntriesIterator<'_, Tui> {
|
||||
//EntriesIterator {
|
||||
//offset: 0,
|
||||
//index: 0,
|
||||
//length: self.dirs.len() + self.files.len(),
|
||||
//browser: self,
|
||||
//_screen: Default::default(),
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
||||
impl PartialEq for BrowseTarget {
|
||||
fn eq (&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::ImportSample(_) => false,
|
||||
Self::ExportSample(_) => false,
|
||||
Self::ImportClip(_) => false,
|
||||
Self::ExportClip(_) => false,
|
||||
#[allow(unused)] t => matches!(other, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
||||
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
|
||||
let entry = entry.expect("failed to read drectory entry");
|
||||
let meta = entry.metadata().expect("failed to read entry metadata");
|
||||
if meta.is_file() {
|
||||
files.push(entry.file_name());
|
||||
} else if meta.is_dir() {
|
||||
subdirs.push(entry.file_name());
|
||||
}
|
||||
(subdirs, files)
|
||||
});
|
||||
subdirs.sort();
|
||||
files.sort();
|
||||
Ok((subdirs, files))
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Return reference to content browser if open.
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(tek::App::default().browser(), None);
|
||||
/// ```
|
||||
pub fn browser (&self) -> Option<&Browse> {
|
||||
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue