fix main menu
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
i do not exist 2026-08-06 18:30:18 +03:00
parent 6a675ec964
commit 4aefaf452c
8 changed files with 260 additions and 246 deletions

View file

@ -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> {