mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-07 14:06:57 +02:00
parent
6a675ec964
commit
4aefaf452c
8 changed files with 260 additions and 246 deletions
|
|
@ -4,11 +4,12 @@
|
||||||
stdenv = pkgs.clang19Stdenv;
|
stdenv = pkgs.clang19Stdenv;
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pkgs.bacon
|
pkgs.bacon
|
||||||
pkgs.pkg-config
|
pkgs.cloc
|
||||||
pkgs.freetype
|
pkgs.freetype
|
||||||
|
pkgs.grcov
|
||||||
pkgs.libclang
|
pkgs.libclang
|
||||||
pkgs.mold
|
pkgs.mold
|
||||||
pkgs.cloc
|
pkgs.pkg-config
|
||||||
pkgs.watchexec
|
pkgs.watchexec
|
||||||
];
|
];
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,19 @@ mod ticker; pub use self::ticker::*;
|
||||||
mod timebase; pub use self::timebase::*;
|
mod timebase; pub use self::timebase::*;
|
||||||
mod clock_view; pub use self::clock_view::*;
|
mod clock_view; pub use self::clock_view::*;
|
||||||
|
|
||||||
impl <T: AsRef<Clock>+AsMut<Clock>> HasClock for T {}
|
impl App {
|
||||||
|
/// 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T: AsRef<Clock>+AsMut<Clock>> HasClock for T {}
|
||||||
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||||
fn clock (&self) -> &Clock { self.as_ref() }
|
fn clock (&self) -> &Clock { self.as_ref() }
|
||||||
fn clock_mut (&mut self) -> &mut Clock { self.as_mut() }
|
fn clock_mut (&mut self) -> &mut Clock { self.as_mut() }
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,39 @@
|
||||||
use crate::{*, device::*};
|
use crate::{*, device::*};
|
||||||
|
|
||||||
/// Various possible dialog modes.
|
pub fn draw_dialog (
|
||||||
///
|
to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &impl Expression
|
||||||
/// ```
|
) -> Drawn<u16> {
|
||||||
/// let dialog: tek::Dialog = Default::default();
|
match frags.next() {
|
||||||
/// ```
|
Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog {
|
||||||
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
|
//Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{
|
||||||
#[default] None,
|
//let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
|
||||||
Help(usize),
|
//let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
|
||||||
Menu(usize, MenuItems),
|
//fg_bg(f, b, item.full_w().align_x().exact_h(2)).push_y(index as u16 * 4)
|
||||||
Device(usize),
|
//})).full_wh())
|
||||||
Message(Arc<str>),
|
//let items = items.clone();
|
||||||
Browse(BrowseTarget, Arc<Browse>),
|
//let selected = selected;
|
||||||
Options,
|
//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()).draw(to)?;
|
||||||
|
//}
|
||||||
|
//Ok(Some(to.area().into()))
|
||||||
|
//}))
|
||||||
|
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).align_x()//.exact_wh(20, 3)//.exact_wh(20, 3).align_x()//.push_y(index as u16 * 4).
|
||||||
|
})).exact_w(20).align_x())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}.draw(to),
|
||||||
|
_ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'n> Namespace<'n, Dialog> for App {
|
impl App {
|
||||||
fn namespace (&self, src: impl Language) -> Perhaps<Dialog> {
|
pub fn get_dialog (&self, src: impl Language) -> Perhaps<Dialog> {
|
||||||
src.word()?.map(|word|Ok(match word {
|
src.word()?.map(|word|Ok(match word {
|
||||||
":dialog/none" => Dialog::None,
|
":dialog/none" => Dialog::None,
|
||||||
":dialog/options" => Dialog::Options,
|
":dialog/options" => Dialog::Options,
|
||||||
|
|
@ -47,8 +64,87 @@ impl<'n> Namespace<'n, Dialog> for App {
|
||||||
)
|
)
|
||||||
})).transpose()
|
})).transpose()
|
||||||
}
|
}
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
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!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Various possible dialog modes.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let dialog: tek::Dialog = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
|
||||||
|
#[default] None,
|
||||||
|
Help(usize),
|
||||||
|
Menu(usize, MenuItems),
|
||||||
|
Device(usize),
|
||||||
|
Message(Arc<str>),
|
||||||
|
Browse(BrowseTarget, Arc<Browse>),
|
||||||
|
Options,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of menu items.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let items: tek::MenuItems = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)] pub struct MenuItems(
|
||||||
|
pub Arc<[MenuItem]>
|
||||||
|
);
|
||||||
|
|
||||||
|
/// An item of a menu.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let item: tek::MenuItem = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)] pub struct MenuItem(
|
||||||
|
/// Label
|
||||||
|
pub Arc<str>,
|
||||||
|
/// Callback
|
||||||
|
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
||||||
|
);
|
||||||
|
|
||||||
|
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||||
|
|
||||||
|
impl_default!(MenuItem: Self("".into(), Arc::new(Box::new(|_|Ok(())))));
|
||||||
|
|
||||||
|
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 } }
|
||||||
|
|
||||||
impl Dialog {
|
impl Dialog {
|
||||||
/// ```
|
/// ```
|
||||||
/// let _ = tek::Dialog::welcome();
|
/// let _ = tek::Dialog::welcome();
|
||||||
|
|
@ -56,16 +152,20 @@ impl Dialog {
|
||||||
pub fn welcome () -> Self {
|
pub fn welcome () -> Self {
|
||||||
Self::Menu(1, MenuItems([
|
Self::Menu(1, MenuItems([
|
||||||
|
|
||||||
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem(" Resume session \n".into(), Arc::new(Box::new(|_app|{
|
||||||
|
Ok(())
|
||||||
|
}))),
|
||||||
|
|
||||||
MenuItem("New session".into(), Arc::new(Box::new(|app|Ok({
|
MenuItem(" Create session \n".into(), Arc::new(Box::new(|app|Ok({
|
||||||
app.dialog = Dialog::None;
|
app.dialog = Dialog::None;
|
||||||
app.mode = Some(":arranger".into());
|
app.mode = Some(":arranger".into());
|
||||||
})))),
|
})))),
|
||||||
|
|
||||||
MenuItem("Load session".into(), Arc::new(Box::new(|_|Ok(())))),
|
MenuItem(" Load session \n".into(), Arc::new(Box::new(|_|{
|
||||||
|
Ok(())
|
||||||
|
}))),
|
||||||
|
|
||||||
MenuItem("Exit".into(), Arc::new(Box::new(|app|Ok({
|
MenuItem(" Exit \n".into(), Arc::new(Box::new(|app|Ok({
|
||||||
app.exit.exit();
|
app.exit.exit();
|
||||||
println!("Exited cleanly.")
|
println!("Exited cleanly.")
|
||||||
})))),
|
})))),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,61 @@
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Contains state for viewing and editing a clip.
|
/// Contains state for viewing and editing a clip.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
|
||||||
impl_default!(MenuItem: Self("".into(), Arc::new(Box::new(|_|Ok(())))));
|
|
||||||
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 } }
|
|
||||||
|
|
||||||
/// List of menu items.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let items: tek::MenuItems = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)] pub struct MenuItems(
|
|
||||||
pub Arc<[MenuItem]>
|
|
||||||
);
|
|
||||||
|
|
||||||
/// An item of a menu.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let item: tek::MenuItem = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone)] pub struct MenuItem(
|
|
||||||
/// Label
|
|
||||||
pub Arc<str>,
|
|
||||||
/// Callback
|
|
||||||
pub Arc<Box<dyn Fn(&mut App)->Usually<()> + Send + Sync>>
|
|
||||||
);
|
|
||||||
21
src/tek.edn
21
src/tek.edn
|
|
@ -1,7 +1,7 @@
|
||||||
(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)
|
(view :logo (bsp/s (text ~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )
|
||||||
(bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~)
|
(bsp/s (text ~~~~ ║ ~ ╟─╌ ~╟─< ~~ heatwave is the new darkwave ~~ )
|
||||||
(bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)
|
(bsp/s (text ~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ )
|
||||||
(text powered by tengri & dizzle\n)))))
|
(text)))))
|
||||||
|
|
||||||
(view :browse (bsp/s
|
(view :browse (bsp/s
|
||||||
(padding 3 1 :browse-title)
|
(padding 3 1 :browse-title)
|
||||||
|
|
@ -14,7 +14,12 @@
|
||||||
:transport)
|
:transport)
|
||||||
|
|
||||||
(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm)
|
(mode :menu (name Menu) (info Mode selector.) (keys :axis/y :confirm)
|
||||||
(view (bg (g 16) (bsp/s (bsp/w (bg (g 32) :logo) (bsp/e :ports/out (bsp/e :transport :ports/in))) :dialog/menu))))
|
(view (bsp/s
|
||||||
|
(align/s (bsp/e :ports/out
|
||||||
|
(bsp/e :transport
|
||||||
|
:ports/in)))
|
||||||
|
(align/c (bsp/s (align/x (bg (g 36) :logo))
|
||||||
|
(bg (g 24) :dialog/menu))))))
|
||||||
|
|
||||||
(view :ports/out
|
(view :ports/out
|
||||||
(bsp/s (align/w (text L-AUDIO-OUT))
|
(bsp/s (align/w (text L-AUDIO-OUT))
|
||||||
|
|
@ -22,7 +27,7 @@
|
||||||
(align/e (text AUDIO-OUT-R)))))
|
(align/e (text AUDIO-OUT-R)))))
|
||||||
|
|
||||||
(view :ports/in
|
(view :ports/in
|
||||||
(bsp/s (fill/x (align/w (text L-AUDIO-IN)))
|
(bsp/s (align/w (text L-AUDIO-IN))
|
||||||
(bsp/e (text MIDI-IN)
|
(bsp/e (text MIDI-IN)
|
||||||
(align/e (text AUDIO-IN-R)))))
|
(align/e (text AUDIO-IN-R)))))
|
||||||
|
|
||||||
|
|
@ -71,9 +76,7 @@
|
||||||
(mode :track (keys :track))
|
(mode :track (keys :track))
|
||||||
(mode :scene (keys :scene))
|
(mode :scene (keys :scene))
|
||||||
(mode :mix (keys :mix))
|
(mode :mix (keys :mix))
|
||||||
(view (bsp/n :status (bsp/w :meters/output (bsp/e :meters/input (bsp/n :tracks/inputs
|
(view (bg (g 64) (bsp/n :status (text test)))))
|
||||||
(bsp/s :tracks/outputs (bsp/s :tracks/names (bsp/s :tracks/devices
|
|
||||||
(fill (either :mode/editor (bsp/e :scenes/names :editor) :scenes)))))))))))
|
|
||||||
|
|
||||||
(keys :back (@escape back))
|
(keys :back (@escape back))
|
||||||
(keys :confirm (@enter confirm))
|
(keys :confirm (@enter confirm))
|
||||||
|
|
|
||||||
242
src/tek.rs
242
src/tek.rs
|
|
@ -243,10 +243,7 @@ pub use self::config::*;
|
||||||
mod config {
|
mod config {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use notify_debouncer_full::{
|
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
|
||||||
new_debouncer_opt, Debouncer, DebounceEventResult, NoCache,
|
|
||||||
notify::{RecursiveMode, RecommendedWatcher, PollWatcher, Config as NotifyConfig, Watcher}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Configuration: mode, view, and bind definitions.
|
/// Configuration: mode, view, and bind definitions.
|
||||||
///
|
///
|
||||||
|
|
@ -388,11 +385,11 @@ mod config {
|
||||||
Ok(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 handler = {
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
move |result|match result {
|
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());
|
*config.error.write().unwrap() = Some(format!("{e:?}").into());
|
||||||
panic!("{e:?}");
|
panic!("{e:?}");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -405,9 +402,10 @@ mod config {
|
||||||
};
|
};
|
||||||
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new(
|
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new(
|
||||||
handler,
|
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:?}");
|
//println!("watching: {path:?}");
|
||||||
watcher.watch(&path, RecursiveMode::NonRecursive)?;
|
watcher.watch(&path, RecursiveMode::NonRecursive)?;
|
||||||
*config.watch.write().unwrap() = Some(watcher);
|
*config.watch.write().unwrap() = Some(watcher);
|
||||||
|
|
@ -688,6 +686,7 @@ mod app {
|
||||||
#[namespace(Option<u16> App::get_opt_u16)]
|
#[namespace(Option<u16> App::get_opt_u16)]
|
||||||
#[namespace(Option<usize> App::get_opt_usize)]
|
#[namespace(Option<usize> App::get_opt_usize)]
|
||||||
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
||||||
|
#[namespace(Dialog App::get_dialog)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Exit flag
|
/// Exit flag
|
||||||
pub exit: Exit,
|
pub exit: Exit,
|
||||||
|
|
@ -857,55 +856,6 @@ mod app {
|
||||||
})).transpose()
|
})).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.
|
/// 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 }
|
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> (
|
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||||
|
|
@ -1369,7 +1266,6 @@ mod device {
|
||||||
pub mod clock; pub use self::clock::*;
|
pub mod clock; pub use self::clock::*;
|
||||||
pub mod dialog; pub use self::dialog::*;
|
pub mod dialog; pub use self::dialog::*;
|
||||||
pub mod editor; pub use self::editor::*;
|
pub mod editor; pub use self::editor::*;
|
||||||
pub mod menu; pub use self::menu::*;
|
|
||||||
pub mod meter; pub use self::meter::*;
|
pub mod meter; pub use self::meter::*;
|
||||||
pub mod mix; pub use self::mix::*;
|
pub mod mix; pub use self::mix::*;
|
||||||
pub mod sampler; pub use self::sampler::*;
|
pub mod sampler; pub use self::sampler::*;
|
||||||
|
|
@ -1510,53 +1406,55 @@ mod draw {
|
||||||
/// Then, every top-level form of the DSL description is rendered.
|
/// Then, every top-level form of the DSL description is rendered.
|
||||||
impl View<Tui> for App {
|
impl View<Tui> for App {
|
||||||
fn view (&self) -> impl Draw<Tui> {
|
fn view (&self) -> impl Draw<Tui> {
|
||||||
draw(|to: &mut Tui|{
|
self.perf.cycle(&mut |_|{
|
||||||
let xywh = to.area().into();
|
draw(|to: &mut Tui|{
|
||||||
|
let xywh = to.area().into();
|
||||||
|
|
||||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||||
e.as_ref().align_c().draw(to)?;
|
e.as_ref().align_c().draw(to)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mode
|
self.mode
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m|self.config.get_mode(m))
|
.and_then(|m|self.config.get_mode(m))
|
||||||
.map(|mode|{
|
.map(|mode|{
|
||||||
|
|
||||||
//south!(
|
//south!(
|
||||||
//format!("Mode: {:?}", self.mode.as_ref()),
|
//format!("Mode: {:?}", self.mode.as_ref()),
|
||||||
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
||||||
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
||||||
//format!("View #{index} {line}")
|
//format!("View #{index} {line}")
|
||||||
//}))
|
//}))
|
||||||
//).align_nw().draw(to)?;
|
//).align_nw().draw(to)?;
|
||||||
|
|
||||||
for (index, dsl) in mode.view.iter().enumerate() {
|
for (index, dsl) in mode.view.iter().enumerate() {
|
||||||
match self.interpret(to, dsl) {
|
match self.interpret(to, dsl) {
|
||||||
Ok(Some(XYWH(.., w, h))) => {
|
Ok(Some(XYWH(.., w, h))) => {
|
||||||
self.size.0.store(w as usize, Relaxed);
|
self.size.0.store(w as usize, Relaxed);
|
||||||
self.size.1.store(h as usize, Relaxed);
|
self.size.1.store(h as usize, Relaxed);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
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);
|
let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src);
|
||||||
*self.error.write().unwrap() = Some(message.into());
|
*self.error.write().unwrap() = Some(message.into());
|
||||||
return Err(e);
|
return Err(e);
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
*self.error.write().unwrap() = None;
|
*self.error.write().unwrap() = None;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
east(
|
east(
|
||||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||||
format!("{} ", self.perf.clock.raw() / 1000000000),
|
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000),
|
||||||
).align_se().draw(to)?;
|
).align_se().draw(to)?;
|
||||||
|
|
||||||
Ok(Some(xywh))
|
Ok(Some(xywh))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1577,7 +1475,7 @@ mod draw {
|
||||||
Some(":sessions") => view_sessions().draw(to),
|
Some(":sessions") => view_sessions().draw(to),
|
||||||
Some(":browse/title") => view_browse_title(self).draw(to),
|
Some(":browse/title") => view_browse_title(self).draw(to),
|
||||||
Some(":device") => view_device(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(":editor") => "TODO Editor".draw(to),
|
||||||
Some(":transport") => view_transport(true, "", "", "").draw(to),
|
Some(":transport") => view_transport(true, "", "", "").draw(to),
|
||||||
Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).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> {
|
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;
|
let height = (state.config.modes.len() * 2) as u16;
|
||||||
draw(move |to: &mut Tui|{
|
draw(move |to: &mut Tui|{
|
||||||
|
|
@ -1727,16 +1599,14 @@ mod draw {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||||
field_v(ItemTheme::default(),
|
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
||||||
match state.dialog.browser_target().unwrap() {
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
BrowseTarget::SaveProject => "Save project:",
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
BrowseTarget::LoadProject => "Load project:",
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
||||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)
|
|
||||||
).align_w().full_w()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||||
|
|
|
||||||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
||||||
Subproject commit cf67185ffde1719bd5cc0c75cb0f9ac49d95a6d7
|
Subproject commit 799e49762250f48778dadd92874a29495114a6af
|
||||||
Loading…
Add table
Add a link
Reference in a new issue