mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
parent
6a675ec964
commit
4aefaf452c
8 changed files with 260 additions and 246 deletions
242
src/tek.rs
242
src/tek.rs
|
|
@ -243,10 +243,7 @@ pub use self::config::*;
|
|||
mod config {
|
||||
use crate::*;
|
||||
use std::path::PathBuf;
|
||||
use notify_debouncer_full::{
|
||||
new_debouncer_opt, Debouncer, DebounceEventResult, NoCache,
|
||||
notify::{RecursiveMode, RecommendedWatcher, PollWatcher, Config as NotifyConfig, Watcher}
|
||||
};
|
||||
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
|
||||
|
||||
/// Configuration: mode, view, and bind definitions.
|
||||
///
|
||||
|
|
@ -388,11 +385,11 @@ mod config {
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn watch (config: Arc<Self>, bounce: Option<Duration>) -> Usually<()> {
|
||||
pub fn watch (config: Arc<Self>, poll: Option<Duration>) -> Usually<()> {
|
||||
let handler = {
|
||||
let config = config.clone();
|
||||
move |result|match result {
|
||||
Ok(events) => if let Err(e) = config.init() {
|
||||
Ok(_events) => if let Err(e) = config.init() {
|
||||
*config.error.write().unwrap() = Some(format!("{e:?}").into());
|
||||
panic!("{e:?}");
|
||||
} else {
|
||||
|
|
@ -405,9 +402,10 @@ mod config {
|
|||
};
|
||||
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new(
|
||||
handler,
|
||||
NotifyConfig::default().with_poll_interval(Duration::from_millis(250))
|
||||
NotifyConfig::default()
|
||||
.with_poll_interval(poll.unwrap_or(Duration::from_millis(250)))
|
||||
)?;
|
||||
if let Some(mut path) = config.get_file() {
|
||||
if let Some(path) = config.get_file() {
|
||||
//println!("watching: {path:?}");
|
||||
watcher.watch(&path, RecursiveMode::NonRecursive)?;
|
||||
*config.watch.write().unwrap() = Some(watcher);
|
||||
|
|
@ -688,6 +686,7 @@ mod app {
|
|||
#[namespace(Option<u16> App::get_opt_u16)]
|
||||
#[namespace(Option<usize> App::get_opt_usize)]
|
||||
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
||||
#[namespace(Dialog App::get_dialog)]
|
||||
pub struct App {
|
||||
/// Exit flag
|
||||
pub exit: Exit,
|
||||
|
|
@ -857,55 +856,6 @@ mod app {
|
|||
})).transpose()
|
||||
}
|
||||
|
||||
pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => todo!(),
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => None,
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
||||
Ok(match &self.dialog {
|
||||
Dialog::Menu(index, items) => {
|
||||
let callback = items.0[*index].1.clone();
|
||||
callback(self)?;
|
||||
None
|
||||
},
|
||||
_ => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Update memoized render of clock values.
|
||||
/// ```
|
||||
/// tek::App::default().update_clock();
|
||||
/// ```
|
||||
pub fn update_clock (&self) {
|
||||
ClockView::update_clock(
|
||||
&self.project.clock.view_cache, self.clock(), self.size.w() > 80
|
||||
)
|
||||
}
|
||||
|
||||
/// Set modal dialog.
|
||||
///
|
||||
/// ```
|
||||
/// let previous: tek::Dialog = tek::App::default().set_dialog(tek::Dialog::welcome());
|
||||
/// ```
|
||||
pub fn set_dialog (&mut self, mut dialog: Dialog) -> Dialog {
|
||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||
dialog
|
||||
}
|
||||
|
||||
/// FIXME: generalize. Set picked device in device pick dialog.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -954,59 +904,6 @@ mod app {
|
|||
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||
}
|
||||
|
||||
/// Is a MIDI editor currently focused?
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().editor_focused();
|
||||
/// ```
|
||||
pub fn editor_focused (&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Toggle MIDI editor.
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().toggle_editor(None);
|
||||
/// ```
|
||||
pub fn toggle_editor (&mut self, value: Option<bool>) {
|
||||
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
|
||||
let value = value.unwrap_or_else(||!self.editor().is_some());
|
||||
if value {
|
||||
// Create new clip in pool when entering empty cell
|
||||
if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& slot.is_none()
|
||||
&& let Some(track) = self.project.tracks.get_mut(track)
|
||||
{
|
||||
let (_index, clip) = self.pool.add_new_clip();
|
||||
// autocolor: new clip colors from scene and track color
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||
if let Some(editor) = &mut self.project.editor {
|
||||
editor.set_clip(Some(&clip));
|
||||
}
|
||||
*slot = Some(clip.clone());
|
||||
//Some(clip)
|
||||
} else {
|
||||
//None
|
||||
}
|
||||
} else if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& let Some(clip) = slot.as_mut()
|
||||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
let mut swapped = None;
|
||||
if clip.read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
self.pool.delete_clip(&clip.read().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||
|
|
@ -1369,7 +1266,6 @@ mod device {
|
|||
pub mod clock; pub use self::clock::*;
|
||||
pub mod dialog; pub use self::dialog::*;
|
||||
pub mod editor; pub use self::editor::*;
|
||||
pub mod menu; pub use self::menu::*;
|
||||
pub mod meter; pub use self::meter::*;
|
||||
pub mod mix; pub use self::mix::*;
|
||||
pub mod sampler; pub use self::sampler::*;
|
||||
|
|
@ -1510,53 +1406,55 @@ mod draw {
|
|||
/// Then, every top-level form of the DSL description is rendered.
|
||||
impl View<Tui> for App {
|
||||
fn view (&self) -> impl Draw<Tui> {
|
||||
draw(|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
self.perf.cycle(&mut |_|{
|
||||
draw(|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
|
||||
self.mode
|
||||
.as_ref()
|
||||
.and_then(|m|self.config.get_mode(m))
|
||||
.map(|mode|{
|
||||
self.mode
|
||||
.as_ref()
|
||||
.and_then(|m|self.config.get_mode(m))
|
||||
.map(|mode|{
|
||||
|
||||
//south!(
|
||||
//format!("Mode: {:?}", self.mode.as_ref()),
|
||||
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
||||
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
||||
//format!("View #{index} {line}")
|
||||
//}))
|
||||
//).align_nw().draw(to)?;
|
||||
//south!(
|
||||
//format!("Mode: {:?}", self.mode.as_ref()),
|
||||
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
||||
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
||||
//format!("View #{index} {line}")
|
||||
//}))
|
||||
//).align_nw().draw(to)?;
|
||||
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
match self.interpret(to, dsl) {
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
return Err(e);
|
||||
},
|
||||
_ => {}
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
match self.interpret(to, dsl) {
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
return Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*self.error.write().unwrap() = None;
|
||||
*self.error.write().unwrap() = None;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
});
|
||||
|
||||
east(
|
||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||
format!("{} ", self.perf.clock.raw() / 1000000000),
|
||||
).align_se().draw(to)?;
|
||||
east(
|
||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000),
|
||||
).align_se().draw(to)?;
|
||||
|
||||
Ok(Some(xywh))
|
||||
Ok(Some(xywh))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1577,7 +1475,7 @@ mod draw {
|
|||
Some(":sessions") => view_sessions().draw(to),
|
||||
Some(":browse/title") => view_browse_title(self).draw(to),
|
||||
Some(":device") => view_device(self).draw(to),
|
||||
Some(":status") => "TODO: Status Bar".exact_h(1).draw(to),
|
||||
Some(":status") => "TODO: Status Bar".draw(to),
|
||||
Some(":editor") => "TODO Editor".draw(to),
|
||||
Some(":transport") => view_transport(true, "", "", "").draw(to),
|
||||
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to),
|
||||
|
|
@ -1633,32 +1531,6 @@ mod draw {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn draw_dialog (
|
||||
to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &impl Expression) -> Drawn<u16> {
|
||||
match frags.next() {
|
||||
Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog {
|
||||
//Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{
|
||||
//let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
|
||||
//let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
|
||||
//fg_bg(f, b, item.full_w().align_x().exact_h(2)).push_y(index as u16 * 4)
|
||||
//})).full_wh())
|
||||
let items = items.clone();
|
||||
let selected = selected;
|
||||
Some(draw(move|to: &mut Tui|{
|
||||
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
||||
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
|
||||
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
|
||||
fg_bg(f, b, item.exact_h(2).full_w().align_x()).push_y(1 + (2 * index) as u16).draw(to)?;
|
||||
}
|
||||
Ok(Some(to.area().into()))
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}.draw(to),
|
||||
_ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_templates (to: &mut Tui, _frags: std::str::Split<&str>, state: &App) -> Drawn<u16> {
|
||||
let height = (state.config.modes.len() * 2) as u16;
|
||||
draw(move |to: &mut Tui|{
|
||||
|
|
@ -1727,16 +1599,14 @@ mod draw {
|
|||
}
|
||||
|
||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||
field_v(ItemTheme::default(),
|
||||
match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)
|
||||
).align_w().full_w()
|
||||
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
||||
}
|
||||
|
||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue